[도메인 주도 설계] (9) 기본 구성 요소 6 – 리포지토리와 도메인 모델 체크리스트

도메인 주도 설계에서 리포지토리(Repository)는 도메인 모델에 기반해 객체를 저장하고 조회하는 것을 의미해요. 리포지토리는 데이터베이스 쿼리와 무엇이 다른지, 그리고 올바른 리포지토리는 어떤 특성을 가지고 있는지 알아보세요.

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

지난 편에서 도메인 객체가 어떻게 올바르게 만들어지는지를 다루는 팩토리(Factory)를 살펴봤어요. 이번 편에서는 만들어진 객체를 시간에 걸쳐 안전하게 다루는 리포지토리(Repository)를 알아봅니다. 지금까지 다룬 모든 기본 요소들을 실무에서 점검할 수 있는 최종 체크리스트와 시리즈 핵심 정리도 담았어요.


1. 리포지토리란 무엇인가: 저장된 객체에 대한 관리

팩토리가 “이 객체가 존재할 수 있는 조건이 무엇인가?”에 대한 질문에 답한다면, 리포지토리는 다른 질문에 답해요.

“도메인 규칙을 깨뜨리지 않고, 시간에 걸쳐 이 객체를 어떻게 다루는가?”

객체가 존재하면, 제품은 한 번만 상호작용하는 경우가 드물어요. 여러 사용자 액션에 걸쳐 불러오고, 업데이트하고, 저장하고, 다시 방문하죠. 리포지토리는 이 지속적인 상호작용이 안전하게 이루어지는 방법을 정의해요.

리포지토리(Repository)는 도메인 객체가 생성된 후부터 더 이상 필요하지 않을 때까지 관리하는 도메인 구성 요소예요.

리포지토리는 시스템이 도메인 언어를 사용해 도메인 객체를 요청하고 작업할 수 있는 인터페이스를 제공하면서, 기저 저장 메커니즘의 복잡성을 숨기죠. 도메인은 객체가 SQL 데이터베이스에 저장되든, NoSQL에 저장되든, 캐시에 저장되든, 외부 API에 저장되든 알지도 신경 쓰지도 않아요.

리포지토리는 영속성 세부 사항을 숨기고 의도 중심 연산을 노출함으로써, 리포지토리가 클라이언트를 저장 관심사 대신 도메인 개념에 집중하게 해요. 이것이 저장 전략과 인프라스트럭처가 진화해도 도메인 로직을 안정적으로 유지하는 방법이에요.

리포지토리를 비유하면, 도서관 사서와 같아요. “DDD 관련 책 있나요?”라고 물으면 사서가 찾아다 줘요. 책이 3층 서가에 있는지, 지하 창고에 있는지, 다른 도서관에서 대여해 오는지는 이용자가 알 필요 없죠. 사서가 도서관의 물리적 구조를 숨기고, “도서 주제”라는 도메인 언어로 소통하는 거예요.


2. 리포지토리가 중요한 이유: 제품 쿼리, 성능, 규칙의 교차점

리포지토리는 중요한 교차점에 위치해요.

리포지토리가 없으면 이 관심사가 섞이는 경향이 있어요. 도메인 로직이 쿼리 세부 사항에 의존하기 시작하고, 성능 최적화가 제품 규칙에 스며들고, 저장의 변경이 시스템 전체에 파급되죠.

리포지토리가 이 복잡성을 흡수하고 도메인에 깨끗하고 안정적인 표면을 제공해요.

리포지토리의 교차점을 비유하면, 음식 배달 플랫폼의 주문 시스템과 같아요. 사용자(제품 요구사항)는 “치킨 주문”만 하면 되고, 라이더 배치와 동선 최적화(성능)는 뒤에서 처리되고, “최소 주문 금액” 같은 규칙(도메인)은 항상 유지돼요. 이 세 가지를 섞으면 사용자가 라이더 동선을 신경 써야 하거나, 최소 금액 규칙이 성능 때문에 무시되는 상황이 생기죠.


3. 팩토리 vs 리포지토리: 탄생과 생명 주기

팩토리와 리포지토리는 둘 다 “객체를 다루기” 때문에 혼동되곤 해요. 하지만 매우 다른 일을 하죠.

팩토리 리포지토리
새 객체가 존재할 수 있는지 결정 이미 존재하는 객체를 관리
생성 시점 규칙을 강제 시간에 걸친 정확성을 보존
탄생에 집중 조회와 영속성에 집중

4. 리포지토리 설계 원칙

원칙 의미 왜 중요한가
애그리게이트 루트당 하나 각 리포지토리가 정확히 하나의 애그리게이트 루트를 관리. 내부 객체는 직접 불러오거나 조회하지 않음 애그리게이트의 일관성 경계를 보존하고 불변 조건 우회를 방지
도메인 중심 인터페이스 리포지토리 메서드가 쿼리 메커니즘이 아닌 비즈니스 의도를 표현. 질문이 제품 수준에서 인식 가능 도메인 언어를 깨끗하게 유지하고 데이터베이스 관심사 누출을 방지
트랜잭션 제어 없음 리포지토리가 언제 커밋할지, 여러 애그리게이트를 어떻게 조율할지를 결정하지 않음 접근에 집중하고, 오케스트레이션이나 플로우 제어를 분리

5. 예시: 구독 리포지토리

구독 리포지토리(Subscription Repository)이미 존재하는 구독을 다루기 위해 존재해요.

이런 질문에 답하죠.

구독이 존재해야 하는지, 변경이 허용되는지는 결정하지 않아요. 그 결정은 팩토리와 애그리게이트에 속하죠.

Subscription 리포지토리
───────────────────────

┌──────────────────────────────────────────┐
│ Subscription 리포지토리                     │
│------------------------------------------│
│ 답하는 도메인 질문:                           │
│ - ID로 구독 조회                            │
│ - 계정의 구독 찾기                           │
│ - 업데이트된 구독 저장                        │
└───────────────┬──────────────────────────┘
                │ 반환 / 저장
                ▼
┌──────────────────────────────────────────┐
│ Subscription (애그리게이트 루트)              │
└──────────────────────────────────────────┘

리포지토리는 구독이 어떻게 저장되고 조회되는지에 대해 의도적으로 말하지 않아요. 저장 기술, 쿼리 언어, 성능 최적화는 실질적인 관심사이지만, 리포지토리가 제공하는 도메인 영역의 바깥에 위치하죠.


6. 도메인 모델 기본 구성 요소 실무 체크 리스트

제품의 복잡성이 늘어나고 있는 것 같을 때, 다음 체크리스트를 활용해보세요.

1) 언어와 개념

2) 엔티티 vs 값 객체

3) 관계

4) 서비스

5) 모듈

6) 애그리게이트

7) 여러 애그리게이트

8) 팩토리 체크리스트

9) 리포지토리 체크리스트

10) 빠른 점검 신호

실무에서 도메인 모델에 문제가 있다는 신호를 빠르게 포착하는 질문들이에요.

팀이 무엇이 참이어야 하는지, 언제 참이어야 하는지, 누가 그것을 강제하는지를 명확히 말할 수 없다면, 코드가 아무리 깔끔해 보여도 도메인 모델은 무너질 거예요.


마무리: 이것이 중요한 이유

도메인 모델은 팀이 세 가지를 명확히 설명하지 못할 때 무너져요.

첫째, 기능이나 플로우와 관계없이 제품에서 항상 참이어야 하는 것이 무엇인지.

둘째, 그 규칙이 언제 적용되는지. 생성 시점에만인지, 업데이트 중인지, 전체 생명 주기에 걸쳐서인지.

셋째, 모델의 어떤 부분이 그것을 강제하는 책임을 지는지. 애그리게이트인지, 팩토리인지, 서비스인지.

이 답이 불명확하면, 규칙이 UI 로직, 서비스, 저장 코드에 천천히 퍼져요. 시스템은 여전히 작동할 수 있지만, 깨지기 쉽고 변경 비용이 비싸져요.

잘 설계된 도메인 모델은 이 책임들을 명시적으로 유지해서, 시스템이 진화해도 제품 결정이 이해 가능하게 남도록 해요.

도메인 주도 설계 시리즈