36장. 실제 코드 구조로 보는 헥사고날 아키텍처
35장에서 우리는
헥사고날 아키텍처를 살펴봤다.
- 도메인은 외부를 모르고
- 포트는 역할을 정의하며
- 어댑터는 외부 기술을 담당한다
이제 질문은 이것이다.
이 구조를 실제 코드로는 어떻게 나눌 것인가?
전체 구조
헥사고날 아키텍처는
보통 다음과 같은 디렉토리 구조를 가진다.
src/
├─ domain/
├─ application/
├─ port/
├─ adapter/
│ ├─ in/
│ └─ out/
└─ config/
각 영역의 역할
1️⃣ domain/
비즈니스 로직이 존재하는 영역이다.
domain/
├─ Order.java
├─ OrderStatus.java
└─ OrderService.java
여기에는:
- 엔티티
- 도메인 로직
- 상태 전이 규칙
이 들어간다.
외부 기술에 대한 코드는 절대 들어가지 않는다
2️⃣ application/
유스케이스를 담당하는 영역이다.
application/
└─ CreateOrderUseCase.java
역할:
- 도메인 호출
- 흐름 제어
3️⃣ port/
도메인이 외부와 통신하기 위한 인터페이스다.
port/
├─ in/
│ └─ CreateOrderCommand.java
└─ out/
├─ OrderRepositoryPort.java
└─ EventPublisherPort.java
port/in
외부에서 들어오는 요청 정의
- UseCase 인터페이스
- Command 객체
port/out
외부로 나가는 요청 정의
- DB 저장
- 이벤트 발행
- 외부 API 호출
4️⃣ adapter/
외부 기술을 구현하는 영역이다.
adapter/in
adapter/in/
└─ OrderController.java
- HTTP Controller
- Kafka Consumer
adapter/out
adapter/out/
├─ persistence/
│ └─ OrderRepositoryImpl.java
├─ messaging/
│ └─ KafkaEventPublisher.java
└─ outbox/
└─ OutboxAdapter.java
5️⃣ config/
객체를 연결하는 영역이다.
config/
└─ AppConfig.java
여기서:
- 구현체 생성
- 의존성 주입
흐름을 따라가 보자
주문 생성 요청이 들어오는 경우:
Controller → UseCase → Domain → Port → Adapter → DB
실제 흐름
- Controller가 요청을 받는다
- UseCase 호출
- Domain 로직 실행
- RepositoryPort 호출
- Adapter가 DB 저장
이벤트 발행 흐름 (Outbox)
Domain → EventPort → OutboxAdapter → DB
- Domain은 이벤트 발행을 요청만 한다
- 실제 저장은 Outbox Adapter가 수행
이벤트 소비 흐름 (Inbox)
KafkaConsumer → UseCase → Domain
- Consumer는 Adapter
- Domain은 처리만 수행
중요한 규칙
1️⃣ domain은 adapter를 몰라야 한다
domain → adapter (X)
adapter → domain (O)
2️⃣ port를 통해서만 통신한다
domain → port → adapter
3️⃣ 외부 기술은 adapter에만 존재한다
- DB
- Kafka
- Redis
- API
잘못된 구조 예시
다음과 같은 코드는 피해야 한다.
class OrderService {
KafkaProducer kafka;
JdbcTemplate jdbc;
}
문제:
- 도메인이 외부 기술에 의존
올바른 구조 예시
class OrderService {
private final OrderRepositoryPort repository;
private final EventPublisherPort publisher;
}
이 구조의 효과
1️⃣ 코드가 명확해진다
- 어디에 무엇을 넣을지 명확
2️⃣ 테스트가 쉬워진다
new OrderService(fakeRepository, fakePublisher);
3️⃣ 기술 교체가 쉬워진다
- Kafka → SQS 변경
- DB 변경
→ adapter만 수정
실무에서 자주 하는 실수
1️⃣ port 없이 바로 adapter 사용
→ 의존성 다시 깨짐
2️⃣ domain에 framework 코드 넣기
→ 구조 무너짐
3️⃣ application 레이어 생략
→ 흐름이 domain에 몰림
정리
헥사고날 아키텍처는
개념만으로는 부족하다.
실제 코드 구조로 나누어야 의미가 있다
이 장의 핵심
- 헥사고날은 디렉토리 구조로 구현된다
- domain, port, adapter로 분리한다
- 의존성은 항상 domain 방향으로 향한다
- 외부 기술은 adapter에만 존재해야 한다
- 구조를 지켜야 아키텍처가 유지된다