Software Architecture/DDD & Patterns
DDD Patterns - 01. DDD 전략적 설계
한 회사에 "고객"이 다섯 명 있다 — 바운디드 컨텍스트와 보편 언어
한 쇼핑몰 회사에서 "고객"이라는 단어를 쓴다고 하자. 영업팀에게 고객은 "계약을 맺을 잠재 대상"이다. 배송팀에게는 "물건을 받는 수령인"이다. CS팀에게는 "문의를 넣는 사람"이다. 재무팀에게는 "매출을 발생시키는 주체"다. 같은 단어인데 뜻이 다르다. 영업팀이 "고객 수가 1만 명 늘었다"고 말하면 배송팀은 "그럼 배송지도 1만 개 추가됐나?"라고 혼란스러워한다.
이 혼란의 원인은 언어가 아니라 경계가 없는 것이다. 각 부서가 자기 맥락에서 "고객"을 다르게 쓰는데, 그 맥락의 경계를 명시하지 않으니 같은 단어가 충돌한다. 도메인 주도 설계(DDD)가 푸는 문제가 바로 이것이다.
바운디드 컨텍스트 — 언어가 통용되는 국경
Eric Evans는 에서 바운디드 컨텍스트(bounded context)를 "한 모델과 보편 언어가 일관되게 적용되는 경계"라 정의했다. 국가에 비유하면, 한 국경 안에서는 같은 언어와 법이 통용된다. 국경을 넘으면 언어가 바뀌고 법이 바뀐다. "고객"이 영업 컨텍스트에서는 Lead(잠재 고객)이고, 배송 컨텍스트에서는 Recipient(수령인)다. 두 컨텍스트는 서로 다른 국가다 — 같은 단어를 쓰되 다른 뜻으로, 그 차이를 경계로 인정한다.
많은 팀이 이 경계 없이 "통합 모델"을 만들려 한다. 전 회사에 하나의 "고객" 모델을 강제하는 것이다. 그러면 Customer 클래스에 영업 속성(lead_score), 배송 속성(shipping_address), CS 속성(inquiry_history)이 전부 들어간다. 하나의 클래스가 다섯 부서의 요구를 떠안는다 — 변경 이유가 다섯 개인 god object다(foundations에서 본 결합·응집 문제). Evans는 이를 "큰 진흙 덩어리로 가는 지름길"이라 경고했다.
바운디드 컨텍스트로 경계를 긋으면, 같은 "고객"이 컨텍스트마다 자기 맥락에 맞는 모델로 존재한다. 한 단어가 여러 뜻으로 갈라지는 것이다.
flowchart LR
Word(["고객 — 같은 단어, 다른 뜻"])
Word -. 다른 뜻 .-> S
Word -. 다른 뜻 .-> SH
Word -. 다른 뜻 .-> C
subgraph S["영업 컨텍스트"]
S1["Customer = Lead<br/>잠재 고객 · lead_score"]
end
subgraph SH["배송 컨텍스트"]
SH1["Customer = Recipient<br/>수령인 · shipping_address"]
end
subgraph C["CS 컨텍스트"]
C1["Customer = Inquirer<br/>문의자 · inquiry_history"]
end
보편 언어 — 한 국경 안에서만 보편적
보편 언어(ubiquitous language)는 개발자와 도메인 전문가가 함께 쓰는 공유 언어다. 코드의 클래스명·메서드명이 도메인 전문가가 쓰는 단어와 일치해야 한다. "주문을 취소한다"가 비즈니스 용어면, 코드에도 cancelOrder()가 있어야지 updateStatus(4)가 있으면 안 된다. 코드와 비즈니스 사이의 번역 오버헤드가 사라진다.
여기서 자주 오해하는 점이 있다. 보편 언어는 "전 회사에 하나"가 아니다. 한 바운디드 컨텍스트 안에서만 보편적이다. 영업 컨텍스트의 보편 언어와 배송 컨텍스트의 보편 언어는 다르다. 각 국가가 자기 언어를 갖듯, 각 컨텍스트가 자기 보편 언어를 갖는다. Evans와 Vernon() 모두 이 점을 강조한다 — 보편 언어의 범위는 바운디드 컨텍스트의 경계와 일치한다.
컨텍스트 맵 — 국가 간 관계도
바운디드 컨텍스트를 나누면 다음 질문이 온다 — 컨텍스트끼리 어떻게 소통하는가. 주문 컨텍스트에서 발생한 주문을 배송 컨텍스트가 알아야 한다. 이 관계를 그린 것이 컨텍스트 맵(context map)이다.
Evans가 정의한 대표적 관계 패턴:
| 관계 | 의미 | 비유 |
|---|---|---|
| 공개 호스트 서비스(OHS) | 표준 API로 외부에 서비스 노출 | 대사관 — 공식 창구 |
| 발행-구독(P/L) | 이벤트를 발행하고 구독자가 받음 | 공식 발표 — 누구나 구독 |
| 순응자(Conformist) | 상류 컨텍스트의 모델을 그대로 따름 | 속국 — 강대국의 언어를 씀 |
| 고객-공급자(C/S) | 하류가 상류의 요구에 맞춤 | 하청 — 주문자 요구에 맞춰 생산 |
| 공유 커널(Shared Kernel) | 두 컨텍스트가 일부 모델을 공유 | 공동 통치 구역 — 양쪽이 함께 관리 |
실제 쇼핑몰에서 주문·결제·배송 컨텍스트가 어떻게 연결되는지 예시로 보면, 각 관계 패턴이 구체적으로 드러난다. 주문은 결제를 공개 API(OHS)로 호출하고, 동시에 "주문 접수" 이벤트(P/L)를 발행해 배송이 구독한다. 결제 완료도 이벤트로 배송에 전달된다.
flowchart LR
Order["주문 컨텍스트"]
Payment["결제 컨텍스트"]
Shipping["배송 컨텍스트"]
Order -->|"OHS — 공개 API (동기 호출)"| Payment
Order -.->|"P/L — 주문 접수 이벤트"| Shipping
Payment -.->|"P/L — 결제 완료 이벤트"| Shipping
이 관계를 명시하지 않으면, 컨텍스트 간 경계가 모호해져 다시 진흙 덩어리로 돌아간다. 컨텍스트 맵은 두 나라가 외교 관계를 명시하는 조약과 같다 — "우리는 이 경계를 인정하고, 이 방식으로 소통한다."
경계는 어디에 긋는가
바운디드 컨텍스트의 경계를 긋는 기준은 조직 구조와 언어에서 온다. 같은 단어를 다르게 쓰는 곳이 나뉘고, 같은 팀이 담당하는 영역이 하나로 묶인다. Conway's Law(06-foundations)와 만나는 지점이다 — 조직의 의사소통 경계가 자연스럽게 컨텍스트 경계가 된다. 영업팀과 배송팀이 다른 회의를 하고 다른 도구를 쓴다면, 그들은 아마 다른 바운디드 컨텍스트에 있다.
경계를 너무 크게 그리면 한 컨텍스트가 감당하기 벅차고(인지 부하 초과), 너무 작게 그리면 컨텍스트 간 소통 비용이 폭발한다. 균형이 필요하고, 그 균형은 팀이 실제로 소통하는 경로와 도메인 전문가가 쓰는 언어에서 나온다 — 다이어그램이 아니라 대화에서.
설계 사례 — 항공 예약 시스템의 컨텍스트 분할
항공사 시스템을 하나로 모델링한다고 하자. "항공편"이라는 단어가 예약팀에게는 "좌석 등급별 잔여 옵션"이고, 탑승관리팀에게는 "물리적 좌석 배정 현황"이며, 결제팀에게는 "구간별 요금 항목"이고, 마일리지팀에게는 "적립 기준이 되는 비행 거리"다. 이걸 하나의 Flight 클래스로 통합하면, 예약 속성(remaining_seats_by_class), 탑승 속성(seat_map), 결제 속성(fare_basis_code), 마일리지 속성(mileage_distance)이 전부 들어간다. 변경 이유가 네 개인 god object가 된다.
바운디드 컨텍스트로 나누면 각 팀이 자기 맥락에 맞는 "항공편"을 갖는다:
flowchart LR
subgraph Booking["예약 컨텍스트"]
BF["Flight = 좌석 옵션<br/>remaining_seats_by_class<br/>booking_class"]
end
subgraph SeatMap["탑승 관리 컨텍스트"]
SF["Flight = 물리적 좌석<br/>seat_map<br/>assigned_seat"]
end
subgraph Pricing["결제 컨텍스트"]
PF["Flight = 요금 항목<br/>fare_basis_code<br/>base_fare"]
end
subgraph Mileage["마일리지 컨텍스트"]
MF["Flight = 적립 기준<br/>mileage_distance<br/>elite_qualifying"]
end
Booking -.->|"예약 완료 이벤트"| SeatMap
Booking -.->|"예약 완료 이벤트"| Pricing
Pricing -.->|"결제 완료 이벤트"| Mileage
각 컨텍스트의 Flight는 같은 비행(KE123)을 가리키되(flight_id로 연결), 자기 속성만 갖는다. 예약 컨텍스트의 Flight는 좌석 옵션만 알고, 물리적 좌석 배정은 탑승 관리 컨텍스트가 담당한다. 결제 컨텍스트는 요금 계산에 필요한 항목만 본다. 마일리지는 적립 거리만 안다. 한 모델이 네 팀의 요구를 떠안는 대신, 네 모델이 각자 집중한다.
컨텍스트 간 통합은 이벤트로 이어진다. 예약이 완료되면 "예약 완료됨(ReservationConfirmed)" 이벤트가 발행되고, 탑승 관리와 결제가 그것을 구독한다. 결제가 완료되면 "결제 완료됨(PaymentCompleted)"이 마일리지로 간다. 각 컨텍스트가 자기 트랜잭션에서 자기 일을 하고, 이벤트로 느슨하게 연결된다. 이것이 전략적 설계의 실천이다 — 경계를 긋고, 각 경계 안에서 보편 언어를 세우고, 경계 사이를 합의된 방식으로 이어준다.
한 회사에 "고객"이 다섯 명 있어도 괜찮다
바운디드 컨텍스트로 경계를 긋고 나면, "고객"이 다섯 명 있어도 괜찮다. 영업 컨텍스트의 Customer(Lead)와 배송 컨텍스트의 Customer(Recipient)는 서로 다른 국가의 다른 단어다. 번역은 경계에서만 일어나고, 각 컨텍스트 안에서는 언어가 일관된다. 통합 모델의 god object 대신, 각 컨텍스트가 자기 모델에 집중한다 — 변경이 국소에 머물고, 팀이 독립적으로 움직인다. DDD의 출발은 패턴이 아니라 이 경계를 인정하는 데 있다.
참고
- Evans, Eric — (Addison-Wesley, 2003), Ch.2·3·14 (바운디드 컨텍스트, 보편 언어, 컨텍스트 맵) — 접근 2026-07-15
- Vernon, Vaughn — (Addison-Wesley, 2013), Ch.2 (바운디드 컨텍스트 실천) — 접근 2026-07-15
'Software Architecture > DDD & Patterns' 카테고리의 다른 글
| DDD Patterns - 06. 헥사고날 실천 (0) | 2026.07.21 |
|---|---|
| DDD Patterns - 05. 빈약한 도메인 모델 (0) | 2026.07.21 |
| DDD Patterns - 04. 앤터프라이즈 패턴 (0) | 2026.07.21 |
| DDD Patterns - 03. 애그리거트 설계 (0) | 2026.07.20 |
| DDD Patterns - 02. DDD 전술적 설계 (0) | 2026.07.20 |