경계 컨텍스트
고친 사람 github-actions[bot]
경계 컨텍스트는 큰 시스템을 몇 구역으로 나눕니다. 구역마다 낱말 하나가 한 가지 뜻만 갖게 합니다. 도메인 주도 설계에서 시스템을 나누는 기본 단위입니다. 구역 안의 코드와 대화는 같은 낱말을 같은 뜻으로 씁니다. 구역을 넘나들 때는 상대의 말을 자기 말로 바꿔 읽습니다.
쉽고 빠른 이해
경계 컨텍스트는 「이 선 안에서는 이 낱말이 이 뜻이다」라고 정해 두는 구역입니다. 쇼핑몰의 주문 쪽에서 「상품」은 이름과 판매가입니다. 배송 쪽에서 「상품」은 무게와 부피입니다.
선을 안 그으면 두 뜻이 클래스 하나에 섞입니다. 필드가 끝없이 붙습니다. 배송 쪽이 고친 것이 주문 쪽 코드까지 흔듭니다.
도는 방식은 이렇습니다.
- 업무에서 낱말의 뜻이 바뀌는 곳을 찾아 선을 긋습니다
- 선 안마다 자기만의 「상품」 클래스를 따로 둡니다
- 선을 넘을 때는 상품 번호 같은 최소한의 값만 넘깁니다. 받는 쪽이 그 값을 자기 뜻으로 바꿔 읽습니다
대가도 있습니다. 비슷한 클래스가 여러 벌 생깁니다. 그 사이를 잇는 번역 코드도 따로 짜야 합니다. 업무가 작고 팀이 하나면 선을 여럿 그을 까닭이 적습니다.
상세
이 절은 먼저 한 낱말이 여러 뜻을 가질 때 무엇이 곤란한지 봅니다. 그다음 경계 컨텍스트가 그 뜻을 어떻게 가르는지, 경계를 넘을 때 무엇이 오가는지를 봅니다. 예로는 쇼핑몰의 「상품」 하나를 끝까지 씁니다.
같은 낱말이 가진 여러 뜻
쇼핑몰의 「상품」이라는 낱말을 보겠습니다. 주문을 받는 쪽에서 상품은 이름과 판매가를 가진 것입니다. 배송을 맡는 쪽에서 상품은 무게와 부피를 가진 짐입니다. 정산을 맡는 쪽에서 상품은 판매자와 수수료율이 붙은 거래 대상입니다.
세 쪽 모두 「상품」이라고 부르지만 챙기는 값이 다릅니다. 하는 업무가 다르기 때문입니다. 배송 담당자는 판매가에 관심이 없습니다. 주문 담당자는 부피에 관심이 없습니다.
하나로 모았을 때 생기는 일
이 셋을 Product 클래스 하나에 모으면 세 쪽의 필드가 전부 한 클래스에 붙습니다. 업무가 하나 늘 때마다 필드도 늘어 클래스가 끝없이 커집니다. 모든 일을 혼자 떠맡아 거대해진 이런 클래스를 신 객체라고 부릅니다.
고칠 때도 문제가 생깁니다. 배송 쪽이 부피를 재는 방식을 바꾸면 같은 클래스를 쓰는 주문 쪽 코드도 다시 시험해야 합니다. 한 쪽의 사정이 다른 쪽을 흔듭니다.
낱말의 뜻도 흐려집니다. 코드에서 product.status 를 보고 그것이 판매 상태인지 배송 상태인지 이름만으로는 알 수 없습니다. 회의에서 「상품이 바뀌었다」고 말해도 누구의 상품인지 다시 물어야 합니다.
한 뜻이 통하는 경계
경계 컨텍스트는 한 낱말이 한 가지 뜻으로만 통하는 범위입니다. 그 범위에 선을 긋습니다. 선 안에서는 그 뜻 하나만 씁니다. 주문 컨텍스트 안의 「상품」은 언제나 이름과 판매가를 가진 상품입니다.
영어로는 Bounded Context 라고 합니다. 소리 나는 대로 바운디드 컨텍스트라고도 부릅니다. 이 이름은 도메인 주도 설계에서 나왔습니다. 도메인 주도 설계는 소프트웨어가 다루는 업무를 코드 설계의 중심에 두는 방법입니다. 경계 컨텍스트는 그 방법에서 큰 시스템을 나누는 핵심 패턴입니다.
선 안에는 모델이 하나 있습니다. 모델은 업무의 개념과 규칙을 코드로 옮긴 클래스들을 말합니다. 이것을 도메인 모델이라고 부릅니다. 주문 컨텍스트의 모델에는 주문, 상품, 주문을 확정하는 규칙 같은 것이 들어갑니다.
선 안에서는 낱말도 한 벌입니다. 개발자와 업무 담당자가 회의에서 쓰는 말과 코드의 클래스 이름이 같습니다. 이 한 벌의 낱말을 유비쿼터스 언어라고 합니다. 유비쿼터스(ubiquitous)는 「어디에나 있는」이라는 뜻입니다.
유비쿼터스 언어는 경계 컨텍스트마다 따로 있습니다. 시스템 전체에 하나가 아닙니다. 주문 컨텍스트의 「상품」과 배송 컨텍스트의 「상품」은 두 언어에 각각 들어 있는 다른 낱말입니다.
코드에서 보이는 모습
경계 컨텍스트는 코드에서 흔히 패키지나 모듈 하나로 드러납니다. 아래는 자바로 두 컨텍스트를 두 패키지로 가른 모습입니다. 먼저 주문 컨텍스트입니다.
package shop.order; // 주문 컨텍스트
class Product {
ProductId id;
String name;
Money price;
}
주문 쪽 Product 는 이름과 판매가를 갖습니다. 배송 컨텍스트에도 이름이 같은 클래스가 하나 더 있습니다.
package shop.shipping; // 배송 컨텍스트
class Product {
ProductId id;
Weight weight;
Volume volume;
}
두 Product 는 이름만 같고 서로 다른 클래스입니다. 둘이 함께 가진 것은 상품 번호 id 하나뿐입니다. 배송 쪽이 Volume 을 고쳐도 주문 쪽 클래스는 손대지 않아도 됩니다.
경계를 넘는 방법
컨텍스트가 나뉘어도 업무는 이어집니다. 주문이 확정되면 배송이 시작돼야 합니다. 이 소절은 그때 무엇이 경계를 건너가는지 봅니다.
경계를 건너는 것은 객체가 아니라 최소한의 값입니다. 주문 컨텍스트는 「주문이 확정됐다」는 알림에 상품 번호와 수량만 실어 보냅니다. 주문 쪽 Product 객체를 통째로 넘기지 않습니다.
받는 쪽은 그 값을 자기 모델로 바꿔 읽습니다. 배송 컨텍스트는 상품 번호로 자기 쪽 Product 를 찾아 무게와 부피를 얻습니다. 이렇게 한 컨텍스트의 말을 다른 컨텍스트의 말로 바꾸는 일을 번역이라고 부릅니다.
flowchart TD
subgraph O["주문 컨텍스트"]
OP["상품 · 이름 · 판매가"]
C["주문 확정"]
end
M["건너가는 것 · 주문 확정 알림 · 상품 번호 · 수량"]
subgraph S["배송 컨텍스트"]
T["번역 · 상품 번호로 배송 쪽 상품을 찾는다"]
SP["상품 · 무게 · 부피"]
T --> SP
end
C --> M --> T
그림에서 주문 컨텍스트의 상품은 선 밖으로 나가지 않습니다. 선을 건너는 것은 가운데 상자의 알림과 두 값뿐입니다. 배송 컨텍스트는 번역을 거친 뒤에야 자기 상품을 다룹니다.
알림은 흔히 이벤트로 보냅니다. 이벤트는 이미 일어난 일을 알리는 메시지입니다. 보내는 쪽은 받는 쪽이 처리를 마칠 때까지 기다리지 않습니다. 주문 컨텍스트는 알림을 보내고 곧바로 자기 일을 이어 갑니다.
그래서 배송 쪽이 반영을 마칠 때까지 두 컨텍스트가 잠시 어긋납니다. 주문은 확정됐는데 배송 준비는 아직 없는 순간이 생깁니다. 시간이 지나면 결국 맞춰지는 이 성질을 결과적 일관성이라고 합니다.
번역 코드를 한 곳에 모아 두는 계층을 부패 방지 계층이라고 부릅니다. 상대의 낱말이 자기 모델 안으로 번지는 것을 이 계층에서 막습니다. 상대 컨텍스트가 필드 이름을 바꿔도 이 계층만 고치면 됩니다.
컨텍스트가 여럿이면 누가 누구에게 무엇을 건네는지 정리해 둘 필요가 생깁니다. 컨텍스트들과 그 사이의 관계를 한 장에 그린 것을 컨텍스트 맵이라고 합니다.
경계를 긋는 기준
경계는 기술 계층이 아니라 업무를 따라 긋습니다. 이 소절은 선을 그을 곳을 찾는 신호와, 경계가 팀과 맞물리는 방식을 봅니다.
선을 그을 곳을 알려 주는 신호는 낱말의 뜻이 바뀌는 곳입니다. 주문 담당자와 배송 담당자가 「상품」을 다르게 설명하면 그 사이가 경계 후보입니다. 업무를 맡는 부서가 갈리는 곳에서 뜻도 자주 갈립니다.
기술 계층으로 자르면 어긋납니다. 화면, 업무 로직, 데이터베이스로 나누면 「상품」 하나의 뜻이 세 조각에 흩어집니다. 기능 하나를 고칠 때마다 세 조각을 다 건드리게 됩니다.
경계는 팀과도 맞물립니다. 컨텍스트 하나를 한 팀이 맡으면 그 팀은 자기 언어와 모델을 다른 팀과 협의하지 않고 바꿀 수 있습니다.
시스템 구조가 그것을 만든 조직의 소통 구조를 닮는다는 관찰을 콘웨이의 법칙이라고 부릅니다. 그래서 원하는 경계를 먼저 정하고 팀을 그 경계대로 짜기도 합니다. 시스템이 조직을 닮으니 시스템도 그 경계를 따라갑니다.
하위 도메인과 다른 점
경계 컨텍스트와 자주 헷갈리는 것이 하위 도메인입니다. 하위 도메인은 업무 자체를 나눈 것입니다. 쇼핑몰의 업무는 주문, 배송, 정산 같은 하위 도메인으로 나뉩니다.
하위 도메인은 코드가 없어도 있습니다. 쇼핑몰을 종이와 전화로 운영해도 주문 업무와 배송 업무는 갈립니다. 경계 컨텍스트는 그 업무를 코드로 옮기면서 모델과 낱말에 긋는 선입니다.
둘을 하나씩 맞추는 것이 흔한 목표입니다. 배송 하위 도메인을 배송 컨텍스트 하나가 맡는 식입니다. 오래된 시스템에서는 컨텍스트 하나가 여러 하위 도메인을 품기도 합니다.
마이크로서비스와 모듈
경계 컨텍스트는 모델의 경계이지 배포 단위가 아닙니다. 그래서 같은 경계를 여러 모양으로 코드에 옮길 수 있습니다.
서비스로 옮기면 마이크로서비스가 됩니다. 컨텍스트 하나를 따로 배포하는 서비스 하나로 만듭니다. 서비스마다 자기 데이터베이스를 둡니다. 경계 컨텍스트는 서비스를 어디서 자를지 정하는 기준으로 흔히 쓰입니다.
한 애플리케이션 안의 모듈로 옮기면 모듈러 모놀리스가 됩니다. 배포는 한 덩어리로 합니다. 모듈 사이의 호출은 정해 둔 창구로만 합니다. 모듈 경계가 이미 컨텍스트 경계라서, 나중에 한 모듈을 서비스로 떼어 낼 때 자를 곳을 새로 찾지 않아도 됩니다.
쓰면 나빠지는 것
경계를 그으면 치르는 값이 있습니다. 이 소절은 그 값 셋과, 경계를 여럿 긋지 않아도 되는 경우를 봅니다.
첫째, 비슷한 클래스가 여러 벌 생깁니다. Product 가 컨텍스트마다 하나씩 있어 중복처럼 보입니다. 공통 필드를 한 클래스로 모으고 싶어집니다. 모으는 순간 경계가 다시 무너집니다.
둘째, 번역 코드를 따로 짜고 지켜야 합니다. 상대 컨텍스트가 보내는 값이 바뀌면 번역도 함께 고칩니다.
셋째, 컨텍스트마다 저장소를 따로 두면 두 컨텍스트의 변경을 한 트랜잭션으로 묶을 수 없습니다. 주문 확정과 배송 준비가 잠시 어긋나는 것을 받아들여야 합니다.
경계를 잘못 그으면 되돌리기가 비쌉니다. 한 업무가 두 컨텍스트에 쪼개져 있으면 기능 하나를 고칠 때마다 두 팀이 맞춰야 합니다. 경계를 서비스로까지 굳혔다면 서비스를 합치거나 다시 나눠야 합니다.
업무가 작고 팀이 하나인 시스템에는 경계를 여럿 그을 까닭이 적습니다. 낱말의 뜻이 갈리지 않으면 모델 하나로 충분합니다. 이때는 시스템 전체가 경계 컨텍스트 하나라고 볼 수 있습니다.
관련 항목
경계 컨텍스트가 속하는 설계 방법
도메인 주도 설계 · 전략적 설계 · 전술적 설계 · 소프트웨어 아키텍처
경계 안에서 모델을 이루는 구성 요소
유비쿼터스 언어 · 도메인 모델 · 엔티티 · 값 객체 · 애그리거트 · 도메인 이벤트
컨텍스트 사이의 관계를 정하는 패턴
컨텍스트 맵 · 부패 방지 계층 · 공유 커널 · 오픈 호스트 서비스 · 발행된 언어 · 고객-공급자 · 순응자 · 분리된 길
경계를 긋는 기준이 되는 업무 구분
도메인 · 하위 도메인 · 핵심 도메인 · 지원 하위 도메인 · 일반 하위 도메인 · 이벤트 스토밍
경계 컨텍스트를 옮겨 담는 코드·배포 단위
마이크로서비스 · 모듈러 모놀리스 · 모놀리스 · 모듈화 · 패키지
경계를 넘어 값을 주고받는 수단
이벤트 · 메시지 브로커 · 결과적 일관성 · 트랜잭셔널 아웃박스 · API · 트랜잭션
경계가 잘 그어졌나를 재는 성질
경계를 따라 나뉘는 팀 구성
콘웨이의 법칙 · 팀 경계 · 팀 토폴로지
경계를 잘못 그었을 때 드러나는 문제
다른 이름: 바운디드 컨텍스트 · Bounded Context · 제한된 컨텍스트 · 경계된 컨텍스트