서론
EDA 스터디를 진행하면서 개인적으로도, 또 공통적이고 반복적으로 나온 의견이 다음과 같았다.
- DDD 를 같이 공부해야 하나?
- 이벤트 스토밍 중 Aggregate 용어가 나와서 찾아보니 DDD(Domain Driven Design) 용어임.
- 경계 컨텍스트를 나누는 작업은 도메인에서 시작함.
- 추상적인 이야기라서 와닿지 않는다. 코드레벨에서 구현이 어떻게 되어있는지 궁금하다.
DDD와 헥사고날이 패턴이 각각 어떤 질문에 어떻게 답하는지를 조사하고, 이 결과를 얼마 전 진행한 EDA 를 적용하여 API 응답 지연 개선 작업 회고에 적용해보았다.
DDD
도메인을 어떻게 모델링하고, 경계를 어떻게 나눌 것인가?
EDA에서 다루는 용어와 설계 단위가 DDD에서 비롯된다고 느껴져서 DDD의 철학과 주요 개념을 간단히 정리해보았다.
Eric Evans는 DDD에서 복잡한 소프트웨어의 진짜 복잡성은 기술이 아니라 도메인에 있다고 말한다. 코드의 구조를 비즈니스의 구조와 일치시키면, 비즈니스가 변할 때 코드도 해당 부분만 변경하면 되므로 복잡성이 줄어든다.
용어
- 도메인(Domain): 소프트웨어가 해결하려는 비즈니스 문제 영역 그 자체
- 채용 플랫폼이면 "채용 공고, 지원, 기업 매칭"이 도메인이고, 쇼핑몰이면 "상품, 주문, 결제"가 도메인
- DDD는 이 도메인의 언어와 규칙을 코드에 그대로 반영하자는 설계 철학
- Entity: 고유 식별자(ID)가 있는 객체.
- 속성이 바뀌어도 같은 객체임.
- Value Object:
- 식별자가 없고, 값 자체가 정체성인 객체.
- 불변(immutable)
- Aggregate: 함께 변경되어야 하는 Entity와 Value Object의 묶음.
- Aggregate Root가 유일한 진입점이고, 외부에서 내부 객체를 직접 수정하면 안 됨
- Repository는 Aggregate Root 단위로만 존재
- 경계 컨텍스트: 같은 단어가 다른 의미를 가지는 경계.
- EDA에서 이벤트가 흐르는 단위
- Ubiquitous Language: 개발자와 비즈니스 담당자가 같은 용어를 사용하자는 원칙
경계컨텍스트 예시
일상에서 사용하는 서비스들을 분석해보았다.
오늘의 집 서비스에서 상품이라는 단어는 컨텍스트에 따라 다른 모델을 가진다.
콘텐츠 컨텍스트 → 사진 속 태그 (이름, 이미지)
스토어 컨텍스트 → 구매 대상 (가격, 옵션, 재고, 배송, 리뷰)
판매자 컨텍스트 → 관리 대상 (등록폼, 심사상태, 수수료, 정산)
광고 컨텍스트 → 광고 소재 (노출수, 클릭률, 캠페인기간)
당근마켓에서 게시글이라는 단어는 컨텍스트에 따라 다른 모델을 가진다
중고거래 컨텍스트 → 사진, 가격, 카테고리, 판매상태, 끌어올리기
동네생활 컨텍스트 → 주제태그, 공감수, 댓글, 동네 범위
알바 컨텍스트 → 시급, 근무시간, 근무요일, 사업장
부동산 컨텍스트 → 매매/전세/월세, 면적, 보증금, 입주일
광고 컨텍스트 → 노출수, 클릭률, 지역타겟, 과금방식
Hexagonal 아키텍처 (aka. Adapter and Port 패턴)
코드를 어디에 배치하고, 의존성 방향을 어떻게 잡을 것인가?
레퍼런스 코드를 찾다보니 반복적으로 언급되는 Hexagonal 아키텍처를 알게 되었다.
전통적으로 비즈니스 로직과 외부 기술을 분리하기 위해 UI 쪽에는 MVC, DB 쪽에는 DAO 패턴이 사용되어왔다. 그런데 Cockburn은 이 둘이 사실 같은 문제, 즉 비즈니스 로직과 외부 기술의 얽힘라는 점에 주목하여 비즈니스 로직을 외부 기술로부터 완전히 분리하는 Adapter and Port 패턴을 설계했다. Hexagonal은 육각형과는 관련이 없으며, UI - 애플리케이션 - DB 의 일렬로 이루어진 구조가 개발자들로 하여금 마치 UI 와 DB 를 완전히 다른 것으로 인식하게 하여 서로 다른 접근과 전략을 사용하도록 해온 것과 대비되기 위함이라고 한다.

이 아키텍처는 크게 세 영역으로 이루어져 있다.
- Domain (=핵심 비즈니스 로직): 가장 안쪽 레이어로, 순수한 비즈니스 규칙만 존재. 외부 기술에 대한 의존성이 전혀 없음.
- Entity, Value Object, Domain Service 등
- Port (=인터페이스): 도메인과 외부 사이 인터페이스
- Inbound Port (Driving Port): 외부에서 도메인으로 들어오는 요청을 정의
- Outbound Port (Driven Port): 도메인이 외부 인프라에 요청하는 것을 정의
- Adapter (=구현체): Port의 실제 구현
- Inbound Adapter: REST Controller, GraphQL Resolver, CLI 등
- Outbound Adapter: Prisma Repository 구현체, HTTP 클라이언트, SQS Publisher 등
의존성 방향은 항상 바깥 → 안쪽이다. 즉 도메인은 절대 Adapter를 직접 참조하지 않고, Port(인터페이스)만 알고 있다.
장점으로는
- DB를 Aurora에서 다른 걸로 바꿔도 Adapter만 교체하면 도메인 로직은 그대로
- 테스트 시 Adapter를 Mock으로 쉽게 교체 가능
- 비즈니스 로직이 프레임워크나 인프라에 종속되지 않음
Conclusion
정리해보면 DDD, EDA와 헥사고날 구조는 현실세계를 모델링하고 SW로 구현하는데 필요한 개념이라고 이해했다.
- 모델링
- 명사 (무엇이 있는가) → DDD (Entity, Value Object, Aggregate)
- 규칙 (무엇을 해도/하면 안 되는가) → DDD (도메인 메서드, 상태 전이)
- 동사-동기적 (무엇을 한다) → DDD (Aggregate 메서드)
- 동사-비동기적 (그 결과 뭐가 일어난다) → EDA (도메인 이벤트, 구독)
- 코드 구조 → 헥사고날 (Port, Adapter)
Discussion
만약 서비스를 처음 개발할때 부터 DDD, 헥사고날 구조, EDA를 적용했으면 문제가 전혀 없었을 것이라고 생각하지는 않는다.
처음부터 완벽한 아키텍처를 짜는 게 아니라, 단순하게 시작하고 복잡성이 생기는 지점에서 구조를 개선하는 진화적 설계(Evolutionary Design) 개념이 비즈니스 최적화 측면에서는 최선의 선택일 수 있다.
또 Evans 에 따르면 모델은 반복적으로 정제된다고 한다. 처음부터 완벽한 도메인 모델을 짜는 게 아니라, 비즈니스를 운영하면서 이해가 깊어질 때 오히려 모델이 더 정교하게 고도화 될 수 있다고 생각한다.
이는 YAGNI(You Ain't Gonna Need It)와도 비슷한 맥락이다. 복잡성을 예측해서 미리 대비하는 것보다, 복잡성이 드러나는 시점을 잘 감지해서 적시에 구조를 개선하는 것이 현실적이라고 생각한다.
참고
https://alistair.cockburn.us/hexagonal-architecture
hexagonal-architecture
hexagonal-architecture
alistair.cockburn.us
포스트를 작성하면서, 이전에 진행했던 API 응답 지연을 Transactional Outbox 패턴으로 해결한 작업에 더 개선 포인트를 찾았다. 해당 내용은 별도로 다루겠다.
'SW Engineering > 아키텍처&설계' 카테고리의 다른 글
| Event Driven Architecture 고찰 - (3) 서비스에 분석하고 적용하기 (0) | 2026.04.04 |
|---|---|
| Event Driven Architecture 고찰 - (1) 이벤트스토밍 회고 (2) | 2026.03.13 |
| Building Event Driven Microservices 북스터디 (0) | 2025.12.31 |