💡 프로젝트를 시작하기에 앞서서 프로젝트의 패키지 구조에 관련된 사항들을 정하게 되는데요.
대표적으로 사용하시는 구조는 계층형과 도메인형이 있습니다.
이번 포스트에서는 도메인형 구조에 대해서 구체적으로 알아보도록 하겠습니다.
📌 계층형 구조와 도메인형 구조의 차이
✅ 계층형 구조
src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── demo
│ │ ├── DemoApplication.java
│ │ ├── config
│ │ ├── controller
│ │ ├── dao
│ │ ├── domain
│ │ ├── exception
│ │ └── service
│ └── resources
│ └── application.properties
계층형 구조는 애플리케이션을 기능별로 나누어 각 기능이 독립적으로 동작하도록 하는 방식입니다. 예를 들어 Controller들은 Controller 패키지에 넣고, Service는 모두 Service 패키지에 넣게 됩니다. 역할(기능)에 따라서 패키지 구조를 설계하는 구조입니다.
✅ 도메인형 구조
src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── demo
│ │ ├── DemoApplication.java
│ │ ├── coupon
│ │ │ ├── controller
│ │ │ ├── domain
│ │ │ ├── exception
│ │ │ ├── repository
│ │ │ └── service
│ │ ├── member
│ │ │ ├── controller
│ │ │ ├── domain
│ │ │ ├── exception
│ │ │ ├── repository
│ │ │ └── service
│ │ └── order
│ │ ├── controller
│ │ ├── domain
│ │ ├── exception
│ │ ├── repository
│ │ └── service
│ └── resources
│ └── application.properties
도메인형 구조는 도메인에 초점을 맞추어 코드를 구성하는 방식입니다. 이 방식에서는 관련된 기능들을 도메인 단위로 그룹화한다. 예를 들어, 사용자 관련 클래스는 user 패키지에, 제품 관련 클래스는 product 패키지에 넣는 방식입니다. 여기서 도메인은 꼭 Member 나 쿠폰처럼 특정 엔티티에 관련해서 묶일 필요는 없습니다. 예를 들어, '결제 서비스'를 도메인으로 결정했다면, 그와 관련된 Coupon과 Member 모두 하나의 도메인에 들어갈 수도 있습니다.
✅ 구조의 차이에 대해서
사실, 패키지 구조라는 것은 보기 좋게 패키지를 정리해놓은 약속과 같습니다. 따라서, 패키지 구조마다 대단한 기능이 숨겨져 있거나 어떤 것이 항상 좋다 이런 것이 정해져 있지 않습니다. 패키지 구조의 특징 또한 굉장히 직관적이고 상식적으로 알 수 있습니다. 위의 패키지 구조를 보면서 자세히 설명을 해드리겠습니다.
먼저 계층형 구조를 사용하는 경우에 대해서 생각해보겠습니다.
👍🏻 장점
- 프로젝트의 구조가 한눈에 보인다
프로젝트를 진행하는 과정에서 Demo 디렉터리 하위 폴더를 많이 보게 될 것 같은데요.
당연하게도 디렉토리가 많이 없고 기능별로 구현이 되어 있기 때문에 구조가 잘 보일 수 있겠습니다.
- 기능에 나누어진 구조로 매우 직관적임
Controller를 만들고 싶으면 그냥 Controller 폴더에 만들면 되는 간단한 구조를 가지고 있습니다! 도메인형 구조는 Controller라도 어떤 것에 관련한 Controller인지에 따라 디렉토리가 달라지는데요, 계층형은 그냥 Controller는 전부 Controller 디렉토리에 들어가게 됩니다. 새로 만들 때에도 Controller에 넣고 어떤 Controller를 찾을 때에도 Controller 디렉토리만 보면 되니까 아주 간단합니다.
👎🏻 단점
- 프로젝트의 규모가 커지면 복잡해져요
계층형은 디렉토리 분류가 많지 않은 구조를 가지고 있어요. 이 말은 프로젝트의 구조가 커지게 되면 추가적인 정리가 되지 않기 때문에 하나의 디렉토리에 많은 클래스가 쌓일 수 있다는 말입니다. 엔티티가 많아지고 기능도 많아짐에 따라서 Controller, Service, 그리고 Repository도 많아질텐데.. 하나의 디렉토리에 있으면 보기 힘든 상황이 올 수도 있겠죠?
다음으로 도메인형 구조입니다.
👍🏻 장점
- 큰 규모의 프로젝트에 용이하며 확장에 유리하다
도메인이라는 것을 정의함에 따라서 디렉토리가 추가 생성되기도 하며 이는 프로젝트의 규모가 커지고 확장될 경우 매우 유리한 특징입니다. 계층형 구조는 바뀌지 않는 디렉토리에 계속 클래스가 쌓이겠지만, 도메인형 구조에서는 하나의 파일에 클래스가 넘처나는 일은 잘 발생하지 않습니다!
- 하나의 도메인 디렉토리는 높은 응집력을 가지고 있다.
하나의 도메인의 하위 구조의 디렉토리들은 높은 응집력을 가지고 있습니다.. 계층형 구조에서는 Controller라는 디렉토리에 분류된 클래스들은 그냥 Controller라는 기능을 하기 때문에 분류되지만, 도메인형 구조에서는 어떤 도메인의 하위 디렉토리의 Controller는 해당 도메인과 관련된 Controller들이 들어 있게 됩니다.
- 유지보수가 쉽다
특정 주제(도메인)에 따라서 분류가 되어 있기 때문에 유지보수를 하기 용이합니다. 예를 들어 사용자 가입 서비스를 유지보수 하고 싶다면 관련된 도메인의 클래스들만 보면 되기 때문에 편리함이 높아집니다.
👎🏻 단점
- 프로젝트 구조가 한눈에 들어오지 않는다.
Demo 하위에 디렉토리가 적은 계층형 구조와 다르게 도메인형 구조는 디렉토리가 많기 때문에 프로젝트 구조가 한눈에 들어오지 않을 수 있습니다. 하지만, 반대로 디렉토리마다의 클래스가 적어지게 되는 장점이 있습니다.
- 도메인이란 것을 나누기 애매할 수 있다고 생각합니다.
프로젝트가 반드시 도메인에 따라서 분리하게 애매할 수 있습니다. 예를 들어서, 쇼핑몰 서비스를 생각해 보겠습니다. 쇼핑몰의 주문 서비스는 필수적으로 옷이라는 개념과 관련되어 있습니다. 따라서, 주문이라는 도메인을 만들고 회원과 서비스에 대한 엔티티를 하위 폴더에 정리했습니다. 또 다른 도메인으로 옷 재고 관리 서비스를 만들었다고 해봅시다, 이 서비스에도 옷 엔티티가 필요하게 될 텐데 도메인별로 분리하기 애매한 경우가 발생할 수도 있겠습니다.
✅ 개인적으로 선호하는 구조 : DDD
물론 각 구조의 장단점이 존재하고 항상 DDD가 좋다고 확신할 수는 없겠지만 저는 개인적으로 대부분의 상황에서 DDD가 좋다고 생각되는데요. 그 이유는 대부분의 프로젝트 진행의 과정 때문입니다.
- 💠 실습 수준의 프로젝트가 아닌 이상 규모가 생기게 됩니다.
정말 간단하게 만들어도 엔티티 10개는 우습게 넘어가는 경우가 많습니다. 그 엔티티마다 3개의 Service만 만들게 되어도 서비스 디렉토리는 벌써 30개의 클래스로 하나의 디렉토리 안에서 클래스를 찾기가 너무 어렵다고 생각됩니다.
- 💠 확장성을 열어두는 것이 좋다고 생각합니다.
항상 처음 계획대로 프로젝트가 흘러가는 것은 아니죠!
대부분의 프로젝트는 진행하는 과정에서 바뀌는 부분이 정말 많습니다. 그리고 대부분의 경우에 기능을 추가함에 따라서, 규모가 조금씩 커질 수도 있는데요 이런 상황을 대비해서 확장성에 유리한 DDD가 큰 장점을 발휘한다고 생각합니다.
- 💠 프로젝트에서는 분업이 이루어진다
결국 이 부분이 DDD에서 가장 매력을 느끼게 된 부분인데요.
하나의 프로젝트에서 혼자 서버 개발을 전부 할 수는 없기 때문에 당연히 역할 분배가 이루어집니다!
결국 프로젝트를 하게 되니 현재 내가 작업하고 있는 도메인의 디렉토리만 보면서 작업한다는 점이 매우 매력적으로 느껴졌습니다.
위에서 든 예시를 기준으로 본다면 coupon과 관련된 부분을 작업하고 있다면 해당 디렉토리만 볼 수 있다는 점이 매우 편리하고 클래스가 많지 않아서 개인적으로 작업 능률이 높아졌다고 생각합니다.
결과적으로 정답은 없으나 위와 같은 이유로 저는 DDD를 앞으로 많이 사용하게 될 것 같습니다. ✌🏻
📌 DDD 구조 파헤치기
이제 본격적으로 DDD 구조에 대해서 알아보겠습니다. 위에서 설명을 읽으셨다면 DDD의 개요에 대해서는 간단히 이해를 하고 계층형 구조와의 차이를 인지하셨을 텐데요. 저는 이쯤에서 고민이 있었습니다.
🤔 도메인(Domain)이 그래서 정확히 무엇일까요?
DDD구조에서 가장 중요한 개념인 도메인(Domain). 결국 이것에 따라서 분류를 하고 디렉토리를 만들어야 하는데요, 암묵적으로는 도메인이라는 게 무엇인지 인지하고 계신 분들은 많이 있을 것 같습니다.
하지만, 개인적으로는 완벽히 도메인이란 것을 설명하기는 쉽지 않았습니다.
따라서, 구조에 대한 내용을 설명하기에 앞서서 도메인이 무엇인지 정확히 짚고 넘어가 보겠습니다.
✅ 도메인(Domain)
DDD에서 말하는 도메인은 비즈니스 도메인입니다. 도메인에 대해서 알아보던 와중 굉장히 와닿게 정리하신 블로그를 참고했습니다.
도메인이란 사전적 의미는 '영역', '집합'이다.
'실세계에서 사건이 발생하는 집합'이라고 생각하면 쉬울 것 같다.
DDD에서 말하는 도메인은 비즈니스 도메인을 말하며, 비즈니스 도메인은 유사한 업무의 집합이다.
쇼핑몰을 예로 들면, 쇼핑몰에서는 손님들이 주문하는 도메인(Order Domain)이 있을 수 있고,
직원입장에선 옷들을 관리하는 도메인(Manage Domain)이 있을 수 있고,
결제를 담당하는 도메인(Payment Domain)이 있을 수 있다.
도메인(Domain)과 객체(Object)의 차이
객체의 의미는 아주 간단하게 말해서 현실 세계의 개념들을 프로그래밍에 반영하는 것을 말한다.
객체는 추상화 또는 구체화할 수 있는 특정 요소만을 표현하는 반면,
도메인은 사용자가 사용하는 모든 것을 설명할 수 있다.
예를 들면, "고양이는 사과를 먹는다."
객체의 관점에서는 "고양이"와 "사과"를 표현할 수 있고, "먹는다"는 객체가 하는 행위로 별도로 표현한다.
도메인의 관점에서는 "고양이", "사과", "먹는다", "고양이는 사과를 먹는다." 모두 각각 도메인이라고 할 수 있다.
출처: 티스토리 : 슬기로운 개발생활
- 💡 결국 DDD에서 도메인이란 내가 정한 '분류' 또는 '영역'으로 생각하면 편합니다.
쇼핑몰 구현을 생각해 봅시다.
"프로젝트 API 분류를 만들고 API 명세서를 만들어야겠다 그런데 어떻게 분류하면 좋지?"
- 결제 서비스
- 옷 재고 관리
- 회원 관리
이렇게 3개로 분류를 하셨다면 3개의 도메인을 설계하신 겁니다.
위의 예시로 프로젝트를 시작한다면 Demo 하위 디렉토리에 3개의 디렉토리가 생기겠네요!
이렇게 프로젝트에서 주제 영역을 분류를 하면 그 주제 영역이 하나의 도메인, 즉, 독립적인 도메인이 된다고 볼 수 있습니다.
결국 DDD는 내가 정한 도메인에 따라서 디렉토리를 분류하여 사용하면 되는 구조입니다!
✅ DDD 구조의 기본
가장 기본적인 DDD의 구조입니다.
Layered Architecture의 2가지 규칙
- 위의 계층에서 아래 계층에는 접근이 가능하지만 아래에서 위로는 불가능한 것을 기본으로 한다
이건 유지보수와도 관련이 있는 이야기입니다. DDD 뿐만 아니라 모든 구조에서도 의존관계가 복잡하면 유지보수가 어려워지니까요!
이 부분에 대해서 DIP를 추가적으로 공부하시면 이해가 편할 것 같아요.
-한 계층의 관심사와 관련된 어떤 것도 다른 계층에 배치되어서는 안 된다.
💠 PRESENTATION LAYER(표현 영역)
- 사용자가 접근할 수 있는 유일한 영역으로 사용자와 응용 영역을 연결합니다.
- 사용자에게 UI를 제공하거나 클라이언트에 응답을 다시 보내는 역할을 하는 모든 클래스가 포함된다.
- Controller가 대표적인 예시
💠 APPLICATION LAYER(응용 영역)
- 비즈니스 로직을 수행하고 domain과 presentation 계층을 연결해 줍니다.
- 정보를 많이 가지고 있지 않도록 설계를 해야 합니다.
- 실질적인 데이터의 상태 변화 같은 처리는 도메인 계층에 위임한다.
(Service 보다는 Repository나 엔티티에 메서드를 만들어 주는 것이 좋아요.) - 대표적으로 Service가 해당된다 ex) 주문 등록, 주문 취소, 상품 등록
- 엔티티 조회 저장 - 트랜잭션의 단위, DTO 변환, 엔티티 조회/저장 등이 수행된다.
- 사용자 인증/인가 - URL 만으로 판단이 어렵거나 DB내의 데이터를 대조해봐야 하는 경우( 존재, 중복)
- 파라미터 검증(Validation) - 논리적 오류를 검증한다.
💠 DOMAIN LAYER(도메인 계층)
- 비즈니스 규칙, 정보에 대한 실질적인 도메인에 대한 정보를 책임지는 계층.
- 대표적으로 Entity와 Repository가 있다.
💠 INFRASTRUCTURE LAYER(인프라 영역)
- 외부와의 통신(DB, 메시징 시스템 등)을 담당하는 계층이다.
- 외부의 정보를 응용 계층과 도메인 계층에 공유한다.
- 대표적으로 Configuration과 구현체 클래스들이 여기에 속한다.
예시로 가장 대표적인 Spring Pet Clinic의 구조를 살펴보겠습니다.
😲 위에서 설명한 Layer에 따라서 디렉토리가 짜여 있고 내부에 속하는 것들이 잘 연결되고 있습니다.
이제 완벽히 DDD의 구조에 대해서 이해를 하셨어요!
하지만,
모듈 구조를 얼마나 세분화해야 하는지에 대해 정해진 규칙은 없기 때문에, 한 패키지에 너무 많은 클래스가 모이는 정도만 아니게 Custom화 해서 사용할 수 있습니다. ( 위의 예시만 보아도 저는 개인적으로 Repository와 RepositoryImpl이 함께 있어도 좋을 것 같은데 말이죠..?)
마지막으로 예시와 개인적인 의견을 말씀드리고 포스트를 마무리하겠습니다.
✅ DDD 커스텀화 예시 (출처 - Yun Blog)
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── spring
│ │ └── guide
│ │ ├── ApiApp.java
│ │ ├── SampleApi.java
│ │ ├── domain
│ │ │ ├── coupon
│ │ │ │ ├── api //Controller
│ │ │ │ ├── application // Service
│ │ │ │ ├── dao //Repository
│ │ │ │ ├── domain //Entity
│ │ │ │ ├── dto
│ │ │ │ └── exception
│ │ │ ├── member
│ │ │ │ ├── api
│ │ │ │ ├── application
│ │ │ │ ├── dao
│ │ │ │ ├── domain
│ │ │ │ ├── dto
│ │ │ │ └── exception
│ │ │ └── model
│ │ │ ├── Address.java
│ │ │ ├── Email.java
│ │ │ └── Name.java
│ │ ├── global
│ │ │ ├── common
│ │ │ │ ├── request
│ │ │ │ └── response
│ │ │ ├── config
│ │ │ │ ├── SwaggerConfig.java
│ │ │ │ ├── properties
│ │ │ │ ├── resttemplate
│ │ │ │ └── security
│ │ │ ├── error
│ │ │ │ ├── ErrorResponse.java
│ │ │ │ ├── GlobalExceptionHandler.java
│ │ │ │ └── exception
│ │ │ └── util
│ │ └── infra
│ │ ├── email
│ │ └── sms
│ │ ├── AmazonSmsClient.java
│ │ ├── SmsClient.java
│ │ └── dto
│ └── resources
│ ├── application-dev.yml
│ ├── application-local.yml
│ ├── application-prod.yml
│ └── application.yml
DDD에 구조의 정체성을 가져가지만, 세부적인 디렉토리가 다른 것을 볼 수 있습니다.
디렉터리의 이유에 대해서는 하단 Reference에서 링크를 참조해 주세요
❗ 이에 따라 DDD의 장점을 가져가면서도 어느 정도 직관성을 가지고 있는 디렉토리 구조를 사용할 수 있다고 생각합니다.
하지만, Layer에 대한 구분이 조금 모호할 수도 있다고 생각됩니다.
따라서 저는 개인적으로 아래와 같은 디렉토리를 만들어 보았습니다!
✅ 나만의 DDD 패키지
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── clokey
│ │ └── server
│ │ ├── ServerApplication.java
│ │ ├── domain
│ │ │ ├── member
│ │ │ │ ├── presentation // Controller
│ │ │ │ ├── domain
│ │ │ │ │ ├── repository
│ │ │ │ │ └── entity
│ │ │ │ ├── application
│ │ │ │ │ ├── service
│ │ │ │ │ ├── converter
│ │ │ │ │ ├── dto
│ │ │ │ │ └── validation
│ │ │ │ ├── infrastructure
│ │ │ │ │ ├── persistence // 구현체
│ │ │ │ │ └── config
│ │ │ ├── ...
│ │ │ └── model
│ │ │ ├── Address.java
│ │ │ ├── Email.java
│ │ │ └── Name.java
│ │ ├── global
│ │ │ ├── common
│ │ │ │ └── response
│ │ │ │ └── BaseResponse.java
│ │ │ ├── config
│ │ │ │ ├── SwaggerConfig.java
│ │ │ │ ├── properties
│ │ │ │ ├── resttemplate
│ │ │ │ └── security
│ │ │ │ └── SecurityConfig.java
│ │ │ ├── error
│ │ │ │ ├── code
│ │ │ │ │ ├── BaseErrorCode.java
│ │ │ │ │ ├── ErrorReasonDTO.java
│ │ │ │ │ ├── ReasonDTO.java
│ │ │ │ │ ├── ErrorStatus.java
│ │ │ │ │ └── status
│ │ │ │ └── exception
│ │ │ │ ├── GeneralException.java
│ │ │ │ └── GlobalExceptionAdvice.java
│ │ │ └── util
│ │ └── infra
└── resources
└── application.yml
디렉토리가 조금 많아진 부분이 있습니다.
하지만, 계층 간의 구분을 추가하고 의존 관계를 신경 쓰면서 유지 보수성이 높은 개발을 하는 것에 초점을 두었습니다!
🙌🏻 각자의 프로젝트의 특성에 맞게 커스텀화 해서 사용하면 되겠습니다!
Reference :
Dzone - Layered Architecture is good (Grzegorz Ziemoński)🔗
깃허브 - PetClinic 🔗
티스토리 - Yun Blog 🔗
티스토리 - 기록과 정리의 공간 🔗
티스토리 - 슬기로운 개발생활 🔗
'프로젝트' 카테고리의 다른 글
배포의 모든 것 - 2. RDS와 Session Manager (0) | 2025.02.25 |
---|---|
배포의 모든 것 - 1. AWS 시작하기 및 EC2 띄우기 (5) | 2025.01.25 |
Restful API Endpoint를 어떻게 설계해야 할까? (1) | 2025.01.14 |
API 응답 통일을 파헤치다 (0) | 2025.01.01 |
커스텀 상태 코드(Custom Error Code) (0) | 2025.01.01 |