← posts/b.log()

blog92@web:~$ cat posts/db-design-23-domain-boundaries.md

DATABASE4 min read

DB 설계 A to Z 23강 — 도메인 경계와 DB 분리

바운디드 컨텍스트로 소유권을 나누고 스키마·권한·공개 뷰에서 물리 분리까지 단계적으로 강화하는 방법, DB를 나눌 때 잃는 조인·FK·트랜잭션과 아웃박스·CDC·사가라는 대안, 공유 DB 안티패턴을 다룬다.

스키마를 바꾸는 방법을 알았다면, 다음 질문은 그 스키마를 어디까지 하나로 둘 것인가입니다.

1. 왜 경계를 고민하는가

하나의 DB에 모든 것을 넣으면 처음에는 편하지만, 시간이 지나면:

  • 어느 팀이 어떤 테이블을 소유하는지 불분명해지고
  • 한 팀의 스키마 변경이 다른 팀의 쿼리를 깨뜨리며
  • 무거운 분석 쿼리가 운영 트랜잭션을 방해합니다

반대로 너무 일찍 나누면 조인이 불가능해지고, 분산 트랜잭션과 데이터 동기화라는 훨씬 비싼 문제가 생깁니다.

2. 바운디드 컨텍스트

같은 단어가 영역마다 다른 의미를 가집니다.

컨텍스트"주문항목"의 의미관심 속성
판매상품 선택 단위상품, 수량, 주문유형
결제결제 대상결제 금액, 결제 시점, 고객
물류처리 대상처리단계 계획, 실적, 물류센터
정산정산 집계 단위셀러, 매출 금액, 처리 수량

하나의 거대한 order_item 테이블에 모든 컨텍스트의 속성을 넣으면, 컬럼 수십 개와 서로 다른 변경 주기, 서로 다른 팀의 요구가 충돌합니다. 컨텍스트마다 자기 관점의 주문항목 모델을 두고, 공통 식별자(item_id)로 연결하는 것이 도메인 주도 설계의 접근입니다.

3. 분리의 단계

단계구조경계 강도조인
0단일 스키마없음자유
1같은 DB, 컨텍스트별 스키마 + 권한네임스페이스·권한가능 (권한 부여 시)
2같은 DB, 스키마 간 직접 조인 금지 (뷰·API만 허용)규약공개 뷰만
3컨텍스트별 DB물리적불가

앞서 정리한 원칙대로 처음에는 덜 나누는 쪽이 안전합니다. 1~2단계로 경계를 연습하고, 경계가 실제로 안정적임이 확인된 컨텍스트만 3단계로 옮깁니다. 나누는 것보다 합치는 것이 훨씬 어렵기 때문입니다.

4. 공개 인터페이스로서의 뷰

sql
-- 물류 컨텍스트가 소유
CREATE SCHEMA fulfillment;
CREATE TABLE fulfillment.item_stage (...);
 
-- 다른 컨텍스트에 공개하는 계약
CREATE VIEW fulfillment.v_item_progress_public AS
SELECT item_id, stage_code, actual_date
FROM fulfillment.item_stage;
 
GRANT SELECT ON fulfillment.v_item_progress_public TO inspection_app;
-- 내부 테이블에는 권한을 주지 않음

물류 컨텍스트는 내부 테이블을 자유롭게 바꾸고, 공개 뷰의 형태만 유지하면 됩니다. 이것이 1강의 외부 스키마를 조직 경계에 적용한 모습입니다.

5. DB를 분리했을 때 잃는 것과 대안

잃는 것대안
컨텍스트 간 조인API 조합, 데이터 복제(읽기 모델), 분석 저장소
컨텍스트 간 FK식별자 규약 + 비동기 정합성 검사
단일 트랜잭션사가(Saga), 보상 트랜잭션
일관된 스냅샷이벤트 시각 기준 정렬, 최종 일관성 수용

데이터 복제 (읽기 모델) 정산 컨텍스트가 주문항목의 출고건 정보를 자주 필요로 한다면, 물류 DB를 매번 호출하는 대신 필요한 속성만 로컬에 복제합니다. 복제는 이미 학습한 트랜잭셔널 아웃박스 + CDC 패턴으로 구현합니다.

sql
-- 물류 DB: 비즈니스 변경과 이벤트를 같은 트랜잭션으로
-- 바뀌는 부모는 출고건. 주문항목의 소속 주문(order_no)은 불변이라 건드리지 않음
BEGIN;
UPDATE fulfillment.order_item SET shipment_no = 'SH-26030-002' WHERE item_id = 7;
INSERT INTO fulfillment.outbox (aggregate_type, aggregate_id, event_type, payload)
VALUES ('OrderItem', '7', 'ItemReassigned', '{"item_id":7,"shipment_no":"SH-26030-002"}');
COMMIT;
-- Debezium이 outbox를 읽어 Kafka로 발행 → 정산 DB가 구독해 로컬 사본 갱신

복제된 데이터는 읽기 전용 사본이며, 원천 소유권은 물류 컨텍스트에 있다는 것을 스키마 이름과 문서로 명확히 합니다.

6. 공유 DB 안티패턴

여러 서비스가 같은 테이블을 각자 직접 읽고 쓰는 구조입니다.

  • 테이블 변경 시 영향 범위를 알 수 없음
  • 서비스별 배포가 독립적이지 않음
  • 락 경합과 성능 문제의 원인 추적이 어려움

"DB는 하나지만 테이블 소유자는 하나"라는 규칙만 지켜도 많은 문제가 사라집니다. 소유 서비스만 쓰기, 다른 서비스는 공개 뷰로 읽기.

7. 분리 판단 기준

질문분리 쪽
변경 주기와 배포 주기가 다른가?예
다른 팀이 소유하는가?예
부하 특성이 크게 다른가? (OLTP vs 분석)예
보안·규제 등급이 다른가?예
강한 일관성이 필요한 트랜잭션이 경계를 넘는가?아니오여야 분리 가능
경계를 넘는 조인이 핵심 기능인가?아니오여야 분리 가능

23강 정리

  1. 바운디드 컨텍스트마다 같은 개념을 다르게 모델링할 수 있다.
  2. 경계는 스키마·권한·공개 뷰 → 물리 분리 순으로 점진적으로 강화한다.
  3. DB를 나누면 조인·FK·트랜잭션을 잃고, 복제·사가·최종 일관성으로 보완한다.
  4. 컨텍스트 간 복제는 아웃박스 + CDC로 구현한다.
  5. 강한 일관성이 경계를 넘는다면 아직 나눌 때가 아니다.
COMMENTS (…)

댓글을 불러오는 중이에요.

NEW COMMENT0 / 1000
⌘↵ 전송

blog92@web:~$ cd ..