📑 개요
지난번에 작성했던 블로그의 내용은 완전 기초적인 내용 및 설정에 대해서 설명을 했다면 이번에는 세부적으로 샘플 코드를 작업하면서 나온 내용에 대해서 정리를 하고 작업 간에 했던 생각을 적어 보려고 한다. :)
📕 구조
BackEnd
내가 이번 프로젝트에서 잡은 구조는 도메인 구조이다. 계층형 구조와 도메인 구조에 대해서 비교를 하는 블로그들을 많이 보았다.내용을 살펴보면 알겠지만 정답은 없다. 장단점에 따라서 프로젝트에 맞는 구조를 사용해서 개발을 진행하면 된다. (유지보수를 위해서라도)
본론으로 돌아와서 도메인 구조를 잡은 이유에 대해서 설명하도록 하겠다.
- 패키지별 분리를 했을때 내용을 파악하는데 더 쉽다고 판단
- 프로젝트 개발간에 기획이 변경되는 상황이 많이 발생하는데 이때 유리하다고 판단
- 확장성을 고려했을때 나쁘지 않을 거 같다는 판단
간단하게 이유를 적어봤다. 현재 신규 프로젝트의 규모가 크지 않아서 사실 어떤 구조로 해도 상관이 없는 상황이라서 선택을 했고, 이후 확장성을 고려했을 때 도메인 구조로 해도 나쁘지 않다고 판단을 했다.

작업을 완료한 샘플 구조이다. 에러가 나는 이유은 Intellij 업데이트를 하고 라이브러리를 다운 받고 빌드를 하지 않아서 생겼다. :(
다른 개발자분들이 작업하는 도메인 구조와 다르지 않다. 도메인을 기준으로 분리를 해서 구현을 할 예정이고, global 패키지에서는 공통으로 구현해야 하는 부분에 대한 내용을 구현할 예정이다. (위의 이미지 참조)
Front
vue의 경우는 vite를 통해서 프로젝트를 생성했다.
vite, cli등등 vue 프로젝트를 만드는 방법은 많다.
npm create vite@latest

vite를 통해서 프로젝트를 만들게 되면 vue 뿐만 아니라 다른 프로젝트도 만들 수 있다.
혹시 나와 같이 vite를 통해서 프로젝트를 만드는 경우에는 공식 홈페이지에 가이드가 잘되어 있으니 한번 읽어보고 진행 하면 좋다.

vite, cli 등 vue 프로젝트를 생성하면 나오는 기본적인 구조이다. 아마 config 명칭이 좀 다를 것이다. config의 경우는 어떤 도구를 사용해서 만들었냐에 따라서 방법이 다르니까 찾아보고 내용 숙지가 필요하다.
추가적으로 샘플 코드 작성을 위한 내용이 조금 추가 되어 있는데 세부 내용은 따로 설명하도록 하겠다.
📘 샘플 코드
샘플 코드를 구현하면서 각각 알아두면 좋은 내용에 대해서 정리한 내용이다.
BackEnd
DGS를 사용한다고 해서 기존에 사용 했던 Graphql 사용과 크게 달라지는 부분은 존재하지 않는다. 혹시 나처럼 spqr을 사용하지 않았다면.. :) 기본적으로 스키마를 정의 하고 서비스를 구현해서 grapgql에서 호출해서 사용하는 방식 그대로를 따라간다.
DGS 플러그인도 있으니 같이 사용하면 개발하는데 도움을 주게 된다 (아래 이미지 참조)

위의 플러그인 사용 방법과 이어지는 sample.graphqls 파일을 작성한 내용을 설명하도록 하겠다.
아래의 이미지는 샘플 스키마를 작성한 내용이며, 우측의 DGS 아이콘을 누르게 되면 맵핑되는 서비스로 이어지도록 해준다. 스키마 정의를 잘해놓는다면 스키마를 확인하고 필요하다면 구현된 서비스로 가서 내용 확인까지 가능하다는 장점이 있다.

추가적으로 스키마에서 스칼라 타입을 사용하려면 라이브러리를 추가하고 사용해야 한다. 이전 게시글에서 build.gradle의 라이브러리 내용을 참조하기 바란다.

아래의 내용은 실제 서비스를 구현한 샘플 서비스 코드이다.
주의 깊게 봐야 하는 점은 DGS에서 제공 해주는 어노테이션을 사용했다는 것이다. 세부적인 클래스 내용은 일부로 내용을 제거했다.
@DgsComponent
class SampleServiceImpl(
private val queryFactory: JPAQueryFactory
) : SampleService {
@DgsQuery
@Transactional(readOnly = true)
override fun findTestList(): List<test_info> {
return null;
}
@DgsQuery
@Transactional(readOnly = true)
override fun findTestInfoById(key: Long): test_info? {
return null;
}
@DgsMutation
@Transactional
override fun saveTestInfo(userInfo: add_test_info): executeQueryResult? {
return null;
}
}
sample.graphqls 와 연결되는 아이콘이 나오고 클릭하면 연결된 스키마로 이동이 된다.

Front
앞단의 경우는 스토어를 사용하는 방식이 좀 바뀌었다. 아무래도 vuex 에서 pinia로 상태 관리 라이브러리를 변경하면서 생긴 내용이다.
간단하게 공식 문서에 나와있는 스토어의 사용 방법에 대해서 설명 하자면 크게 셋업 스토어 방식과 옵션 스토어 방식이 존재한다.
import {defineStore} from 'pinia'
import {ref, computed} from 'vue';
// 셋업 스토어 방식
// ref() = state
// computed() = getter
// function() = actions
export const sampleStore3 = defineStore('sample', () => {
const list = ref([]);
function addList(param){
list.value.push(param);
}
const getAllList = computed(() => list.value);
return {list, addList, getAllList};
});
import {defineStore} from 'pinia'
// 옵션 스토어 방식
export const sampleStore2 = defineStore('sample', {
state: () => ({list: []}),
actions: {
addList(param){
this.list.push(param);
}
},
getters: {
getAllList(state){
return state.list;
}
}
})
위의 내용은 앞단 개발을 위해서 샘플로 두개의 방식에 대해서 간단하게 작업한 내용이다. 아마 세부적으로 개발을 진행하면 더 복잡해질 수도 있고 모르는 부분이 나올 수도 있지만 기본적인 방식만 두고 보면 기존 vuex에서 사용하던 방식보다 편하다고 생각한다.
방식중에서 좀 더 편한 방식을 아무거나 사용해도 상관없다고 한다. 하지만 잘 모르겠다면 옵션 스토어 방식을 추천한다고 공식 홈페이지에서는 말하고 있다.
API 호출
스토어에서 API를 호출할때 사용하는 부분의 경우는 basicInfo.js 파일에 개발을 진행하고 전 스토어에서 공통으로 호출해서 사용하려고 작업을 했다. (테스트 완료)
import axios from 'axios';
import {defineStore} from 'pinia'
export const basicStore = defineStore('basicStore', {
state: () => ({}),
getters: {
// getter setting
},
actions: {
async graphqlRequest(param) {
try {
const address = import.meta.env.VITE_ADDRESS
const port = import.meta.env.VITE_PORT
const result = await axios({
method: "POST",
url: `http://${address}:${port}/graphql`,
data: {
query: param
}
});
if (result.status === 203){
alert("203")
} else if (result.status === 204){
alert("204")
} else {
// 추가 설정.
console.log(result.data.data)
}
return result.data.data;
} catch (error) {
console.error(error)
}
}
},
})
이외 vue3에서는 computed 와 method에서 mapState와 mapAction을 사용하는 방법이 좀 달라졌으니 확인을 해보는 게 좋다.
// 예시
export default {
data: () => ({
text: "",
}),
computed: {
...mapState(sampleStore, ["getAllList"]),
},
methods: {
...mapActions(sampleStore, ["addList"]),
onClickAddList(){
if (!this.text) return;
this.addList(this.text);
this.text = "";
},
},
};
⚡ 생각
신규 프로젝트를 진행 하면서 기본틀을 작업한 내용을 정리해 보았다. 아직 제대로 시작한 것은 아니기 때문에 공통으로 필요한 내용을 정리하고 구현하고 있으며, 기획이 되는대로 개발을 진행할 에정이다.
추가적으로 신규 프로젝트를 하면서 느낀점이지만 새로 하니까 재밌다. :)
'Spring' 카테고리의 다른 글
| [JPA] Entity Default 값 저장 이슈 정리 (0) | 2024.08.21 |
|---|---|
| [QueryDSL] SQLQueryFactory 사용시 LocalDate 타입 반환시 날짜 에러 (0) | 2024.06.01 |
| Spring boot + vue 프로젝트 (1) (1) | 2024.04.04 |
| [JPA] Spring boot 3.x.x 에서 QueryDsl Date Type 사용 방법 (0) | 2024.01.09 |
| [Spring] Spring boot 3.x.x 버전 마이그레이션 내용 정리 (0) | 2023.12.31 |