Where Do You Draw the Boundary
한국어 원문으로 표시합니다.
한 줄 요약
경계는 명사가 아니라 언어에서 나온다. 같은 단어를 두 팀이 다른 뜻으로 쓰고 있다면 거기가 경계다.
왜 이게 필요했나
"주문 서비스, 상품 서비스, 회원 서비스로 나눕시다." 회의실에서 가장 흔히 나오는 문장이고, 가장 자주 실패하는 설계입니다. 이 방식은 데이터베이스 테이블 이름을 그대로 서비스 이름으로 승격시킨 것에 불과합니다. 결과는 예측 가능합니다. 주문 하나를 만들려면 상품 서비스와 회원 서비스를 각각 호출해야 하고, 세 서비스가 한 화면을 위해 항상 함께 움직입니다. 배포 독립성은 생기지 않습니다.
도메인 주도 설계가 제안하는 관점은 다릅니다. 경계는 데이터의 종류가 아니라 그 데이터를 두고 이야기하는 사람들의 언어에서 나옵니다.
어떻게 동작하나
같은 "상품"이라는 단어를 봅시다. 판매 팀에게 상품은 가격, 할인, 노출 여부입니다. 물류 팀에게 상품은 부피, 무게, 보관 온도입니다. 정산 팀에게 상품은 수수료율과 세금 코드입니다. 이 셋을 하나의 상품 테이블로 합치면, 물류 요구로 컬럼을 추가할 때마다 판매 팀 코드가 재배포됩니다.
이 지점이 바운디드 컨텍스트입니다. 각 컨텍스트는 자기만의 상품 모델을 가지고, 컨텍스트 사이는 식별자(예: SKU)로만 연결합니다. 컨텍스트 경계를 넘어갈 때 모델을 번역하는 것이 앞에서 본 부패 방지 계층입니다.
실무에서 경계 후보를 찾는 방법은 세 가지입니다. 첫째, 같은 단어의 뜻이 갈리는 지점. 둘째, 트랜잭션이 실제로 필요한 범위 — 한 트랜잭션 안에 있어야만 하는 데이터는 한 서비스 안에 있어야 합니다. 셋째, 변경 빈도 — 매주 바뀌는 부분과 1년에 한 번 바뀌는 부분은 다른 서비스일 가능성이 큽니다.
현장에서 만나는 모습
경계가 잘못됐다는 신호는 코드가 아니라 회의에서 먼저 나타납니다. 한 기능을 배포하는 데 항상 두 팀의 일정 조율이 필요하다면 경계가 틀린 것입니다. 그리고 정정하는 방법은 서비스를 더 쪼개는 게 아니라 대개 두 서비스를 도로 합치는 것입니다. 합치는 결정은 쪼개는 결정보다 훨씬 적게 내려지지만, 훨씬 자주 옳습니다.
또 하나. 마이크로서비스에서 "공용 라이브러리"는 조용한 결합입니다. 공통 DTO 를 한 저장소에 두면, 그 저장소의 버전을 올릴 때 모든 서비스가 함께 움직여야 합니다. 계약은 코드 공유가 아니라 스키마 문서(OpenAPI, protobuf)로 공유하는 편이 안전합니다.
경계를 찾는 실제 절차
"언어에서 찾는다" 는 말은 맞지만 실행하기 어렵습니다. 현장에서 쓰는 절차가 있습니다.
이벤트 스토밍. 도메인 전문가와 개발자를 한 방에 모아 벽에 "일어난 일" 을 과거형 으로 붙입니다. 주문이 접수됨, 결제가 승인됨, 재고가 차감됨, 배송이 시작됨. 그다음 이 사건들을 시간 순으로 늘어놓고, 누가 그 사건에 반응하는지 를 표시합니다. 반응이 몰리는 덩어리가 경계 후보입니다.
결합도를 세어 본다. 후보 경계를 그어 놓고 실제 코드에서 호출 횟수를 셉니다.
| 지표 | 좋은 값 | 나쁘면 뜻하는 것 |
|---|---|---|
| 한 화면을 그리는 데 필요한 서비스 수 | 1~2 | 3 이상이면 경계가 잘못됐다 |
| 한 기능 배포에 관여하는 팀 수 | 1 | 2 이상이면 경계가 팀을 가로지른다 |
| 서비스 간 동기 호출 깊이 | 1~2 | 3 이상이면 장애가 연쇄한다 |
모놀리스로 시작한다. 처음부터 나누면 대개 틀립니다. 도메인을 모르는 상태에서 그은 선이기 때문입니다. 한 저장소 안에서 모듈로 경계를 긋고(모듈러 모놀리스), 그 경계가 6개월 동안 흔들리지 않으면 그때 서비스로 뗍니다. 잘못 그은 모듈 경계는 반나절이면 옮기지만, 잘못 나눈 서비스는 몇 달이 걸립니다.
나누지 않아야 할 때
경계를 잘 긋는 것만큼 나누지 않기로 결정하는 것 도 설계입니다. 다음 조건에서는 서비스로 떼는 것이 거의 언제나 손해입니다.
- 팀이 하나다. 마이크로서비스의 이점은 팀의 독립적 배포입니다. 팀이 하나면 그 이점은 없고 운영 부담만 남습니다.
- 강한 트랜잭션이 필요하다. 재고 차감과 주문 생성이 반드시 함께 성공하거나 함께 실패해야 한다면, 둘을 나누는 순간 사가(saga)와 보상 트랜잭션을 직접 만들어야 합니다. 그 복잡도는 얻는 것보다 큽니다.
- 도메인을 아직 모른다. 새 제품의 첫 6개월에는 경계가 매주 바뀝니다.
운영 비용도 함께 셉니다. 서비스 하나를 늘리면 저장소·CI·배포 파이프라인·대시보드· 경보·온콜 문서가 각각 하나씩 늘어납니다. 서비스 다섯 개짜리 시스템은 코드가 다섯 배가 아니라 운영 표면이 다섯 배 입니다.
데이터가 경계를 넘어갈 때
경계를 그으면 곧바로 "그럼 조인은 어떻게 하나" 라는 문제가 옵니다. 주문 목록에 상품 이름을 보여 줘야 하는데 상품은 다른 서비스에 있습니다. 세 가지 답이 있고, 각각 값이 다릅니다.
- 호출해서 가져온다. 가장 단순하지만 상품 서비스가 죽으면 주문 목록도 죽습니다. 화면 하나에 서비스 둘의 가용성을 곱하게 됩니다(99.9% × 99.9% = 99.8%).
- 필요한 만큼 복제해 둔다. 주문에 상품 이름을 주문 시점의 값으로 박아 둡니다. 사실 이것이 도메인적으로도 옳습니다 — 상품명이 나중에 바뀌어도 과거 주문서의 이름은 그대로여야 합니다.
- 조회 전용 뷰를 만든다. 이벤트를 구독해 읽기 전용 테이블을 유지합니다(CQRS). 가장 유연하지만 최종 일관성과 재구축 절차를 감당해야 합니다.
두 번째가 정답인 경우가 생각보다 많습니다. "참조" 로 볼 것인가 "그 시점의 사실" 로 볼 것인가 를 먼저 정하면 답이 나옵니다.
다음 퀴즈에서 확인할 것
이 모듈은 개념만 다룹니다. 바로 이어지는 퀴즈로 경계 판단 기준을 점검하고, 다음 모듈에서 그렇게 나눈 서비스들이 실제로 통신할 때 무엇을 지불하는지를 손으로 확인합니다.