본문 바로가기

개발기술/설계|소프트웨어패턴

헥사고날 아키텍처 (포트와 어댑터(Ports and Adapters) 패턴)

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());
    }
}