5.4 KiB
5.4 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| arch-ddd-bounded-context | DDD — Bounded Context / Ubiquitous Language | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
DDD — Bounded Context
큰 도메인을 나눔 = Bounded Context. 같은 단어 (Customer) 가 context 마다 다른 의미. Context map 으로 관계 명시. ACL = 외부 모델 변환.
📖 핵심 개념
- Bounded Context: 일관 model + 용어가 통하는 경계.
- Ubiquitous Language: business + dev 같은 용어.
- Context Map: 여러 context 의 관계 (shared kernel / customer-supplier / ACL).
- Anti-Corruption Layer: 다른 context 의 모델을 우리 모델로 변환.
💻 코드 패턴
Context 분리 예
E-commerce 큰 도메인:
Bounded Contexts:
- Catalog (Product, Category, SKU)
- Ordering (Order, Customer, Cart)
- Shipping (Shipment, Address, Carrier)
- Billing (Invoice, Payment, RefundPolicy)
- Customer (Account, Profile, Auth)
같은 "Customer":
- Ordering 의 Customer = 주문 history + cart
- Billing 의 Customer = 결제 method + tax id
- Shipping 의 Customer = 배송 주소
→ 같은 개념 X. 별 model.
폴더 / module 분리
src/
catalog/
domain/
application/
adapters/
ordering/
domain/
application/
adapters/
shipping/
...
→ Hexagonal × N (each context).
Context map 종류
1. Partnership: 두 팀 같이 진화.
2. Shared Kernel: 작은 공유 model (e.g. Money, UserId type).
3. Customer-Supplier: A가 B의 요구 따름.
4. Conformist: A가 B에 일방 따름.
5. Anti-Corruption Layer: A가 B를 자기 model 로 변환.
6. Open Host Service: B가 표준 API 노출.
7. Published Language: 표준 schema (Avro / JSON Schema).
Anti-Corruption Layer (ACL)
// ordering/adapters/legacyShippingAdapter.ts
import * as Legacy from 'legacy-shipping-sdk';
export class LegacyShippingAdapter implements ShippingService {
// Legacy 의 이상한 model → ordering domain 의 깔끔한 interface
async ship(order: Order): Promise<TrackingNumber> {
const legacyReq = {
shipperCode: 'X1',
itm: order.items.map(i => ({ p_id: i.productId, qt: i.qty })),
addr: this.toLegacyAddr(order.shippingAddress),
};
const r = await Legacy.client.create(legacyReq);
return new TrackingNumber(r.tn); // ordering 의 type
}
private toLegacyAddr(a: Address) { ... }
}
→ 이 mapper 만 수정하면 legacy 변경 차단.
Shared Kernel
// shared/types.ts — 모든 context 가 같은
export type UserId = Brand<string, 'UserId'>;
export class Money {
constructor(public amount: Decimal, public currency: 'USD' | 'KRW') {}
}
작게 유지 — 너무 많이 공유 = 결합.
Domain event (context 간 통신)
// ordering 이 OrderPlaced 발행
export class OrderPlaced {
readonly _tag = 'OrderPlaced';
constructor(public readonly orderId: string, public readonly userId: string, public readonly total: Money) {}
}
// shipping 이 listen
on(OrderPlaced, async (ev) => {
await shippingApp.scheduleShipment(ev.orderId);
});
→ Pub/sub 또는 outbox.
Repository 별 context
// ordering/orderRepository.ts
interface OrderRepository {
find(id: OrderId): Promise<Order | null>; // Ordering 의 Order
}
// shipping/orderInfo.ts
interface OrderInfoService {
getShippingDetails(orderId: string): Promise<ShippingInfo>; // 다른 view
}
→ Shipping 의 OrderInfo 가 ordering DB 의 일부 필드만.
Strategic vs Tactical DDD
Strategic: 큰 그림 (context, language, map)
Tactical: 안 ddd (entity, value object, aggregate, event, service)
작은 팀 = Strategic 만으로도 큰 가치.
Module boundary 강제
// turborepo / pnpm workspaces
packages/
catalog/
ordering/
shipping/
shared/
// 의존: ordering → shared OK, ordering → catalog 직접 X
// 통신은 event 또는 published API
Context 발견 (Event Storming)
Workshop: 도메인 전문가 + dev
- 모든 domain event 적기 (post-it)
- Group → 시간 순
- Aggregate 식별
- Context 경계 그리기
→ 보통 1-2 일 워크숍.
🤔 의사결정 기준
| 도메인 크기 | 추천 |
|---|---|
| 단순 / CRUD | DDD overkill |
| 중간 / 비즈니스 복잡 | Tactical (entity, aggregate, event) |
| 큰 / 여러 팀 | Strategic + Tactical |
| 마이크로서비스 design | Bounded context = service 경계 |
| Legacy 통합 | ACL 필수 |
| 도메인 전문가 가까이 | Ubiquitous language |
❌ 안티패턴
- God context (모든 도메인 한 곳): 의도 안 보임. 분리.
- Anemic domain model: 서비스만, entity 는 데이터. 행위 entity 안.
- Strategic skip + Tactical 만: 결국 god.
- Shared kernel 거대: 사실상 monolith.
- Repository 가 generic CRUD: aggregate 별 의도 잃음.
- Event 너무 잘게 (각 setter): noise. 비즈니스 의미만.
- Model 직접 노출 (HTTP body): 도메인 변경 = API 변경.
🤖 LLM 활용 힌트
- 도메인 분리 → folder / module = bounded context.
- Domain event 가 context 간 통신.
- 외부 시스템 = ACL 로 변환.