도메인 모델 문서 작성 가이드
Template
문서 순서
도메인 모델은 다음 순서로 작성:
- Domain Events (내부/외부 이벤트)
- Entities (엔티티 속성)
- Enums (열거형)
- Value Objects (값 객체)
- Aggregates (집합체 상세)
- Error Codes (에러 코드)
Domain Events 작성
Internal Events vs External Events:
Internal Events (내부 이벤트):
- 해당 도메인/컨텍스트 내에서만 발생하고 처리
- 같은 Context 내 다른 Aggregate나 Service가 구독
- 예: CallCompleted → CallBannerService (같은 Support Context)
External Events (외부 이벤트):
- Outbound: 다른 Context로 발행하는 이벤트
- Inbound: 다른 Context에서 받아서 처리하는 이벤트
- 예: CallCompletedNotification → Notification Context (다른 Context)
작성 형식:
**EventName**
- Payload: `field1`, `field2`, `field3`
- Trigger: 발생 조건
- Publisher: 발행 주체
- Subscribers: 구독자들
작성 팁:
- Payload는 필드명만 나열 (타입은 생략 가능)
- Trigger는 언제 발생하는지 간단히
- Publisher는 Aggregate명
- Subscribers는 쉼표로 구분
Entities 작성
작성 형식:
**EntityName** (Aggregate Root 여부 표시)
- `propertyName`: 설명 (타입)
속성 표현:
- 개념 수준으로 작성 (구현 상세 X)
- 타입은 괄호 안에 간단히
- Enum이나 VO 타입은 이름으로 참조
좋은 예:
**AppUserOutboundCallPlan** (Aggregate Root)
- `id`: 콜 계획 고유 식별자
- `userId`: 대상 회원 ID
- `status`: 콜 상태 (CallStatus)
나쁜 예:
**AppUserOutboundCallPlan**
- `id`: UUID String ❌ 너무 구현 상세
- `user_id`: Long ❌ 컬럼명 스타일
- `status_code`: Integer ❌ 의미 불명확
Enums 작성
언제 사용하나:
- 정해진 값 집합 (상태, 타입, 카테고리 등)
- 값의 의미가 명확하고 제한적일 때
- 예: CallStatus, CompletionReason, NotificationType
작성 형식:
**EnumName** - 설명
- `VALUE1`: 설명
- `VALUE2`: 설명
작성 예시:
**CallStatus** - 콜의 상태
- `Draft`: 초안 (생성 직후)
- `Active`: 활성 (처리 중)
- `Completed`: 완료
- `Cancelled`: 취소
Value Objects 작성
Entity vs Value Object 구분:
| 구분 | Entity | Value Object |
|---|---|---|
| 식별 | ID로 식별 | 속성 값으로 식별 |
| 변경 | 상태 변경 가능 | 불변 (교체만 가능) |
| 예시 | Order, User | Money, Address, PhoneNumber |
작성 형식:
**VOName**
- `property1`: 설명 (타입)
- `property2`: 설명 (타입)
- 동등성: 비교 기준
동등성 기준:
- 어떤 필드들이 같으면 같은 객체인지 명시
- 예: "content 값이 동일하면 같은 객체"
- 예: "number와 countryCode가 모두 동일하면 같은 객체"
작성 예시:
**PhoneNumber**
- `number`: 전화번호 (String, 숫자만)
- `countryCode`: 국가 코드 (String)
- 동등성: number와 countryCode가 모두 동일하면 같은 객체
Aggregates 작성
구현 중심 접근:
바운디드 컨텍스트 문서에서 이미 Aggregate의 책임과 개념을 정리했으므로, 도메인 모델에서는 실제 구현에 필요한 메서드와 Repository를 정리합니다.
작성 구조:
**AggregateName**
**Methods:**
- 메서드 목록 (상태 변경)
- 메서드 목록 (조회)
**Repository:**
- 저장소 메서드
**Events Published:**
- 발행하는 이벤트 목록
Methods:
-
Aggregate의 상태를 변경하는 메서드
-
메서드명(파라미터) 형태로 간단히
-
예:
completeCall(completedBy, reason) -
비즈니스 로직의 진입점
-
Aggregate의 상태를 조회하는 메서드
-
상태 변경 없이 정보만 반환
-
예:
getCallStatus(),isCompletable()
Repository:
- Aggregate를 저장/조회하는 메서드
- CRUD 기본 + 도메인 특화 조회
- 예:
findByUserId(),findActiveCallsByUser()
Events Published:
- 이 Aggregate가 발행하는 이벤트
- Domain Events 섹션과 연결
- 예:
CallCompleted,MemoAdded
작성 예시:
**AppUserOutboundCallPlan**
**Methods:**
- `createCallPlan(userId, ...)`
- `completeCall(completedBy, reason)`
- `cancelCall(cancelledBy, reason)`
- `getCallStatus()`
- `isCompletable()`
**Repository:**
- `findById(id)`
- `findByUserId(userId)`
- `save(callPlan)`
**Events Published:**
- `CallCompleted`
- `CallCancelled`
작성 팁:
- Methods는 동사로 시작 (create, complete, cancel, add...)
- Query Methods는 get/is/has로 시작
- Repository는 find/save/delete
- 구현 상세(리턴 타입, 예외)는 생략
Domain Services 작성 (선택)
언제 Domain Service가 필요한가:
Domain Service는 다음 경우에만 추가:
- 여러 Aggregate에 걸친 로직
- 예: 콜 할당 로직 (사용자 정보 + 콜 계획 확인)
- 외부 시스템과의 도메인 로직
- 예: 외부 인증 서비스 호출
- 복잡한 계산 로직
- 예: 가격 계산 (여러 정책 적용)
작성 형식:
## 6. Domain Services (선택)
**ServiceName**
- `methodName(params)`: 설명 [관련 Aggregate들]
작성 예시:
## 6. Domain Services
**CallAssignmentService**
- `assignCallToOperator(callId, operatorId)`: 콜을 운영자에게 할당 [CallPlan, Operator]
- `findAvailableOperator()`: 사용 가능한 운영자 찾기 [Operator]
**PriceCalculationService**
- `calculateDiscount(userId, amount)`: 사용자별 할인율 계산 [User, Order]
- `applyPromotionRules(orderId)`: 프로모션 규칙 적용 [Order, Promotion]
Aggregate 표시:
- 대괄호
[...]안에 관련 Aggregate 나열 - 여러 Aggregate일 경우 쉼표로 구분
- 이 메서드가 어떤 Aggregate들과 협업하는지 명확히
Domain Service vs Aggregate 판단:
- 하나의 Aggregate에만 속한다 → Aggregate 메서드로
- 여러 Aggregate 조율한다 → Domain Service로
- 트랜잭션 관리가 필요하다 → Application Service로 (별도 문서)
주의사항:
- Domain Service는 최소한으로 유지
- 가능하면 Aggregate 메서드로 해결
- 남용하면 빈약한 도메인 모델(Anemic Domain Model)이 됨
Error Codes 작성
코드 체계:
- 4자리 숫자 (도메인 prefix 없음)
- 4XXX: 클라이언트 오류
- 5XXX: 서버 오류
작성 형식:
**4xx - Client Errors**
- `4001` ERROR_NAME (HTTP Status): 설명
**5xx - Server Errors**
- `5001` ERROR_NAME (HTTP Status): 설명
HTTP Status 매핑:
- 400: 잘못된 요청 (유효성 실패)
- 403: 권한 없음
- 404: 리소스 없음
- 409: 충돌 (이미 처리됨, 상태 불일치)
- 500: 내부 서버 오류
작성 예시:
**4xx - Client Errors**
- `4001` CALL_ALREADY_COMPLETED (409): 이미 완료된 콜
- `4002` INVALID_CALL_STATUS (400): 유효하지 않은 상태 전이
- `4003` UNAUTHORIZED_ACTION (403): 권한 없음
**5xx - Server Errors**
- `5001` INTERNAL_ERROR (500): 내부 서버 오류
작성 순서 및 흐름
도메인 모델은 다음 흐름으로 작성:
- 이벤트 스토밍에서 도출 → Domain Events
- Aggregate 매핑에서 도출 → Entities, Aggregates
- 속성 분석 → Enums, Value Objects
- 비즈니스 규칙 연계 → Error Codes
작성 팁:
- 한 번에 완성하려 하지 말고 반복적으로 정제
- Aggregate부터 작성하면 Entity/VO 구조가 명확해짐
- Error Codes는 비즈니스 규칙과 1:1 매핑
- 500: 내부 서버 오류
Support 도메인 예시
요구사항 → 이벤트 스토밍 흐름 예시
요구사항:
SPT-FR-BE-006: 운영자가 콜을 완료 처리할 수 있어야 한다.
이벤트 스토밍:
- Command:
CompleteOutboundCall - Actor:
OperationUser - Event:
OutboundCallCompleted - Aggregate:
AppUserOutboundCallPlan
이벤트 스토밍 → 비즈니스 규칙 흐름 예시
정책:
OutboundCallCompletionRequested → 이미 완료된 콜이면 거부
비즈니스 규칙:
| Rule Name | Aggregate | Enforcement | Violation Response | Related Req |
|---|---|---|---|---|
| 완료된 콜 재처리 방지 | AppUserOutboundCallPlan | Command 실행 시 | 거부 이벤트 | SPT-FR-BE-008 |
비즈니스 규칙 → 에러 코드 흐름 예시
비즈니스 규칙:
완료된 콜 재처리 방지
에러 코드:
| Error Code | Error Name | HTTP Status | Description | Trigger Condition | Related Rule |
|---|---|---|---|---|---|
| SPT-4001 | CALL_ALREADY_COMPLETED | 409 | 이미 완료된 콜 | 완료된 콜에 완료 요청 시 | 완료된 콜 재처리 방지 |