Skip to content

[volume-3] 도메인 주도 설계 및 구현#92

Merged
APapeIsName merged 9 commits intoLoopers-dev-lab:APapeIsNamefrom
APapeIsName:volume-3
Mar 2, 2026
Merged

[volume-3] 도메인 주도 설계 및 구현#92
APapeIsName merged 9 commits intoLoopers-dev-lab:APapeIsNamefrom
APapeIsName:volume-3

Conversation

@APapeIsName
Copy link

@APapeIsName APapeIsName commented Feb 25, 2026

📌 Summary

  • 배경: 감성 이커머스의 전체적인 레이어드 아키텍처 디자인과 도메인 모델링을 진행한 뒤, 요구사항에 맞는 기능 구현을 진행했습니다.
  • 목표: 제시된 요구사항을 바탕으로, 알맞는 레이어드 아키텍처를 디자인하고, 도메인 모델링을 진행하였고, 요구사항에 맞는 기능을 구현했습니다.
  • 결과: 아키텍처와 도메인 모델을 디자인하기 위해 스스로 개념에 대해 정의내리고 정답이 아닌 해답이 되는 설계를 진행할 수 있었고, 구현을 하는 와중에도 세부적인 설계를 다듬는 과정을 통해 설계를 고도화시킬 수 있었습니다.

🧭 Context & Decision

1. Domain? Application?

고민

  • Domain 레이어와 Applicaiton 사이를 나누는 저만의 기준점이 존재하지 않아 어떻게 나누고 어디에 로직을 둬야 할지 고민했습니다.

결론

  • Domain 레이어는 기능적인 요구사항을 로직으로 표현해내는 레이어라고 판단해 공책 게임에 비유하여, 공책 게임으로 만들어도 어색하지 않은가? 라는 판단 기준점을 세웠습니다.
  • 그 이외의 로직은 전부 Applicaiton 레이어나 Presentation 레이어에 위임하기로 결정하고 다음과 같은 흐름도를 그렸습니다.
flowchart TD
    START["새 로직 추가"] --> Q1{"이 규칙은 기술 구현과 무관하게 항상 성립하는가?"}

    Q1 -->|"Yes — 논리적"| Q2{"단일 엔티티 상태/불변식인가?"}
    Q1 -->|"No — 물리적"| Q5{"BC 경계를 넘는 조율인가?"}

    Q2 -->|"Yes"| A1["Entity 또는 VO"]
    Q2 -->|"No"| Q3{"같은 BC 내 cross-aggregate 규칙인가?"}

    Q3 -->|"Yes"| A2["Domain Service"]
    Q3 -->|"No"| Q5

    Q5 -->|"Yes"| A3["Application Service"]
    Q5 -->|"No"| Q6{"물리적 기술 관심사인가? (트랜잭션, 락, 데드락 방지)"}

    Q6 -->|"Yes"| A3
    Q6 -->|"No"| Q7{"Application Service 간 순환 참조인가?"}

    Q7 -->|"Yes"| A4["Facade"]
    Q7 -->|"No"| A3

    A1 --> DOMAIN["Domain Layer"]
    A2 --> DOMAIN
    A3 --> APP["Application Layer"]
    A4 --> APP

    style DOMAIN fill:#e8f5e9
    style APP fill:#e3f2fd
Loading

2. VO 선정 기준

고민

  • VO 를 사용하면 객체의 행위를 캡슐화시킬 수 있고, 이는 변화하는 요구사항에 맞추기 쉬워지는 코드를 만들 수 있습니다. 그렇다면 모든 필드를 VO로 만들어야 하는 건지? 아니면 일부만 VO로 만들어야 하는지? 그 기준은 무엇인지 고민했습니다.

결론

  • Alen 멘토님께서는 자주 사용하는 것이 VO로 쓰인다고 말씀하셨지만, 저는 좀 더 나아가 충분히 Test 가능성 있는, 즉 지켜야 할 규칙이 있는지 를 VO 생성 기준점으로 봤습니다.
  • 이에 따라 값을 검증하는 로직이 생기면 따로 VO 테스트 코드를 작성하여 해당 값 자체 검증에 더욱 집중할 수 있었습니다.

🏗️ Design Overview

graph LR
    subgraph Presentation["Presentation Layer"]
        direction TB
        P_CTRL["Controller"]
        P_DTO["API DTO
        (Request / Response)"]
        P_RES["ApiResponse
        ApiControllerAdvice"]
    end

    subgraph Application["Application Layer"]
        direction TB
        A_SVC["Service"]
        A_DTO["Command / Info DTO"]
    end

    subgraph Domain["Domain Layer"]
        direction TB
        D_ENT["Entity
        (BaseEntity → BaseTimeEntity
        → SoftDeletableEntity)"]
        D_VO["VO
        (LoginId, Password, Name, Money ...)"]
        D_PORT["Repository / Port
        (interface)"]
        D_DS["Domain Service
        (BrandDeleteService,
        ActiveProductService)"]
    end

    subgraph Infrastructure["Infrastructure Layer"]
        direction TB
        I_JPA["jpa/
        RepositoryImpl
        JpaRepository"]
        I_SEC["security/
        BCryptPasswordEncryptor"]
        I_ETC["redis/ kafka/"]
    end

    subgraph Supports["Supports (Cross-cutting)"]
        direction TB
        S_ERR["error/
        ErrorType, CoreException"]
        S_ETC["jackson/ logging/
        monitoring/"]
    end

    Presentation -->|depends on| Application
    Application -->|depends on| Domain
    Infrastructure -->|"implements
    (Repository, PasswordEncryptor)"| Domain
    Supports -.->|"used by all layers"| Domain
Loading

Domain Layer

기술 구현과 상관 없는 기능적인 요구사항(공책 게임으로 구현할 수 있는 요구사항) 을 구현하는 레이어

Entity, VO

JPA @Entity, @Embeddable 를 사용: 순수 도메인 엔티티보다 현재 프로젝트에서는 JPA 엔티티로 구현했을 때 단점이 더 작다 고 판단하였기 때문.

순수한 도메인 엔티티로 사용했을 때 단점

  • JPA만의 편의성을 잃어버림: JPA의 영속성 컨텍스트 사용 불가해 직접 구현해야 함
  • 코드 복잡도 상승: 관리해야 하는 레이어 및 코드 증가

JPA 엔티티로 사용했을 때 단점

  • 비즈니스 로직 가독성 저하: 엔티티의 기능 구현 시 JPA 고려해야 함
  • 테스트 코드 작성 시 영속성 컨텍스트 고려: 엔티티에 JPA가 강하게 의존
  • 프레임워크 변경 시 엔티티 코드도 수정: 도메인 레이어에 JPA가 강하게 의존

위 단점들을 비교해봤을 때 현재 프로젝트인 감성 이커머스에서는 JPA 엔티티로 구현했을 때의 단점이 순수 도메인 엔티티로 구현했을 때보다 작다 고 판단함. 또한, Repository에 DIP를 적용했음에도, Entity 까지 따로 관리했을 때 비용이 많이 들지만 얻을 수 있는 이점은 적다고 생각하였고, 만약 예상치 못한 이유로 순수 도메인으로 변경해야 한다면 AI 로 빠르게 변경할 수 있다고 판단하여 JPA Entity 로 구현하게 됨.

Domain Service

Domain Service 나만의 정의: Domain Service는 단일 엔티티에서 구현 불가능하지만, 하나의 BC 내에서 책임을 맡는 기능을 구현

  • Service 의 의미는 Input 과 Output 을 가지며 상태를 가지지 않는다는 걸 기반

    Service == 함수의 객체화 라고 정의

  • Domain Service도 하나의 객체라는 판단 하에 SRP 를 적용

    단일 엔티티에서 구현 불가능하지만, 하나의 BC 내에서 책임을 맡는 기능이라면 Domain Service에 해당

Repository

  • 도메인 레이어는 알 수 없는 저장소랑 상호작용(저장, 조회, 수정, 삭제)하는 용도.
  • 공책 게임으로 치면 연필로 쓰고, 지우개로 지우고 하며 정보를 작성하는 것이라고 판단→ Domain 레이어에서 호출하는 것을 정당하다고 생각함. 그것 또한 도메인 레이어가 담당하는 것이 아닐까?

Application Layer

**비기능적인 요구사항(트랜잭션 등)**을 구현하거나, 도메인 레이어에서 해결하지 못하는 여러 BC들을 조합하여 기능을 구현하는 레이어.

Application Service

여러 도메인 계층의 BC나 외부 기술 등을 응용하여 비즈니스 로직으로 조합시켜 만드는, 인풋과 아웃풋이 분명한 함수 같은 객체

Facade (현재 프로젝트 내에는 없음)

Service 들을 가져와서 조합해야 하는데 Service 사이의 순환 참조가 발생할 때, 이를 막기 위해 만들어진 패턴

Presentation Layer (Interfaces Layer)

클래스를 매핑하여 사용자나 다른 서버 등 인터페이스로 값을 표현하는 레이어.

API DTO

(컨트롤러 기준) Application 의 Command / Query DTO 로부터 API 요청값 및 응답값을 매핑해 레이어 사이 또는 다른 개체(클라이언트, 서버 등)와 통신함.

ApiControllerAdvice

Domain 레이어의 ErrorType을 HttpStatus로 매핑

Infrastructure Layer

Domain Layer 에서 DB 와 연결하여 도메인 객체를 가져다 쓸 수 있도록 하기 위해 기존의 의존 방향을 뒤집어, Domain Layer 의 변경을 최소화하기 위해 만들어진 레이어.

RepositoryImpl

Domain 레이어의 Repository 를 구현해 DIP 를 만족하도록 구현하는 구현체. 실제 기능은 JpaRepository 에 위임

JpaRepository

Spring Data Jpa 로 만들어둔 DB에서 값을 가져다 쓸 수 있는 Repository 객체

Support Layer

효율적인 코드 작성을 위해 유일하게 모든 레이어가 의존할 수 있는 레이어. 공통으로 사용하는 예외나 유틸성 클래스를 담음.

CoreException(Custom Exception) & ErrorType

  • AOP 를 활용해 예외가 발생하면 이에 맞는 HTTP 코드를 내려주기 위해 설계됨.
  • ErrorType에 각 상태 코드에 맞는 값들이 작성돼 있고, 이 값들을 Presentation 레이어의 ApiControllerAdvice 에서 매핑함

- 회원가입 기능 (검증 + 암호화 + 저장)
- 내 정보 조회 기능 (인증 + 마스킹)
- 비밀번호 변경 기능 (인증 + 검증 + 변경)
- 컨트롤러 구현 (REST API 엔드포인트)
- 단위 테스트, 통합 테스트, E2E 테스트 작성
- 예외 처리 (ErrorType + CoreException)
- 01. 요구사항 문서
- 02. 시퀀스 다이어그램
- 03. 클래스 다이어그램
- 04. ERD
- 구조 리팩토링 계획서
- 아키텍처 논의 기록
@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@APapeIsName APapeIsName changed the title Volume 3 [volume-3] Feb 26, 2026
# Conflicts:
#	.gitignore
#	docs/analysis/class-diagram-analysis.md
#	docs/analysis/erd-analysis.md
#	docs/analysis/prompt-design-process-analysis.md
#	docs/analysis/requirements-gathering-analysis.md
#	docs/analysis/sequence-diagram-analysis.md
#	docs/design/02-sequence-diagrams.md
#	docs/design/03-class-diagram.md
#	docs/design/04-erd.md
#	docs/design/base/class-diagram-erd.md
#	docs/design/base/domain-definition-v2.md
#	docs/design/base/member-class-diagram.md
#	docs/design/base/requirements-input.md
#	docs/design/base/sequence-diagrams.md
#	docs/design/base/ubiquitous-language.md
#	docs/design/base/user-story.md
@APapeIsName APapeIsName changed the title [volume-3] [volume-3] 도메인 주도 설계 및 구현 Feb 26, 2026
@APapeIsName APapeIsName merged commit f40f52e into Loopers-dev-lab:APapeIsName Mar 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant