Hexagonal 구조란 어떤 구조일까요?
흔히들 "포트 앤 어댑터(Ports and Adapters)" 아키텍처라고도 불리는 Hexagonal 구조는, 외부 세계(사용자, DB, 메시지 브로커 등)와 애플리케이션의 핵심 로직을 명확히 분리하고자 등장한 구조입니다. 이 구조에서는 애플리케이션의 핵심 로직(Core, 또는 도메인 로직)이 중심에 있고, 그 주위를 둘러싸는 형태로 여러 어댑터들이 존재합니다.
Hexagonal이라는 이름은 그 구조를 그림으로 나타낼 때 보통 육각형 모양으로 표현하기 때문에 붙여진 이름입니다. 중요한 건 이 육각형의 '모양'이 아니라, 중심(Core)과 외부(Adapters)를 연결하는 포트(Ports)의 개념입니다. 애플리케이션은 포트를 통해서만 외부와 통신하고, 어댑터는 이 포트를 구현하여 외부 시스템(DB, 웹, UI 등)과 연결합니다.
그래서 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 |
---|