---
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]]
