1. 헥사고날의 핵심 문제 정의: "의존성의 역전"
전통적인 3계층 아키텍처(Controller - Service - Repository)에서는 서비스 로직이 DB 코드(JPA 등)에 직접 의존합니다. 이로 인해 DB 스키마가 바뀌면 비즈니스 로직 코드가 줄줄이 깨집니다.
헥사고날은 이를 **'내부(도메인)'**와 **'외부(인프라)'**로 나누어 해결합니다.
- 내부 (Inside): 순수한 비즈니스 로직 (애그리거트, 도메인 엔티티). "우리 회사는 쿠폰을 어떻게 발급하는가?"라는 규칙만 존재합니다. 여기엔 import문에 spring, jpa, aws 같은 단어가 절대 들어오지 않습니다.
- 외부 (Outside): DB, 웹 UI, 메시지 큐, 외부 API 등. 로직을 실행하기 위한 수단일 뿐입니다.
2. 세 가지 핵심 구성 요소
① 포트 (Port) - "구멍"
내부가 외부와 소통하기 위한 인터페이스입니다.
- 입력 포트 (Inbound Port): 외부(사용자, API 요청)가 내부 로직을 실행하기 위해 통과하는 문. (예: OrderService 인터페이스)
- 출력 포트 (Outbound Port): 내부 로직이 데이터를 저장하거나 이벤트를 보낼 때 사용하는 추상적인 구멍. (예: OrderRepository 인터페이스)
② 어댑터 (Adapter) - "플러그"
포트라는 구멍에 끼워 넣는 실제 구현체입니다.
- 입력 어댑터 (Driving Adapter): HTTP 요청을 받아서 내부 로직으로 전달하는 Controller가 대표적입니다.
- 출력 어댑터 (Driven Adapter): 내부의 저장 요청을 받아 실제 DB에 저장하는 JpaRepository 구현체나 KafkaProducer가 해당됩니다.
③ 도메인 (Domain) - "핵심"
질문자님이 말씀하신 애그리거트가 사는 곳입니다. 모든 비즈니스 판단은 여기서만 일어납니다.
1. 헥사고날이 아닌 코드 (전통적 방식)
이 방식은 서비스가 '카프카'라는 구체적인 기술에 꽉 묶여 있습니다. 카프카 설정이 바뀌거나 RabbitMQ로 바꾸려면 비즈니스 로직을 다 뜯어고쳐야 하죠.
// 내부(서비스)가 외부(인프라)를 직접 참조하는 나쁜 예
@Service
public class OrderService {
private final KafkaTemplate<String, String> kafkaTemplate; // 구체적인 기술(Kafka)에 의존
public void completeOrder(Order order) {
// 1. 비즈니스 로직 실행
order.complete();
// 2. 외부 인프라 직접 호출
kafkaTemplate.send("order-topic", "주문 완료됐음: " + order.getId());
}
}
헥사고날 아키텍처 적용 코드
이 방식에서는 OrderService 안에 카프카 관련 코드가 1그램도 섞이지 않습니다.
내부 도메인 영역 (Inside)
비즈니스 로직은 오직 **포트(인터페이스)**만 바라봅니다.
// [Output Port] 외부로 나가는 구멍
public interface OrderEventPublisher {
void publish(OrderCompletedEvent event);
}
// [Domain Service] 비즈니스 로직의 중심
@Service
public class OrderService {
private final OrderEventPublisher eventPublisher; // 인터페이스(포트)에만 의존!
public void completeOrder(Order order) {
// 1. 비즈니스 로직 실행 (애그리거트 상태 변경)
order.complete();
// 2. 포트 호출 (이게 카프카인지 알림톡인지 서비스는 모름)
eventPublisher.publish(new OrderCompletedEvent(order.getId()));
}
}
외부 인프라 영역 (Outside)
실제 카프카 기술은 어댑터라는 이름으로 성벽 밖에서 구현됩니다.
// [Output Adapter] 포트에 끼워지는 실제 플러그
@Component
public class KafkaOrderEventAdapter implements OrderEventPublisher {
private final KafkaTemplate<String, String> kafkaTemplate;
@Override
public void publish(OrderCompletedEvent event) {
// 실제 카프카 기술을 사용하는 곳은 여기뿐!
kafkaTemplate.send("order-topic", "주문번호: " + event.getOrderId());
}
}
'개발기술 > 설계|소프트웨어패턴' 카테고리의 다른 글
| Saga 패턴 (0) | 2025.12.16 |
|---|---|
| 동시성 고려 프로그래밍(stateless, Thread-Safe, DB Lock) (0) | 2025.11.10 |
| GOF 디자인 패턴 (0) | 2025.09.22 |
| 소프트웨어 패턴 개념과 분류 (0) | 2025.09.22 |
| Consumer-Producer 패턴 (2) | 2025.08.11 |