DDD와 클린아키텍처

박종원·2025년 4월 27일

기본적인 방향

  1. DDD (Domain-Driven Design) : 비지니스 규칙(도메인 모델)에 집중
  2. 클린 아키텍처 : 비지니스 로직이 외부와 독립적

최상위 도메인을 나누는 기준

  • 비지니스 관점에서 독립된 의미 단위 (Bounded Context) → 이걸 우리가 어떻게 나눌 것인가?

클린 레이어드 아키텍처

/src
  ├── user
  │     ├── domain
  │     ├── application
  │     ├── infrastructure
  │     └── interface/presentation
  │
  ├── order
  │     ├── domain
  │     ├── application
  │     ├── infrastructure
  │     └── interface/presentation
  │
  ├── product
  │     ├── domain
  │     ├── application
  │     ├── infrastructure
  │     └── interface/presentation
  │
  ├── shared (공통 유틸, 에러 핸들링, 베이스 클래스)
  │
  └── config (전체 설정, DI 컨테이너, 라우팅 등)
  • Presentation: 요청을 받고 응답을 만드는 책임만 (Controller가 있어야합니다.)
  • Application (UseCase): 유저 요청을 어떻게 처리할지 결정하는 책임만 (요청을 어떻게 처리할 것인가?)
  • Domain (Entity): 비즈니스 규칙을 표현하고 유지하는 책임만 객체가 존재. (JPA Entity랑 달라요)
  • Infrastructure: 외부 시스템(DB, API)과 통신하는 책임만 (JPA Entity는 여기에!)

진정한 클린아키텍처란?

  1. 구조만으로 부족하다.
  2. 코드의 내부 의존성을 잘 지켜야한다. Domain이 Infrastrucutre import하면 실패..
  3. Domain은 Application, Infrastructure, Interface를 모두 몰라야한다.

클린 아키텍처 내부 코딩 규칙 체크리스트

  1. 폴더 구조 기본
    1. 최상단은 도메인 기준을 나누기(user, email, product)
    2. 도메인 내부는 클린 아키텍처 레이어(domain, application, infrastrucutrue, interface(presentation))
  2. Domain Layer 규칙 (순수 비지니스)
    1. Domain에는 도메인 객체만 둔다.
    2. Repository “인터페이스”는 Domain Layer에 정의한다.
  3. Application Layer 규칙
    1. UseCase로 비지니스 흐름을 정의한다. (그냥 우리가 생각하는 Service Class라고 생각해도 무방)
    2. 흐름만 담당!, 진짜 로직은 Domain 객체에 위임하고, 필요한 저장/조회는 Application Layer에서 Repository 인터페이스를 호출한다.
    3. 입출력은 DTO로 관리 (도메인 객체를 그대로 Controller에 Return X)
    4. 트랜잭션 관리
  4. Infrastructure Layer
    1. JPA, API, 파일시스템 등 기술 구현은 Infrastructure에 둔다.
    2. JPA @Entity 클래스 (Infra Entity)는 Infrastructure에 둔다.
    3. Infra Entity ↔ Domain 객체 매핑은 Mapper/Assembler를 통해 한다.
    4. Infra 코드는 Domain 객체를 직접 변경하지 않는다.
  5. 의존성 규칙
    1. interface → application → domain 방향만 허용!
    2. domain은 application, infrastructure, interface를 절대 import하면 안 됨
    3. Cross-domain 호출이 필요하면 Application 레벨에서만 통신한다.

구체적인 레이어드 구조

/src
 ├── user
 │    ├── domain
 │    │    ├── entity
 │    │    │    └── User.java
 │    │    ├── valueobject
 │    │    │    └── UserId.java
 │    │    ├── repository
 │    │    │    └── UserRepository.java
 │    │    └── service
 │    │         └── UserDomainService.java
 │    │
 │    ├── application
 │    │    ├── usecase
 │    │    │    ├── RegisterUserUseCase.java
 │    │    ├── dto
 │    │    │    ├── RegisterUserCommand.java
 │    │    │    ├── RegisterUserResponse.java
 │    │    └── service
 │    │         └── UserApplicationService.java (필요시 UseCase 통합 관리용)
 │    ├── infrastructure
 │    │    ├── repository
 │    │    │    └── UserRepositoryImpl.java (UserRepository 인터페이스 구현체 class!)
 │    │    │    └── UserJPARepository.java (Spring Data JPA를 연결한 진짜 Interface)
 │    │    ├── Entity
 │    │    │    └── UserEntity.java (DB테이블 매핑용 Entity)
 │    │    ├── mapper     
 │    │         └── UserEntityMapper.java (Entity <-> Domain 매핑)
 │    │
 │    └── interface(Presntation)
 │            ├── controller
 │            │    └── UserController.java (UserController)
 │            └── dto
 │                 ├── RegisterUserRequest.java
 │                 ├── RegisterUserResponse.java
 │                 ├── SendUserEmailRequest.java
 │                 └── SendUserEmailResponse.java

전체적인 흐름

[HTTP Request][UserController] (RequestDTO 수신 → Command 변환 → UseCase 호출)[RegisterUserUseCase] (Command 수신 → Domain 로직 호출 → 저장)[User Entity] (비즈니스 규칙 적용 및 생성)[UserRepository] (Domain Interface → Infra 구현체)[UseCase] (결과 반환)[UserController] (ResponseDTO 변환 후 클라이언트에 응답)

UseCase에서 DB와 흐름!

[UseCase][UserRepository (Domain Interface)][UserRepositoryImpl (Infrastructure Adapter)][UserJPARepository (Spring Data JPA)][UserEntity (DB 테이블 매핑)]

클린 아키텍처의 UseCase와 MVC Service 계층 비교

공통점

  • 위치 및 계층 구조
    • 둘 다 Presentation(Controller, UI)과 Domain 계층 사이의 Application 계층에 위치함.
  • 비즈니스 로직 수행
    • 둘 다 애플리케이션의 비즈니스 로직을 수행하는 책임이 있음.
  • 도메인과 인프라 연계
    • 도메인 모델과 인프라(DB, 외부 서비스) 간의 상호작용을 중재함.
  • 트랜잭션 관리
    • 데이터베이스 작업과 같은 트랜잭션 관리를 주로 담당함.

차이점

  • 책임과 역할의 범위
    • UseCase (클린 아키텍처):
      • 클래스당 오직 하나의 비지니스 유즈케이스 관리!!
      • (예: RegisterUserUseCase, CreateOrderUseCase 등 명확한 행동 하나)
    • Service (MVC):
      • 클래스 하나에 여러 비지니스 유즈 케이스를 묶어서 처리. (우리가 일반적으로 해왔던 방식!!!)
      • (예: UserService 클래스 내에 유저 등록, 수정, 삭제 등 여러 메서드 존재)
  • 클래스 이름 스타일 (그냥 이름 차이)
    • UseCase : 이름이 동사형 (RegistUserUseCase, SubmitOrderUseCase)
    • Service : 클래스 이름이 명사형 (도메인 중심) (ex : UserService, OrderSerivce)
  • 클래스 내 메서드 개수
    • UseCase : 일반적으로 클래스당 하나의 메서드만 존재 (예: execute(), handle())
    • Service : 클래스당 여러 개의 메서드가 존재할 수 있음
  • 단일 책임 원칙(SRP)의 엄격성
    • UseCase : SRP(단일 책임 원칙)를 매우 엄격히 지킴.
    • Service : SRP를 느슨하게 적용, 하나의 클래스가 여러 책임을 가질 수 있음.
  • 클래스 개수와 유지보수성
    • UseCase : 클래스 수가 많아질 수 있으나, 비즈니스 로직이 명확히 분리되어 있어 유지보수 시 부작용(side-effect)이 최소화됨.
    • Service : 클래스 수는 적어지나, 클래스 내 메서드가 많아질 경우 유지보수 시 특정 변경사항이 다른 로직에 영향을 줄 수 있어 부작용 발생 가능성이 높음.

결론!

UseCase는 명확하고 엄격한 책임 구분이 있다. 하지만 만드는데 매우 큰 보수가 드며, 초반 비용이 비싸다.

MVC방식은 간단하고 유연한 관리 방식, SRP 위반 위험이 존재한다.

SRP(Single Responsibility Principle)가 뭔데?

“하나의 클래스는 하나의 책임만 가져야 한다”

왜 SRP가 중요한가?

  • 변경에 강하다
    • 하나의 책임만 가진다면, 변경사항이 생겨도 수정 범위가 작다.
  • 코드 재사용성이 좋아진다
    • 역할이 명확히 분리되면 필요한 부분만 쉽게 재사용할 수 있다.
  • 유지보수가 쉬워진다
    • 버그 수정이나 기능 변경 시, 다른 부분에 영향 주지 않고 수정할 수 있다

0개의 댓글