본문 바로가기
MSA (Micro Service Architecture)/Legacy to Domain-Driven Platform

[MSA 말고 Modular Monolith] 2편 — Domain Driven Design 도메인 경계 찾기 : 레거시 DB 테이블 분석

by kellis 2026. 6. 29.

 

 


이 시리즈는 글쓴이 본인이 수행한 레거시 서비스를 리뉴얼 전환한 과정에서 수행한 의사 결정 사항들을 정리한 글입니다. 
실제 운영 중인 PHP 레거시 에듀테크 플랫폼을 Python + React + K3S 환경으로 전환하는 과정에서 
아키텍처 설계와 인프라 구성에 대한 내용이 포함되어 있습니다. 

 

목차

 

 

 

 


 

전환 프로젝트를 시작할 때 가장 중요한 것은 설계라고 생각한다. 

특히 도메인 주도 방식으로 아키텍처를 만들거라면 도메인 경계를 설정하는 것이 가장 먼저 수행해야할 일이 될 것이다. 

도메인을 모르는데 어떻게 폴더 구조를 잡고, 라우터를 짜고, 모델을 정의할 것인가. 

 

이번 글에서는 DDD의 전략적 설계(Strategic Design)가 무엇인지, 그리고 그걸로 레거시로부터 도메인 경계를 어떻게 찾아내는지를 다룰 것이다. 

 

 

1. DDD를 다시 정의하기: 패턴이 아니라 관점 

DDD(Domain Driven Design)라고 하면 흔히 Entity, Value Object, Repository, Aggregate 같은 패턴들을 떠올린다. 코드를 어떻게 짜느냐의 문제로 받아들이는 것이다.

그런데 Eric Evans가 2003년 그의 책에서 이야기한 DDD의 본질은 그것이 아니다. 

DDD는 소프트웨어를 비즈니스 도메인에 맞춰 모델링하는 접근법이다. 핵심은 패턴이 아니라, 도메인을 소프트웨어 설계의 중심에 놓는 관점이다.

 

 

Evans의 책은 크게 두 부분으로 나뉜다. 그리고 이 구분이 도메인 주도 설계를 이해하는 열쇠이다. 

  • Tectical Design (전술적 설계)
    • Bounded Context 안에서 코드를 어떻게 모델링하는가
    • Entity, Value Object, Aggregate, Domain Service, Domain Event
    • "어떻게 구현하는가"
  • Strategic Design (전략적 설계)
    • 큰 도메인을 어떻게 나누고 경계를 긋는가
    • Subdomain, Bounded Context, Context Map
    • "어디서 나누는가"

 

많은 사람들이 전술적 설계(패턴)부터 배운다. 그러나 전환 프로젝트에서 먼저 필요한 것은 전략적 설계다. 경계를 잘못 그으면, 그 안에서 패턴을 아무리 잘 써도 의미가 없다. 틀린 곳에 벽을 세우고 방을 예쁘게 꾸미는 격이다. 

 

 

그래서 2편에서는 전략적 설계를, 3편에서는 전술적 설계를 나누어서 다룬다. 

 

 


 

2. 전략적 설계의 핵심 개념들

경계를 긋기 전에, 전략적 설계의 기본 개념을 정리해보자. 이 개념들이 곧 판단의 도구가 된다. 

 

2.1 Domain과 Subdomain

Domain(도메인) 은 소프트웨어가 다루는 비즈니스 영역 전체다. 이 글에서 다루는 전환 프로젝트의 경우에는 "교육 서비스 운영" 이 도메인이 된다. 

즉, 도메인은 너무 큰 단위이다. 그래서 더 작은 Subdomain(서브도메인)으로 나눈다. 

Evans는 서브도메인을 세 종류로 구분했고, 이 구분이 전환 전략에 직접 영향을 준다. 

 

  • Core Domain (핵심 도메인)
    • 비즈니스 경쟁력이 나오는 곳
    • 시스템의 아이덴티티
    • 가장 공들여 설계해야할 영역
    • ex ) 학습 진행, 출제/시험
  • Supporting Subdomain (지원 서브도메인) 
    • 핵심을 받쳐주지만, 그 자체가 경쟁력은 아닌 영역
    • ex ) 학원/조직 관리, 콘텐츠 관리
  • Generic Subdomain (일반 서브도메인)
    • 어느 회사나 비슷하게 필요한 영역
    • 직접 만들기도 하지만 라이브러리를 가져다 쓰기도 하는 영역
    • ex ) 인증, 알림, 결제

 

이 분류가 중요한 이유는, 자원을 어디에 쏟을지를 알려주기 때문이다. Core Domain에 고비용을 투자하고, Generic은 검증된 라이브러리나 외부 서비스로 빠르게 해결할 수도 있는 것이다. 

 

 

 

2.2 Bounded Context — DDD 전략 설계의 심장

전략적 설계에서 가장 중요한 개념이 Bounded Context(경계 컨텍스트) 이다.

Bounded Context는 하나의 도메인 모델이 일관되게 유지되는 경계다. 같은 단어가 그 경계 안에서는 항상 같은 의미를 갖는다. 

 

"학생(Student)"이라는 단어를 예로 들어보자.

학원 관리 컨텍스트에서의 학생
→ 등록 정보, 소속 학원, 연락처를 가진 "회원"

학습 컨텍스트에서의 학생
→ 진도, 성취도, 학습 이력을 가진 "학습자"

결제 컨텍스트에서의 학생
→ 결제 수단, 청구 내역을 가진 "결제자"

 

같은 "학생"이라도 컨텍스트마다 관심사가 다르다. 이걸 하나의 거대한 Student 객체로 만들면, 온갖 책임이 뒤섞인 괴물이 된다. 

 

Bounded Context는 이렇게 말한다. 

 

"각 컨텍스트가 자기만의 Student 모델을 갖게 하라"

 

학원 컨텍스트의 Student와 학습 컨텍스트의 Student는 다른 모델이다. 같은 사람을 가리키더라도.

 

 

 

2.3 Ubiquitous Language — 경계를 드러내는 신호

Ubiquitous Language(보편 언어)는 개발자와 도메인 전문가가 함께 사용하는, 모호함 없는 공통 언어를 의미한다. 

이것이 Bounded Context와 연결된다. 같은 단어가 다른 의미로 쓰이기 시작하는 지점이 바로 컨텍스트 경계다. 

 

"Student"가 어떤 대화에서는 회원을 뜻하고, 다른 대화에서는 학습자를 뜻한다면, 그 두 대화는 서로 다른 Bounded Context에 속한다. 언어의 균열이 경계의 위치를 알려주는 것이다. 

 

 

 


 

 

 

3. 레거시에서 도메인을 찾는다는 것

여기서 우리는 상황의 특수성을 짚어야 한다. 전략적 설계를 설명하는 대부분의 글은 그린필드(처음부터 새로 만드는 프로젝트)를 가정한다. 그러나 이 글에서는 다르다. 이미 기운영중인 시스템이 있다. 도메인이 코드와 DB안에 이미 녹아있다. 다만 경계가 흐릿하게 뭉개진 채로. 

이건 단점이기도 하지만 장점이기도 하다. 

 

단점
 - 잘못된 구조가 이미 굳어져 있다. 
 - 경계가 뭉개져 있어 추출이 어렵다.
장점
- 추측할 필요없이 실제 데이터가 존재한다.
 - 무엇이 함께 쓰이는지 코드로 구현되어 있다. 

 

그래서 여기에서는 역방향 도메인 추출 (reverse domain extraction) 을 한다. 새로 그리는게 아니라, 이미 있는 시스템에서 도메인을 역으로 캐낸다. DB 스키마와 코드 의존성이 1차 자료가 된다. 

이 방식의 핵심은 무엇보다 

DB 경계는 도메인 경계가 아니다.

 

라는 점이다. 

 

레거시 DB는 "어떤 서비스(앱)에 속하는가"로 나뉘어 있다. 하지만 도메인은 "어떤 비즈니스 책임을 갖는가"로 나뉜다. 이 둘은 일치하지 않기 때문에 DB 구조를 그대로 베껴선 안된다. 거기서 진짜 도메인을 추출해야 한다. 

 

 


 

 

4. 경계를 긋는 6가지 기준

이 글의 핵심이다. 

"도메인 경계를 어디에 그을 것인가"를 판단하는 구체적 기준이 필요하다. 직관으로 그으면 사람마다 달라지고, 어째서 이렇게 나누었지에 대해 타당한 근거를 제시할 수 없다.

DDD이론(응집도 높이기, 결합도 낮추기, Bounded Context, Ubiquitous Language)을 실무에서 던질 수 있는 질문으로 바꾸면 이러하다. 

 

기준 1. 함께 변경되는가 (Change)

 - 하나가 바뀌면  다른 것도 같이 바뀌어야 하는가? 

  → "높은 응집도". 같이 바뀌면 한 경계 안에 둔다.

 

기준 2. 같이 조회되는가 (Query)

 - 항상 함께 읽고 쓰는가? 

  → 늘 붙어 다니면 같은 컨텍스트일 가능성이 높다.

 

기준 3. 생명주기가 같은가 (Ligecycle)

 - 같이 생성되고 같이 삭제되는가?

  → Aggregate의 단서이기도 하다. 이는 다음 글에서 다루겠다.

 

기준 4. 같은 용어가 같은 의미인가 (Language)

 - 여기서의 "학생"과 저기서의 "학생"이 같은 뜻인가? 

  → Ubiquitous Language. 뜻이 갈리면 컨텍스트 경계다.

 

기준 5. 하나의 트랜잭션 단위인가 (Transaction)

 - 하나의 일관성 단위로 함께 커밋되어야 하는가? 

  → 트랜잭션 경계는 도메인 경계의 강한 힌트.

 

기준 6. 책임 주체가 같은가 (Ownership)

 - 이 데이터를 누가 책임지고 변경하는가? 

  → 책임 주체(팀/역할)가 다르면 도메인도 다르다.

 

이 6가지는 서로 보완한다. 하나의 기준만으로 단정하지 않고, 여러 기준을 함께 들이대서 판단한다. 그리고 각 기준은 전략적 설계 개념과 대응된다. 

 

 


 

5. 기준 적용하기 

이제 이 기준들을 실제 레거시에 적용해본다. 

먼저 분석 범위. 재구축 대상인 A, B 서비스만 도메인으로 설계하고 C, D 서비스는 그대로 이관하므로 도메인 분석 대상이 아니다. 

 

예 1. 흩어진 것을 하나로 — 응집도로 묶기

레거시에서 "학습 진행" 관련 데이터는 여러 DB에 흩어져 있다. 서비스 별로 거의 같은 구조가 복제되어 있다고 보면 된다. 

 

예를 들어 "학습카드" 개념이 서비스 A와 B 각각에 존재한다. 

기준 4 (같은용어) : 각 서비스 모두 "학생의 학습 진행"을 의미한다. 

기준 6 (책임주체) : DB는 다르나 책임은 "학습 진행 관리"로 동일하다. 

→ 물리적으로 나뉘어 있어도 도메인은 하나. Learning 컨텍스트로 통합.

 

예 2. 붙어 있던 것을 갈라내기 — 언어로 나누기

반대로 레거시의 거대한 Student 테이블은 회원 정보, 학습 상태, 결제 정보를 한 테이블에 다 담고 있다. 

 

하나의 Student 테이블 = 회원 + 학습자 + 결제자

 

기준 4 (같은 용어) : "학생"이 맥락마다 다른 의미.

 - 학원 관리에서는 "회원"

 - 학습에서는 "학습자"

 - 결제에서는 "결제자"

→ 언어가 갈리는 지점이 컨텍스트 경계. 세 컨텍스트로 분리.

 

Bounded Context 개념이 그대로 적용된다. 같은 "학생"이라도 Academy / Learning / Billing 컨텍스트가 각자의 모델을 갖는다. 

 

 

예 3. 묶지 말아야 할 것 — 결합도로 분리

 

결제(Payment) vs 출제 (exam)

 

기준 1 (함께 변경) : 결제 로직이 바뀐다고 출제 로직이 바뀌지 않는다. 

기준 5 (트랜잭션) : 결제 처리와 시험지 생성은 같은 트랜잭션이 아니다. 

→ 묶을 이유가 전혀 없다. 별개의 도메인.

 

이렇게 기준을 잡으면, 모든 경계 결정에 "왜?"라고 답할 수 있다. 

 

 

흔한 함정 : 이름에 속지 않기

마지막으로, 테이블이나 메뉴명이 도메인을 알려주지 않는다는 것을 인지해야 한다. 

예를 들어 어떤 서비스에는 game이라는 이름의 테이블이 존재한다. 이름만 보면 게임화(gamification) 도메인처럼 보이지만, 실제로는 학습 기능이라기 보다는, 서비스 홍보용 마케팅 페이지였다. 이름으로 분류하여 학습 도메인으로 넣었다면 엉뚱한 결합이 생겼을 것이다. 

경계는 이름이 아니라 책임으로 긋는다. 

 

 

 

 


 

 

6. 도메인 후보 도출

위 기준들을 A, B 서비스 데이터에 적용해 묶었을때 아래와 같이 도메인 후보가 정리되었다.

 

도메인 후보 성격(Subdomain 분류)
조직/학원 관리 Supporting
인증/계정 Generic
교실/수강 Supporting
콘텐츠/교재 Supporting
출제/시험 Core
학습 진행 Core
결제/청구 Generic
공지/고객지원 Generic

 

후보를 나누면서 적용한 판단 중 몇 가지는 아래와 가다. 

 

조직과 인증을 나눈 이유

조직/학원과 인증을 한 덩어리로 보기에는 인증은 책임 주체(기준 6)가 다르다. 보안·세션의 영역이고, 어느 서비스나 비슷하게 필요한 Generic Subdomain이다. 그래서 분리했다.

 

리포트를 독립 도메인으로 두지 않은 이유 

리포트/통계는 따로 떼어내면 독립적으로 보이지만, 기준1(함께 변경)로 보면 학습 데이터가 바뀔때 같이 바뀐다. 학습(Learning)의 읽기 모델로 보는 게 맞았기에 Core인 학습 도메인에 흡수했다. 

 

이 후보들을 최종 8개 Bounded Context로 확정하고, 각 컨텍스트의 내부를 Aggregate로 설계하는 것이 다음 편의 내용이 될 것이다. 

 

 


 

 

7. 데이터 전략: A,B는 새 스키마, C,D는 유지

도메인을 새로 그었으니, 데이터도 그에 맞춰야한다. 여기서 한 가지 결정이 필요했다.

기존 MySQL을 그대로 쓸 것인가, 새로 설계할 것인가. 

 

서비스는 정식 오픈을 한지 얼마 되지 않아, 축적된 데이터량이 많아야 수십만건 정도였다. 따라서 데이터가 더 쌓이기 전에 새 경계를 기준으로 분리하는 것이 맞겠다고 생각했다. 

 

서비스 A, B (재구축)   → 도메인 기반 새 스키마 설계 + 데이터 마이그레이션
                                       Bounded Context별로 깨끗하게 분리
서비스 C,D (유지)       → 기존 스키마 그대로 (PHP를 안 건드리므로 DB도 그대로)

 

 

핵심을 잊어선 안된다. 이 프로젝트의 목표는 단순 언어 이전이 아니라 도메인 주도 설계 기반 재설계 라는 것. 

A, B 서비스까지 옛 스키마를 쓰면 도메인 경계를 코드에만 긋고 데이터는 옛 경계로 남기는 반쪽자리가 된다. 

배치 스크립트로 마이그레이션 가능한 정도의 데이터량이니 처음부터 도메인 기반으로 설계하는 것이 맞다.

 

이 방식은 Martin Fowler의 Strangler Fig 패턴과 닿아 있다. 레거시를 한 번에 들어내지 않고, 새 시스템을 곁에서 키우며 점진적으로 교체한다. C,D 서비스를 남겨둔 채 A, B 서비스만 새 스키마로 가는 것이 그 점진적 교체의 한 형태이다.

 

 

 


 

마치며

이번 글은 코드가 아닌 관점에 대한 내용이었다. 정리하자면 아래와 같다.

 

✓  DDD는 패턴이 아니라 도메인을 중심에 놓는 관점

✓  전략적 설계(Strategic) vs 전술적 설계(Tactical)의 구분

✓  Subdomain 분류(Core/Supporting/Generic)로 자원 배분

✓  Bounded Context — 같은 단어가 같은 의미인 경계

✓  Ubiquitous Language — 언어의 균열이 경계를 드러낸다

✓  레거시에서는 역방향으로 도메인을 추출한다

✓  경계를 긋는 6가지 실무 기준

✓  기준을 적용해 도메인 후보 도출

 

 

그리고 가장 중요한 내용은

 

경계는 이름이나 DB가 아니라, 책임과 언어로 긋는다.

 

는 것이다. 

 

분석을 건너뛰고 FastAPI 폴더부터 만들었다면, 십중팔구 기존 DB 경계를 그대로 따라갔을 것이다. 전략적 설계를 먼저 한 이유가 바로 여기에 있다. 

 

다음편에서는 전술적 설계(Tactical Design) 로 넘어간다. 이번에 도출한 도메인 후보를 8개 Bounded Context로 확정하고, 각 컨텍스트 안을 Aggregate, Entity, Value Object로 설계하는 과정을 다룰 것이다. 

 

 

 

 

 

** 참고 자료

 

Domain-Driven Design (Eric Evans, 2003) — DDD의 원전. 전략적 설계는 Part IV에서 다룬다.

 

Domain-Driven Design 개념 정리 — Martin Fowler: https://martinfowler.com/bliki/DomainDrivenDesign.html

 

bliki: Domain Driven Design

a bliki entry for Domain Driven Design

martinfowler.com

Bounded Context — Martin Fowler: https://martinfowler.com/bliki/BoundedContext.html

 

bliki: Bounded Context

Don't try to build a single, unified model for a large domain. Instead DDD advises us to divide such a domain into many bounded contexts with explicit relationships between them.

martinfowler.com

 

Ubiquitous Language — Martin Fowler: https://martinfowler.com/bliki/UbiquitousLanguage.html

 

bliki: Ubiquitous Language

a bliki entry for Ubiquitous Language

martinfowler.com

 

Strangler Fig 패턴 — Martin Fowler: https://martinfowler.com/bliki/StranglerFigApplication.html

 

bliki: Strangler Fig

Inspired by the strangler figs in Australia, a strangler fig application gradually draws behavior out of its host legacy application

martinfowler.com

 

 

 

댓글