본문 바로가기

Architecture/architecture

[Hexagonal] architecture

Hexagonal 구조란 어떤 구조일까요?

흔히들 "포트 앤 어댑터(Ports and Adapters)" 아키텍처라고도 불리는 Hexagonal 구조는, 외부 세계(사용자, DB, 메시지 브로커 등)와 애플리케이션의 핵심 로직을 명확히 분리하고자 등장한 구조입니다. 이 구조에서는 애플리케이션의 핵심 로직(Core, 또는 도메인 로직)이 중심에 있고, 그 주위를 둘러싸는 형태로 여러 어댑터들이 존재합니다.

Hexagonal이라는 이름은 그 구조를 그림으로 나타낼 때 보통 육각형 모양으로 표현하기 때문에 붙여진 이름입니다. 중요한 건 이 육각형의 '모양'이 아니라, 중심(Core)과 외부(Adapters)를 연결하는 포트(Ports)의 개념입니다. 애플리케이션은 포트를 통해서만 외부와 통신하고, 어댑터는 이 포트를 구현하여 외부 시스템(DB, 웹, UI 등)과 연결합니다.

이미지 출처 : https://dzone.com/articles/hexagonal-architecture-in-java-2

그래서 Hexagonal이 왜 좋을까?

바로 유연성테스트 용이성, 그리고 의존성 역전(Dependency Inversion) 원칙을 자연스럽게 지킬 수 있기 때문입니다. 예를 들어, 웹 컨트롤러나 데이터베이스가 바뀌어도 애플리케이션 코어는 거의 영향을 받지 않으며, 테스트를 할 때도 실제 외부 환경 없이도 코어 로직만 독립적으로 테스트할 수 있습니다.

아래의 예제 코드를 보시면 핵심은 의존성 방향이 안쪽(core)으로만 흐릅니다.

  • OrderService는 OrderRepository라는 인터페이스에만 의존합니다.
    → 즉, "저장"이라는 기능이 있다는 사실에만 관심 있고, 어떻게 저장되는지는 모르게 됩니다.
  • 반대로, InMemoryOrderRepository가 OrderRepository를 구현함으로써
    도메인(core) 입장에서 보면 구현체는 존재조차 모르게 됩니다.
  • 구현이 추상에 의존하고, 애플리케이션 코어는 세부 사항에 전혀 신경 쓰지 않아도 됩니다.

도메인 모델

// Order.java  
public class Order { 
    private final String orderId; 
    private final int amount; 

    public Order(String orderId, int amount)     { 
        this.orderId = orderId; this.amount = amount; 
        } // 도메인 로직 예시 public boolean isExpensive() { return amount > 10000; } 
    // getter 생략 
}

서비스

// OrderService.java - 도메인 서비스
public OrderService(OrderRepository orderRepository) {
    this.orderRepository = orderRepository;
}

public void processOrder(Order order) {
    if (order.isExpensive()) {
        System.out.println("Expensive order detected!");
        }
    orderRepository.save(order);
    }
}

Port(Interface)

// OrderRepository.java - 포트 (Port)
public interface OrderRepository {
    void save(Order order);
}

Repository 어댑터 (driven Adapter)

// InMemoryOrderRepository.java - 어댑터 (Adapter)
public class InMemoryOrderRepository implements OrderRepository {
    @Override
    public void save(Order order) {
    // 실제 저장 로직. 여기선 메모리에 저장하는 척.
    System.out.println("Saving order: " + order);
    }
}

Web 어댑터 (driving Adapter)

// OrderController.java - 어댑터 (Web)
public class OrderController {
    private final OrderService orderService;
    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    public void createOrder(String id, int amount) {
        Order order = new Order(id, amount);
        orderService.processOrder(order);
    }
}

요약을 하자면 아래와 같습니다.

  • "안이 밖을 모른다" → 도메인 코어는 외부 변화에 영향받지 않음
  • "밖은 안을 따른다" → 모든 외부 요소는 코어가 제공하는 인터페이스를 따라야 함
  • 테스트, 유지보수, 변경에 강하다 → 도메인은 단단하고, 외부는 유연하게 바꿀 수 있음

'Architecture > architecture' 카테고리의 다른 글

[Layered] architecture  (0) 2025.04.06