[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
---
|
||||
id: arch-ddd-bounded-context
|
||||
title: DDD — Bounded Context / Ubiquitous Language
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [architecture, ddd, vibe-coding]
|
||||
tech_stack: { language: "TS", applicable_to: ["Backend"] }
|
||||
applied_in: []
|
||||
aliases: [DDD, bounded context, ubiquitous language, context map, anti-corruption layer]
|
||||
---
|
||||
|
||||
# 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)
|
||||
```ts
|
||||
// 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
|
||||
```ts
|
||||
// shared/types.ts — 모든 context 가 같은
|
||||
export type UserId = Brand<string, 'UserId'>;
|
||||
export class Money {
|
||||
constructor(public amount: Decimal, public currency: 'USD' | 'KRW') {}
|
||||
}
|
||||
```
|
||||
|
||||
작게 유지 — 너무 많이 공유 = 결합.
|
||||
|
||||
### Domain event (context 간 통신)
|
||||
```ts
|
||||
// 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
|
||||
```ts
|
||||
// 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 강제
|
||||
```ts
|
||||
// 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 로 변환.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Arch_Hexagonal_Clean]]
|
||||
- [[Arch_Aggregate_Design]]
|
||||
- [[Backend_Event_Sourcing]]
|
||||
Reference in New Issue
Block a user