--- 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 { 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; 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; // Ordering 의 Order } // shipping/orderInfo.ts interface OrderInfoService { getShippingDetails(orderId: string): Promise; // 다른 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]]