[도메인 주도 설계] (8) 기본 구성 요소 5 – 팩토리

도메인 주도 설계에서 팩토리(Factory)는 도메인 모델에 기반해 작동할 수 있는 객체를 생성하는 것을 의미해요. 팩토리는 애그리게이트 및 생성자와 무엇이 다른지, 그리고 올바른 팩토리는 어떤 특성을 가지고 있는지 알아보세요.

'Domain-Driven DESIGN'이라는 컬러 타이포그래피 로고. 하늘색 배경에 '도메인 주도 설계 — (8) 기본 구성 요소 5 - 팩토리'라는 텍스트가 적혀 있다.

지난 편에서 애그리게이트로 일관성 경계를 설계하는 방법을 살펴봤어요. 이번 편에서는 도메인 객체가 어떻게 올바르게 만들어지는지를 다루는 팩토리(Factory)에 대해 알아볼게요.


1. 팩토리란 무엇인가: 객체를 도메인 모델에 맞게 만드는 곳

모듈, 애그리게이트와 같은 기본 구성 요소들은 객체가 존재한 이후에 집중했어요. 하지만 모든 객체에는 시작점이 있죠.

애그리게이트가 불변 조건을 보호하기 전에, 서비스가 행동을 조율하기 전에, 무언가가 처음부터 올바르게 만들어져야 해요. 이 생성의 순간이 제품 규칙이 조용히 누출되는 곳인 경우가 많아요. 이것이 팩토리(Factory)가 해결하려는 문제예요.

팩토리는 객체가 어떻게 존재하게 되는지를 통제하는 도메인 개념이에요.

“이 객체가 애초에 존재할 수 있는 조건이 무엇인가?”

생성은 단순히 메모리를 할당하거나 생성자를 호출하는 게 아니에요. 대부분의 제품에서 객체를 생성한다는 건 입력을 검증하고, 불변 조건을 강제하고, 기본값을 적용하고, 관련 객체를 유효한 방식으로 연결하는 것이에요.

팩토리가 이 책임을 중앙화해요. 객체가 존재한다면, 시스템은 이렇게 가정할 수 있어요.

“모든 생성 규칙을 통과했다.”


2. 생성에 경계가 필요한 이유: 여러 생성 경로 문제

생성은 보통 간단해 보여요. 사용자가 버튼을 클릭하고, 무언가가 나타나고, 플로우가 계속되죠.

문제는 같은 것이 종종 여러 다른 플로우에서 생성된다는 거예요.

예를 들어, 예약이 이런 곳에서 만들어질 수 있어요.

모두 “예약 만들기”처럼 보이지만, 시스템에 대한 서로 다른 생성 경로예요.

명확한 경계가 없으면, 각 경로가 천천히 자체 가정을 만들어요. 하나는 기본 취소 규칙을 적용하는데 다른 하나는 잊어버리고, 하나는 초기 상태를 올바르게 설정하는데 다른 하나는 미정으로 남겨두죠.

시간이 지나면 제품에 생성된 방식에 따라 다르게 행동하는 예약들이 생겨요.

팩토리는 하나의 규칙을 정의해요.

“이것이 어디에서 생성되든, 이 조건들은 반드시 참이어야 한다.”

팩토리가 생성에 대해 하는 일은 애그리게이트가 업데이트에 대해 하는 일과 같아요. 제품 규칙이 조용히 갈라지는 것을 방지하죠.

생성 경계는 공항 보안 검색대와 같아요. 1번 게이트든, 비즈니스 라운지든, 직원 출입구든, 비행기에 탑승하려면 반드시 보안 검색을 통과해야 해요. 어떤 경로로 들어오든 적용되는 규칙이 동일해야 안전이 보장되죠. 하나의 게이트에서만 검색을 하고 다른 곳에서는 안 하면 보안에 구멍이 생기는 것처럼요.


3. 팩토리 원칙: 원자적 생성과 불변 조건 강제

아래 원칙은 팩토리가 어떻게 구현되는지가 아니라 무엇을 보장해야 하는지를 설명해요.

원칙 의미 왜 중요한가
원자적(Atomic) 생성 생성이 완전히 성공하거나 완전히 실패 반쯤 유효한 상태로 객체가 존재하는 것을 방지
불변 조건 강제 모든 필수 규칙이 생성 시점에 검사됨 무효한 상태가 시스템에 들어오기 전에 차단
명시적 인터페이스 호출자가 구성 단계가 아닌 의도를 표현 생성 규칙이 플로우를 깨뜨리지 않고 변경 가능

핵심은 생성이 점진적이지 않다는 거예요. 팩토리는 “거의 유효한” 또는 “일단은 유효한” 객체를 절대 반환해서는 안 돼요. 생성이 실패하면, 아무것도 존재해서는 안 돼요.

원자적 생성은 은행 계좌 개설과 같아요. 본인 확인, 최소 예치금, 약관 동의가 모두 완료되어야 계좌가 열려요. “본인 확인만 하고 나머지는 나중에”라고 하면 계좌가 열리지 않죠. 반쯤 열린 계좌는 존재하면 안 되니까요.


4. 팩토리와 애그리게이트: 루트의 생성이 통제되어야 하는 이유

팩토리가 가장 중요한 곳은 실수를 되돌리기 가장 어려운 곳, 바로 애그리게이트 루트예요.

애그리게이트 루트는 일관성 경계를 정의하고, 핵심 불변 조건을 보호하고, 제품이 권위적으로 다루는 개념이에요. 그래서 비공식적으로나 간접적으로 생성되어서는 안 되죠.

실무에서 다음과 같은 뜻을 담고 있어요.

이것은 애그리게이트가 생성 이후에 하는 역할과 대칭을 이뤄요.

애그리게이트시간에 걸쳐 정확성을 보호하고, 팩토리생성 순간의 정확성을 보호하죠.


5. 예시: 구독 팩토리 (체험판 vs 엔터프라이즈 규칙)

SaaS 제품에서 구독(Subscription)이 애그리게이트로 다뤄진다고 해 볼게요.

구독은 특정 조건이 생성 시점에 이미 참이어야 유효해요.

이것들은 나중에 적용되는 행동이 아니에요. 구독이 애초에 존재할 수 있는지를 정의하죠.

Subscription 팩토리
────────────────────

┌─────────────────────────────────────────┐
│        Subscription 팩토리                │
│-----------------------------------------│
│ 생성 시점에 결정:                           │
│ - 요청된 플랜 유형이 무엇인지                  │
│ - 어떤 기본 한도가 적용되는지                  │
│ - 만료가 필요한지                           │
│ - 생성이 거부되어야 하는지                    │
└───────────────┬─────────────────────────┘
                │ 생성
                ▼
┌─────────────────────────────────────────┐
│     Subscription (애그리게이트 루트)         │
│-----------------------------------------│
│ 식별자: subscriptionId                    │
│                                         │
│ 존재 시 보장:                              │
│ - 한도가 정의됨                             │
│ - 만료 규칙이 충족됨                         │
│ - 플랜별 제약이 존중됨                       │
└─────────────────────────────────────────┘

중요한 건 이 규칙이 언제 적용되는지예요.

구독 애그리게이트가 존재하는 시점에는 기본값이 이미 확정되어 있고, 무효한 조합은 이미 거부되었고, “완성하기” 위한 후속 단계가 필요하지 않아요. 구독 애그리게이트는 절대 “원시” 또는 불완전한 형태로 존재하지 않죠.


6. 팩토리 vs 생성자: 각각 적절한 상황

모든 객체는 어떤 방식으로든 생성돼요. 생성자(Constructor)는 가장 기본적인 방법이에요. 주어진 값을 받아서 인스턴스로 조립하죠.

생성자와 팩토리 모두 객체를 만들지만, 다른 질문에 답해요.

측면 생성자 팩토리
하는 일 주어진 값에서 객체를 조립 객체가 존재해도 되는지 결정
생성 규칙 “값이 제공되면 만든다” “규칙이 충족되어야만 만든다”
비즈니스 규칙 최소한이거나 없음 생성 시점에 명시적으로 강제
변형 처리 여러 변형에 부적합 여러 생성 변형을 처리
변경 허용도 생성 로직을 안전하게 진화시키기 어려움 생성 규칙이 한 곳에서 진화 가능
제품적 의미 생성이 기술적 단계 생성이 제품 결정
전형적 신호 “여기에 객체가 필요해” “이것이 잘못 존재하면 절대 안 돼”

경험 법칙은 이래요.

잘못된 생성이 사용자 혼란, 매출 손실, 정책 위반을 일으킬 수 있다면, 생성은 팩토리를 거쳐야 해요.


다음 편에서는 기존 객체의 접근을 관리하는 리포지토리(Repository)를 살펴볼게요.

도메인 주도 설계 시리즈