헥사고날 아키텍처를 알아보자

spaghetti·2025년 5월 14일

헥사고날 아키텍처는 비즈니스 로직(핵심 도메인)을 외부의 관심사(DB, 연동 서비스)등으로 부터 분리하는 것을 목표로 하는 아키텍처다. 애플리케이션 핵심 비즈니스 로직을 육각형(hexagon)안에 두고, 외부 세계와의 상호작용은 포트와 어댑터라는 인터페이스를 통해 이루어지도록 설계하는 것이다. 육각형이라는 것은 그림을 보면 좀 더 이해가 쉽다

그림과 같이 IN, OUT이라는 개념이 존재하고 어댑터와 어플리케이션을 연결하는 포트를 통해서 연결되고 있음을 볼 수 있다.

핵심 구성 요소

  • 도메인(Domain)

    • 도메인이란 애플리케이션의 핵심 비즈니스 로직과 규칙을 포함한다. (DDD(Domain-Driven Design)라는 개념을는 개념을 들어봤다면 더 이해하기 쉽다)
    • 도메인 영역은 외부의 특정 기술(데이터베이스, 프레임워크)등에 대한 의존성이 없어야한다.
    • 오직 인터페이스(포트)를 통해서만 외부와 상호작용한다.
  • 포트(Port)

    • 도메인이 외부 세계와 통신하기 위해 정의하는 인터페이스다.
    • IN Port : 사용자나 다른 외부 시스템이 핵심 도메인을 실행시키기 위해 사용하는 인터페이스이다. 보통 컨벤션으로 인터페이스 명을 Usecase로 정의한다.
    • OUT Port : 핵심 도메인 요청을 외부 시스템과 통신하기 위해 사용하는 인터페이스이다. 보통 컨벤션으로 인터페이스 명을 Port로 정의한다.
  • 어댑터(Adapter)

    • 포트 인터페이스의 구현체이다. 특정 기술을 도메인이 이해할 수 있는 방식으로 변환하거나, 도메인 요청을 외부 시스템이 이해할 수 있는 방식으로 변환한다.
    • In Adapter : 외부의 요청을 받아서 핵심 도메인 기능을 호출하는 것으로 controller가 이에 해당한다.
    • Out Adapter : 도메인의 요청을 외부 시스템과 통신하여 처리하는데 repository가 이에 해당한다.

아키텍처 구조

MVC 패턴과 비교해서 헥사고날은 어떤 구조를 가지는 차이점을 알아보자.

  • MVC 패턴
com.example.app
├── controller/
│   ├── UserController.java
│   └── ProductController.java
├── model/
│   ├── User.java
│   └── Product.java
├── service/
│   ├── UserService.java  
│   └── ProductService.java 
├── repository/         
│   ├── UserRepository.java
│   └── ProductRepository.java    
└── ...

보통은 이런식으로 controller / model / service / reposiotry로 분리하여 패키지 구조를 구성할 것이다. 도메인별로 구성하는 경우도 있는데 어쨋든, 여기에는 외부시스템과의 구별은 없다. 요즘은 MSA처럼 도메인마다 어플리케이션이 분리되는 구조에다가, 다양한 외부 시스템을 이용해서 어플리케이션을 개발하는데 변경되는 부분들이 많다면 이러한 구조는 결합도가 높다고 볼 수 있다.

  • 헥사고날 아키텍처
com.example.app
├── domain/          // 핵심 비즈니스 로직
│   ├── model/       // 엔티티, 값 객체 등
│   │   ├── User.java
│   │   └── Product.java
│   ├── usecase/     // 비즈니스 행위
│   │   ├── CreateUserUseCase.java
│   │   └── PlaceOrderUseCase.java
│   ├── port/        // 외부 시스템과의 상호작용
│   │   ├── UserRepositoryPort.java
│   │   └── PaymentGatewayPort.java
│   └ service/
│       └── OrderService.java  
├── adapter/         // 외부 시스템과의 상호작용을 처리하는 어댑터
│   ├── in/          // 외부 요청을 핵심 도메인으로 전달
│   │   ├── rest/
│   │   │   └── UserRestController.java
│   │   └── cli/
│   │       └── UserCommandLineAdapter.java
│   ├── out/         // 핵심 도메인의 요청을 외부 시스템으로 전달
│   │   ├── persistence/
│   │   │   └── UserRepositoryJpaAdapter.java
└── ...

헥사고날 아키텍처는 JPA나 외부 시스템에 대한 상호작용은 adapter로 분리해놓은 것을 볼 수 있다. 만약 JPA를 쓰지 않고 다른 방식으로 DB 요청을 해야한다면 domain 부분은 건드릴 필요가 없다. usercase와 port라는 인터페이스로 이루어져 구현체인 adpater만 바꿔 개발하면 되기 때문이다.

장점과 단점

모든 아키텍처는 항상 좋을 수는 없다. 결론적으로는 나의 어플리케이션의 상황에 맞춰 선택하는것이 중요하다. 내 어플리케이션이 헥사고날 아키텍처에 적합한지 알아보는 것은 중요하다.

1) 장점

  • 위에서 설명했듯이 비즈니스 로직과 기술적인 구현 세부 사항을 명확하게 분리 하기 때문에 의존성이 낮아진다. 그래서 아까 말한것처럼 그냥 adpater구현체만 개발하면 된다. 이는 유지보수성과 확장성을 크게 향상시킨다.
  • 독립적이라는 것은, 테스트에서도 유리해진다. 예를들어 이전에는 도메인의 비즈니스 로직을 검증하려면 JPA 기술도 함께 적용해야하기 때문에 불필요하게 테스트 코드를 작성해야했지만, 아무것도 의존하지 않는 도메인만 테스트한다면 Mock객체로 대체하여 더 빠르게 로직을 검증할 수 있다.

2) 단점

  • 하지만 패키기 구조는 상당히 복잡해졌다. controller/service/model에서 끝나는게 아니라 도메인과 연결해주는 port도 필요하고 이를 구현하는 구현체인 adapter도 필요하기 때문이다.
  • 또한 도메인은 어떠한 외부시스템의 영향도 받아서는 안되기때문에 실무에서 상당히 많은 어려움에 부딪힌다. 순수 도메인을 유지하기 위해서 변환하는 작업이 반드시 일어나야하고(물론 실무에 따라서 생략하기는 하지만 어쨋든 위배되는것은 사실이다)이러한 작업들은 초기 개발 시간이 상당해짐을 말한다.

그렇기 때문에 헥사고날 아키텍처는 애플리케이션이 다양한 외부시스템과 상호작용해야하는 경우, 데이터베이스 기술이 변경될 가능성이 있는경우, MSA와 같이 다양한 서비스가 존재하고, 또 확장되면서 비즈니스 로직 복잡성이 높아지고 안정적인 테스트가 필요한 경우에 적합하다고 할 수 있다.

다음은 실제 Springboot 와 JPA를 사용해서 어떤식으로 이루어지는지 코드로 알아보려고 한다.

profile
개발 그렇게 하는거 아닌데의 그렇게를 맡고있습니다

0개의 댓글