출처

만들면서 배우는 헥사고날 아키텍처 설계와 구현 - 링크 도메인 주도 개발 시작하기 - 링크

hexa

도메인 헥사곤

문제 영역에서 라우터가 고정된 것이 아니고, 라우터의 특성이 변경될 수 있다는 사실을 알 수 있다. 이 때문에 라우터는 수명주기를 가진다고 말할 수 있다. 이외에도 모든 라우터는 인벤토리에서 고유해야하므로 식별자를 가져야 한다. 이러한 ‘연속성’과 ‘정체성’은 엔티티를 결정하는 요소이다.

  • DDD의 그 도메인을 이야기한다.
  • Entity, Value Object, aggregate의 개념도 그대로 있다.
  • 아마 아래에서 한 번 더 정리할 것 같다.
  • 여기서는 ‘헥사고날 아키텍처에서는 가장 안쪽 Layer를 이루고 있다.‘는 사실이 중요하다.

애플리케이션 헥사곤

비즈니스 규칙을 ‘지원’하지만, 소프트웨어의 컨텍스트 외부에는 존재하지 않는다. 애플리케이션에 특화된 오퍼레이션이다. 유즈케이스, 입력포트, 출력 포트를 기반으로 구성되어있다.

  1. 유즈케이스 (Use Case)
  • 유즈케이스는 시스템이 제공해야 하는 동작(특정 작업 흐름)을 정의하며, 도메인 규칙을 실행하기 위해 외부와 도메인을 연결하는 역할을 한다.
  • 입력 포트와 출력 포트를 통해 도메인과 외부 시스템을 연결하는 핵심 동작을 구현한다.
class CreateOrderInteractor(
    private val inventoryOutputPort: InventoryOutputPort,
    private val orderOutputPort: OrderOutputPort
) : CreateOrderUseCase {
    override fun execute(request: CreateOrderRequest) {
        // Step 1: 재고 확인
        if (!inventoryOutputPort.checkInventory(request.productId, request.quantity)) {
            throw IllegalArgumentException("재고가 부족합니다.")
        }

        // Step 2: 도메인 객체 생성
        val order = Order(
            productId = request.productId,
            quantity = request.quantity,
            userId = request.userId
        )

        // Step 3: 출력 포트를 통해 주문 저장
        orderOutputPort.saveOrder(order)
    }
}
  1. 입력 포트 (input port)
  • 입력 포트는 시스템이 외부로부터 들어오는 요청을 도메인 유즈케이스로 전달하는 인터페이스.
interface CreateOrderUseCase {
    fun execute(request: CreateOrderRequest)
}
  1. 출력 포트 (output port)
  • 출력 포트는 도메인이 외부 시스템(DB, API 등)과 상호작용할 때 사용하는 인터페이스이다.
interface InventoryOutputPort {
    fun checkInventory(productId: String, quantity: Int): Boolean
}

interface OrderOutputPort {
    fun saveOrder(order: Order)
}

프레임워크 헥사곤

  1. Driving Operation과 Input Adapter
    • 외부 요청을 받아서 이를 도메인 로직(유즈케이스)으로 전달하는 역할
    • 사용자가 시스템과 상호작용하거나, 외부 시스템이 애플리케이션을 호출할 때 사용하는 진입점
    • 주로 컨트롤러, 이벤트 리스너, 또는 메시지 큐 소비자가 인풋 어댑터의 역할을 수행
  2. Driven Operation과 Output Adapter
    • **도메인 로직(유즈케이스)**에서 발생한 작업 결과를 외부 시스템에 전달하는 역할
    • 도메인 로직이 직접 외부 시스템(DB, API, 메시지 브로커 등)과 상호작용하지 않고, 출력 포트를 통해 이 어댑터를 호출
    • 주로 리포지토리, API 클라이언트, 메시지 발행자가 아웃풋 어댑터의 역할을 수행

흐름 정리와 내 생각

  1. 외부 요청 → 인풋 어댑터: 외부 요청이 컨트롤러(인풋 어댑터)를 통해 시스템으로 들어온다
  2. 인풋 어댑터 → 유즈케이스: 입력 포트를 통해 유즈케이스를 호출
  3. 유즈케이스 → 출력 포트: 비즈니스 로직 처리 후, 출력 포트를 호출하여 외부 작업을 위임
  4. 출력 포트 → 아웃풋 어댑터: 출력 포트의 구현체인 아웃풋 어댑터가 외부 시스템과 상호작용

개요만 봤을때는 크게 와닿지는 않았다.

대충 비유하자면 DDD를 레이어드 아키텍처로 구현한걸로 비교했을 때, 표현계층이 InputAdapter로, 인프라계층이 OutputAdapter로 도메인은 그대로 도메인, 서비스에서 도메인을 호출하던 로직을 작게 나누어 유즈케이스로 나눈다 정도인것같고,

InputAdapter, OutputAdapter는 유연하게, 어떠한 어댑터를 만들어 끼워도 동작할 수 있도록 포트를 잘 설정해두는 것 정도가 중요한 것 같다. 도메인을 가운데 두고 기술적인 코드와 비즈니스 로직(도메인의)을 분리한다 라는 말이 아직 잘 와닿지는 않는다. 다만 부정적인 감상을 이야기하는 것은 아니고, 조금 더 봐야 할 것 같다는 이야기이다.