Files
2nd/10_Wiki/Topics/Coding/Arch_DDD_Bounded_Context.md
T
2026-05-09 21:08:02 +09:00

5.4 KiB
Raw Blame History

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
architecture
ddd
vibe-coding
language applicable_to
TS
Backend
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)

// 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 로 변환.

🔗 관련 문서