본문으로 건너뛰기

도메인 용어

16분 분량

이번 포스팅에서는 도메인, 도메인 모델, 도메인 오브젝트, 도메인 오브젝트 모델이 서로 어떻게 다른지에 대한 이야기를 해보려고 한다.

DDD 글을 읽다가 이 단어들이 같은 것을 가리키는지 헷갈렸던 프론트엔드 개발자를 위한 글이다. 끝까지 읽으면 네 용어를 추상에서 구체로 내려가는 한 줄 위에 놓을 수 있고, API 응답 타입이 왜 도메인 모델이 아닌지 설명할 수 있다.

필자도 개발을 하면서 이 단어들을 꽤 자주 접해왔지만, 막상 "도메인이 정확히 뭔데?"라는 질문에는 명쾌하게 대답하기가 쉽지 않았다. (솔직히 개발을 처음 시작했을때 도메인은 www 을 의미하는 줄 알았다.) 예시는 모두 종합소득세 계산으로 든다.


도메인(Domain)

가장 기초적인 질문부터 시작해보자. 도메인이란 무엇인가?

Eric Evans는 그의 저서 Domain-Driven Design: Tackling Complexity in the Heart of Software(2003) 에서 도메인을 다음과 같이 정의한다.

지식, 영향력, 또는 활동의 영역.

"A sphere of knowledge, influence, or activity."

쉽게 말해, 프로그래밍으로 해결하고자 하는 문제 영역 그 자체가 도메인이다. 세금 신고 서비스를 만든다면 "세금 신고"가 도메인이고, 보험 청구 플랫폼을 만든다면 "보험 청구"가 도메인인 것이다. 도메인은 코드가 아니다. 소프트웨어 이전에 존재하는 현실 세계의 문제 영역이다.

프론트엔드 개발자에게 이것은 어떤 의미일까? 우리가 만드는 UI는 결국 이 도메인을 사용자에게 보여주고 조작할 수 있게 해주는 창(window) 이다. 세금 도메인을 메인으로 다루는 토스인컴, 삼쩜삼 같은 세금 환급 서비스를 개발한다면, 소득 유형, 경비율, 소득공제, 세액공제, 환급액이라는 도메인 개념을 UI로 표현하는 것이다. 따라서 프론트엔드 개발자도 자신이 다루는 도메인을 깊이 이해해야 한다. UI 컴포넌트를 잘 그리는 것 못지않게, "이 서비스가 해결하는 문제가 무엇인지" 를 아는 것이 중요하다는 뜻이다.

그런데 "세금"이라는 도메인 하나만 해도, 들여다보면 내부에 수많은 하위 도메인이 존재한다. 내가 겉으로만 알고있는 종합소득세 계산 파이프라인만 봐도 이렇다.

종합소득세 계산 파이프라인. 총수입금액에서 납부와 환급까지 이어지는 단계가 소득, 공제, 세액, 결과 네 하위 도메인으로 색이 나뉜다

이 파이프라인의 각 단계가 독자적인 규칙과 데이터를 가진 하위 도메인이다. "세금"이라는 하나의 큰 도메인 안에 소득(Income), 공제(Deduction), 세액(Tax), 결과(Filing)라는 세부 도메인들이 얽혀 있는 것이다. 이것을 코드로 어떻게 나눠야 하는지가 바로 도메인 모델링의 핵심 질문이다.

도메인 모델(Domain Model)

그렇다면 도메인 모델은 무엇일까? 도메인이랑 "도메인 모델"은 어떻게 다른 것일까?

Martin Fowler는 도메인 모델을 행위와 데이터를 모두 포함하는 도메인의 객체 모델이라고 정의한다. Eric Evans의 정의는 여기서 한 걸음 더 들어간다.

도메인의 선택된 측면을 기술하는 추상화 체계로, 해당 도메인과 관련된 문제를 해결하는 데 사용될 수 있다. (Eric Evans)

A system of abstractions that describes selected aspects of a domain and can be used to solve problems related to that domain.

핵심은 "선택적 추상화" 라는 점이다. 도메인 모델은 현실 세계의 모든 것을 담지 않는다. 영화감독이 현실의 모든 장면을 담지 않고 이야기에 필요한 장면만 선택하듯, 도메인 모델도 해결하려는 문제에 필요한 측면만 골라서 구조화한 것이다.

여기서 중요한 점이 하나 있다. 도메인 모델은 반드시 코드일 필요가 없다. 화이트보드에 그린 다이어그램일 수도 있고, 팀원들 머릿속에 공유된 멘탈 모델(Mental Model)일 수도 있다. 결국 도메인 모델이라는 용어 자체는 소프트웨어와는 별개의 개념일 수 있다.

여기서 프론트엔드 개발자가 특히 혼동하기 쉬운 부분이 있다. API 응답 구조를 보고 "이게 도메인 모델이군"이라고 생각하는 것이다. 하지만 이는 데이터 모델(Data Model) 이지, 도메인 모델이 아니다.

데이터 모델과 도메인 모델을 구분해보면 아래와 같다.

구분 도메인 모델 데이터 모델
목적 비즈니스 개념과 규칙을 표현 저장/전송 구조를 정의
언어 비즈니스 용어 (과세표준, 세액공제, 환급액) 기술 용어 (string, number, array)
포함 요소 데이터 + 행위(규칙) 데이터 구조만
예시 "과세표준 1,400만원 이하 구간은 세율 6%" { taxableBase: number, taxRate: number }

데이터 모델은 "어떤 형태로 데이터가 오가는가"를 정의하고, 도메인 모델은 "이 데이터가 비즈니스적으로 무엇을 의미하고 어떤 규칙을 따르는가"를 정의한다. 이 둘을 구분하지 못하면, 컴포넌트가 API 응답 구조에 직접 의존하게 되어 백엔드 스키마가 바뀔 때마다 프론트엔드 전체가 흔들리는 상황이 벌어진다.

도메인 오브젝트(Domain Object)

도메인 모델이 개념의 체계라면, 도메인 오브젝트는 그 개념이 코드로 구현된 실체이다.

Code with Jason 을 운영하고있는 Jason Swett 의 글에서 도메인 오브젝트를 이렇게 정의한다.

내 객체 모델의 객체 가운데 도메인 모델에서도 하나의 개념으로 존재하는 것은 도메인 오브젝트라고 부르겠다.

Any object in my object model that also exist as a concept in my domain model I would call a domain object.

즉, 도메인 모델에서 "종합소득"이라는 개념이 있고, 코드에서 Income이라는 타입이 있다면, 이 Income이 바로 도메인 오브젝트인 것이다. 하지만 모든 코드 객체가 도메인 오브젝트인 것은 아니다. HttpClient, LocalStorageAdapter, useDebounce 같은 것들은 기술적 도구이지 도메인 개념이 아니다.

Entity와 Value Object

Evans는 도메인 오브젝트를 Entity, Value Object, Service 세 범주로 분류한다. (Martin Fowler는 이 분류를 "Evans Classification"이라고 부른다.) Service는 "특정 오브젝트에 자연스럽게 귀속되지 않는 도메인 연산"을 표현하는 별도 개념인데, 이 절이 다루려는 핵심은 데이터를 어떻게 식별하느냐의 문제이므로 Entity와 Value Object 두 가지를 중심으로 살펴본다.

Entity(엔티티) 는 시간과 다양한 표현을 관통하는 고유한 정체성을 가진 객체다. 세금 신고서(TaxFiling), 납세자(Taxpayer), 소득 내역(IncomeRecord)처럼 고유한 ID로 식별되며, 속성이 바뀌더라도 같은 ID면 같은 엔티티이다. 신고서의 공제 항목이 수정되어도, 신고서 ID가 바뀌지 않는 한 그것은 여전히 같은 신고서이다.

Value Object(값 객체) 는 속성의 조합으로만 의미를 가지는 객체로, 모든 속성 값이 같으면 동일한 것으로 간주한다. 금액(Money), 세율(TaxRate), 과세 구간(TaxBracket)처럼 값 자체가 의미인 객체이다. "세율 6%"는 어디에서 쓰이든 "세율 6%"일 뿐이다.

프론트엔드에서 이 구분이 왜 중요할까? 아래 코드 예시를 통해 알아보자.

interface TaxFiling {
  id: string;
  taxpayerName: string;
  taxYear: number;
  status: FilingStatus;
}
 
const isSameFiling = (a: TaxFiling, b: TaxFiling) => a.id === b.id;
 
interface Money {
  amount: number;
  currency: "KRW" | "USD";
}
 
const isSameMoney = (a: Money, b: Money) =>
  a.amount === b.amount && a.currency === b.currency;

TaxFiling 은 id를 정체성의 기준으로 삼기에 Entity이다. (id 필드를 가지는 것 자체가 Entity의 정의가 아니라, "그 id로 같은지 다른지를 판단한다"는 점이 핵심이다.) Money 는 id 없이 amount와 currency의 조합으로만 식별되고, 모든 속성이 같으면 같은 값으로 간주된다.

Entity는 ID 기반 비교, Value Object는 속성 기반 비교. 이 구분을 명확히 하면 상태 관리에서 "이 데이터가 같은 건지 다른 건지"를 판단하는 로직이 자연스럽게 정리된다. 리스트에서 아이템을 갱신할 때 Entity라면 ID로 찾아서 교체하고, Value Object라면 불변 교체(immutable replace)를 하는 식이다.

도메인 오브젝트 모델(Domain Object Model)

"도메인 모델"과 "도메인 오브젝트"는 알겠는데, 도메인 오브젝트 모델은 무엇일까?

찾아보니 의외로 합의된 정의가 없다. 상당수의 문헌에서는 "도메인 모델", "도메인 오브젝트 모델", "개념 모델(conceptual model)", "분석 오브젝트 모델(analysis object model)"을 사실상 동의어로 본다. 객체지향 분석 단계에서 그려지는 개념적 모델을 부르는 여러 이름이라는 입장이다.

반면 조금 더 분리된 계층으로 보는 시각도 있다. 도메인 모델이 실제 코드로 변환되는 지점이 바로 객체 모델이라는 설명이 대표적이다.

이 두 번째 관점에서 오브젝트 모델은 시스템 안의 모든 코드 객체의 구조다. 여기에는 HttpClient나 useDebounce 같은 기술적 도구도 포함된다. 그 안에서 도메인 개념을 표현하는 객체들의 부분집합, 그리고 그들 사이의 관계가 바로 도메인 오브젝트 모델이다. "Object Model"을 시스템의 정적 구조(클래스, 속성, 연산, 관계)로 정의해온 객체지향 모델링의 전통과도 맞닿아 있다.

필자는 이쪽 관점이 프론트엔드 개발자에게 더 실용적이라고 본다. 우리가 실제 작성하는 코드에는 도메인 객체와 기술 객체가 늘 섞여 있기 때문이다.

결국 도메인 → 도메인 모델 → 도메인 오브젝트 모델 → 도메인 오브젝트 는 추상에서 구체로 내려가는 계층이다. 도메인이 가장 넓고, 도메인 오브젝트가 가장 구체적이다. 그래서 프론트엔드 코드를 작성할 때 우리가 실질적으로 고민하는 영역은 결국 도메인 오브젝트 모델(도메인 개념을 표현하는 타입과 그 사이의 관계)을 어떻게 구조화할 것인가 다.

마무리

정리하면, 도메인은 우리가 해결하려는 문제 영역이고, 도메인 모델은 그 문제를 선택적으로 추상화한 개념 체계이며, 도메인 오브젝트 모델은 필자가 택한 관점에서 그 개념 체계를 코드로 구현한 것이고, 도메인 오브젝트는 그 구현 안의 개별 객체이다.

이 구분이 코드에서 어떤 차이를 만드는지, 즉 세금 계산 같은 도메인 로직을 컴포넌트 밖 어디에 두고 어디까지 나눌지는 도메인 모델에서 이어서 다룬다.

이 글을 읽는 독자 분들도 다음에 API 응답 타입을 보면 "이건 데이터 모델인가, 도메인 모델인가"를 한 번쯤 물어보시길 바란다.

참고 자료

참고 자료

댓글