Spring Boot 개발 중 학습이 필요한 내용을 정리하고,
트러블 슈팅 과정을 기록하는 포스팅입니다.
저는 지금까지 Spring boot
를 활용한 API 서버 개발을 할 때, 디렉터리 구조를 스프링 웹 계층형 구조를 기반으로 패키징 했습니다. 이러한 패키징 방식은 Controller
, Service
, Repository
, Domain
과 같이 각각의 웹 계층을 대표 혹은 구성하는 클래스들로 이루어집니다.
현재 개발하고 있는 프로젝트에서도 이러한 웹 계층형 구조를 기반으로 디렉터리 구조를 설계하려고 하니 해당 방식의 단점과 개선점들이 보였습니다. 때문에 다른 방식의 디렉터리 패키징 구조는 어떤 것이 있고 각각의 장단점이 무엇인지 알아보는 시간을 갖었습니다.
com
ㄴ example
ㄴ sju
ㄴ config
ㄴ controller
ㄴ domain
ㄴ repository
ㄴ service
ㄴ security
ㄴ exception
위 패키지 구조는 일전의 프로젝트에서 계층형 디렉터리 구조를 기반으로 패키징을 한 예시입니다.
앞서 설명했던 것처럼 이러한 방식은 스프링 웹 계층의 대표 클래스 혹은 디렉터리들을 기반으로 패키징됩니다.
스프링 웹계층에 대한 간단한 설명을 해보면 아래와 같습니다.
- Web Layer : 사용자의 요청과 이에 대한 응답 반환의 전반적인 처리가 일어나는 영역을 의미합니다.
- Service Layer :
Web Layer
와Repository Layer
사이에서 실질적인 어플리케이션 비즈니스 로직이 일어나는 영역입니다.
- Repository Layer :
DB
에 접근 및 통신하는 영역입니다.
이러한 각 계층들에는 Controller
, Service
, Repository
등과 같이 그 계층들을 대표하는 다양한 클래스와 디렉터리가 존재하고, 이들을 기반으로 디렉터 구조를 패키징하는 방식이 계층형 디렉터리 구조입니다.
계층형 구조는 전체적인 구조를 빠르게 파악할 수 있는 장점이 있지만, 각각의 패키지 디렉터리에 클래스들이 너무 많이 모이게 된다는 단점이 존재합니다.
저 같은 경우는 이렇게 하나의 디렉터리 안에 다양한 클래스가 모이게 되면서 개발 중 자연스럽게 휴먼 에러가 생기는 비중이 늘어났다고 느낀 경험이 많습니다. 특히 Service
디렉터리에는 SRP 원칙을 준수하기 위해 특정 역할을 하는 전용 구현체 클래스들을 많이 생성하게 되는데, 이렇게 되면 여러 도메인의 클래스들이 개발자 측면에서 다루기 어렵게 뒤섞이게 됩니다.
때문에 저는 아래 설명할 도메인형 디렉터리 구조를 이용해서 패키징 방법을 개선했습니다.
com
ㄴ example
ㄴ vivid
ㄴ domain
| ㄴ user
| | ㄴ api
| | ㄴ application
| | ㄴ dao
| | ㄴ domain
| | ㄴ dto
| | ㄴ exception
| ㄴ video
| | ㄴ api
| | ㄴ application
| | ㄴ dao
| | ㄴ domain
| | ㄴ dto
| | ㄴ exception
| ...
ㄴ global
ㄴ auth
ㄴ common
ㄴ config
ㄴ error
ㄴ infra
ㄴ util
위의 디렉터리 패키지 방식은 이번 프로젝트를 진행하면서 도메인형 디렉터리 구조를 기반으로 제 프로젝트의 성격에 맞게 패키징을 구성한 것입니다.
해당 방식은 스프링 웹 계층에 주목하기보다는 도메인에 주목합니다. 이를 통해서 각각의 도메인 별로 패키지 분리가 가능하여 관리에 있어서 계층형 방식보다 직관적이며, 각각의 도메인들은 서로를 의존하는 코드가 없도록 설계하기 적합해서 코드의 재활용성이 향상됩니다.
또한 OOP
관점과 ORM
기술을 사용함에 있어서 핵심이 되는 Entity
의 특성을 기반으로 패키징하는 것이 해당 기술들의 관점과 지향점과도 맞다고 생각이 듭니다.
그 이유는 때문입니다.
아래로는 각각의 패키지와 이를 구성하는 클래스들에 대한 설명을 하겠습니다.
domain
과 global
로 패키징합니다.domain
패키지에서는 프로젝트와 DB 구조에서 핵심 역할을 하는 domian entitiy
를 기준으로 하위 패키지를 구성합니다.global
패키지에서는 프로젝트 전방위적으로 사용할 수 있는 클래스 들로 구성됩니다.user
, video
와 같이 핵심 domain entity
별로 패키지가 구성됩니다.api
, application
, dao
, domain
, dto
, exception
패키지로 구성됩니다.controller
클래스가 존재합니다. 해당 프로젝트에서 스프링 부트는 Rest API
서버로서의 역할만을 하기 때문에, 명시적으로 api
라는 네이밍으로 패키징 했습니다.service
클래스들이 존재합니다. DB 트랜잭션이 일어나며, 주된 비즈니스 로직을 담당합니다. Service
클래스들 뿐만 아니라, handler
와 같은 같은 성격의 다른 클래스 또한 포함하기 때문에 application
이라는 네이밍으로 패키징 했습니다.dao
, repository
클래스들로 구성됩니다.entity
클래스들로 구성됩니다.dto
클래스들로 구성됩니다.exception
클래스들로 구성됩니다.domain
에 종속되지 않고, 프로젝트 전방위적으로 사용할 수 있는 클래스들이 모여있습니다.auth
, common
, config
, error
, infra
, util
패키지로 구성됩니다.인증
, 인가
와 관련된 클래스들로 구성됩니다.공통 클래스
혹은 공통 value
클래스들로 구성됩니다.configuration
클래스로 구성됩니다.exception
, error
와 관련된 클래스로 구성됩니다.외부 모듈
, api
등을 사용하는 클래스로 구성됩니다.util성 클래스
들로 구성됩니다.기존의 계층형 디렉터리 구조에 대한 불편을 느끼고 도메인형 디렉터리 구조를 적용함으로써 훨씬 더 직관적으로 프로젝트를 관리할 수 있음을 느끼고 있습니다.
특히 Service
클래스 같은 경우는 계층형으로 관리하다보면 클래스들이 많이 모이기 때문에 네이밍하기도 힘들고 관리하기도 힘들어서 능률이 저하됐었습니다. 조금더 직설적으로 말하면 IDE에서 원하는 클래스 파일을 찾기 힘들었습니다ㅎㅎ.. 이러한 불편한 점이 도메인형 디렉터리 구조를 적용함으로써 많이 해소된 것 같습니다.
또한 앞서 말했듯 도메인 별로 의존 관계를 줄일 수 있도록 설계하기 적합해서 코드의 재활용성이 올라감을 체감하고 있습니다.
패키지 방식에 있어서 정해진 답은 없는 것 같습니다. 현재의 방식에 불편을 느끼 언제든지 패키지 구조를 변경하면 되는 것이고, 자신만의 패키징을 추가하면서 발전시켜나가야 합니다.
결국 가장 올바른 패키지 방식은 개발자가 관리하기 쉬운, 능률을 향상 시키는 패키지 방식인 것 같습니다.
https://cheese10yun.github.io/spring-guide-directory/
https://velog.io/@dhwlddjgmanf/%EA%BC%AC%EB%A6%AC%EB%B3%84-%EC%98%A4%EB%A5%98%EC%9D%BC%EC%A7%80-%ED%8C%A8%ED%82%A4%EC%A7%80-%EA%B5%AC%EC%A1%B0%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%A7%9C%EB%8A%94-%EA%B2%8C-%EB%A7%9E%EC%9D%84%EA%B9%8C