헥사고날(hexagonal) 아키텍처

hailey·2024년 9월 19일

면접질문

목록 보기
1/6

업로드중..

헥사고날 아키텍처란?

  • "포트와 어댑터 아키텍처" 라고도 한다.
  • 이 아키텍처의 주요 목표는 "어플리케이션의 비즈니스 로직을 외부 세계로부터 분리시켜 유연하고 테스트하기 쉬운 구조를 만들자" 다.
  • 이를 위해 핵심 비즈니스 로직은 중앙의 도메인 영역에 위치하며, 입력과 출력을 처리하는 포트와 어댑터를 통해 외부와 소통한다.
  • 내부 → 도메인, 외부 → 인프라
    • 내부 : 순수한 비즈니스 로직을 표현, 캡슐화된 영역, 기능적 요구사항에 따라 먼저 설계
    • 외부 : 내부 영역에서 기술을 분리해서 구성한 영역, 내부 영역 설계 이후 설계

왜 헥사고날 아키텍처를 쓰는가?

✅ 관심사의 분리

  • 외부와의 연결에 문제가 생기면? → 어댑터 보면 됨
  • 인터페이스는? → 포트 보면 됨
  • 처리 중간에 이벤트 브릿지에 이벤트를 보내고 싶다면? → 서비스 보면 됨
  • 비즈니스 로직이 제대로 동작하지 않으면? → 도메인 모델 보면 됨

✅ 좀 더 쉬운 테스트

  • 인바운드 어댑터는 서비스 내부 구현을 Mocking 할 필요 없이, 외부에서 들어온 데이터가 잘 가공되어 서비스에 전달되는지만 확인한다.
  • 서비스는 어댑터나 도메인 객체가 정상적으로 동작하는지 확인할 필요 없이, 순서대로 오케스트레이션 잘 되는지만 확인한다. 이 때 모든 어댑터는 포트를 기반으로 하기 때문에, 쉽게 FakeAdapter 를 작성해서 서비스를 테스트 해볼 수 있다.
  • 아웃바운드 어댑터는 kafka, redis 등 외부 서비스를 mocking 해서 파라미터값이 의도대로 전달되는지를 확인한다.
  • 도메인 로직은 모든 비즈니스 로직에 대한 테스트를 작성하게 되지만, 의존성이 없기 때문에 mocking 할 필요가 거의 없다. (있더라도 포트를 통해 아웃바운드 어댑터를 쉽게 모킹할 수 있다)
  • 마지막으로 위 모든 과정에서 포트는 검증이 되므로, 포트는 따로 테스트할 필요 없다.

데이터 흐름

  • 사용자의 요청은 왼쪽에서 → 오른쪽으로 처리된다.
  • 좌측 어댑터를 통해 사용자의 요청을 받아서 어플리케이션 서비스에 전달한다.
  • 서비스와 어댑터는 포트를 인터페이스로 사용하여 통신한다. (어댑터 디자인 패턴 느낌으로)
  • 어플리케이션 서비스는 요청을 도메인 모델로 전달하고, 도메인 모델은 비즈니스 요청을 처리한 후 우측 어댑터를 통해 외부의 데이터를 가져오거나 처리된 데이터를 외부로 저장한다.
  • 필요한 경우 어플리케이션 서비스는 도메인 모델의 처리결과를 전달받아 다시 사용자에게 반환한다.

포트와 어댑터

포트

  • 포트는 단순히 인터페이스다. 이 포트를 구현하여 실제 동작을 구현하게 된다.
  • 어플리케이션 입장에서 consumer 입니다.
  • 어플리케이션에서 나가거나 들어오는 endpoint 라고 볼 수 있다.
  • 포트는 내부 비즈니스 영역을 외부 영역에 노출한 API 이고,
    인바운드 포트, 아웃바운드 포트로 구분한다.
    - 인바운드 포트 : 내부 영역 사용을 위해 노출된 API
    - 아웃바운드 포트 : 내부 영역이 외부 영역을 사용하기 위한 API

어댑터

왼쪽의 어댑터는 사용자의 요청을 받아들일 때 사용, 오른쪽의 어댑터는 도메인 모델의 처리에 사용

  • Primary 어댑터 (인바운드 어댑터)
    - 그림 상 육각형의 왼쪽에 위치하는 어댑터.

    • 어플리케이션을 동작시키는 역할을 한다. 인바운드 포트를 사용하고 주로 UI 쪽 관련된 작업이 들어간다.
    • 이벤트 드리븐 서비스인 경우 Primary 어댑터의 역할이 명확하지만, 웹 서비스의 경우에는 약간 모호해지는 느낌이 있다. 웹 프레임워크들의 컨트롤러..
    • 포트에 의존하고 있다.
    • 실제로 사용될 때는 포트의 구현체가 어댑터에 주입된다.
    • 포트와 포트 구현체는 어플리케이션 내부에 속한다.
  • Secondary 어댑터 (아웃바운드 어댑터)
    - 어플리케이션에 의해 동작되는 역할을 가진 부분이다.

    • 아웃바운드 포트를 사용하고 주로 인프라와 연결되는 부분이 들어간다.
    • 포트의 구현체가 되고 비즈니스 로직에 주입된다.
    • 어플리케이션의 비즈니스 로직 입장에서는 포트의 인터페이스만 알고 있다.
    • 포트는 어플리케이션 내부에 속하지만, 포트 구현체는 외부에 속하게 되고 구현체는 외부 tool (SMS라이브러리, ORM 등등..)을 감싸고 있는 구조가 된다.

어플리케이션 서비스 / 유스케이스

  • 어플리케이션 서비스는 어댑터들과 도메인모델의 오케스트레이션을 담당하게 된다.

헥사고날 아키텍처의 장점

  1. 도메인 비즈니스 모델에 집중할 수 있다.
    : DIP를 통해 의존성이 도메인에서 밖으로 나가는 부분이 없어서 외부 요소를 신경쓰며 개발 할 필요가 없기 때문이다.

  2. 모듈 일부를 배포하는게 용이하다.
    : 기술과 실제 비즈니스 로직을 분리하고, 각 도메인 별 비즈니스 로직을 분리를 해서 느슨한 결합을 가져갈 수 있기 때문이다.

  3. 기능 확장이 용이하다.
    : 원하는 기능에 대한 포트와 해당 포트를 사용할 어댑터를 추가해주면 된다.

  4. 쉬운 테스트 구성
    : 모든 외부 기술들은 포트를 통해 비즈니스 로직과 연결되기 때문에, 본인의 역할을 수행하기 위해 필요한 포트만 사용해서 mock 어댑터를 통해 테스트를 쉽게 수행할 수 있다.
    또한 내부 비즈니스 로직 테스트할 때 외부에 의존성이 없기 때문에 모킹 필요성도 적어진다.

  5. 개발 비용 감소
    : 모든 의존성이 도메인을 향하기 때문에 계층 간 의존성이 낮아지고 유연해진다. 때문에 요구사항에 빠르게 대처할 수 있고, 테스트도 쉽게 적용할 수 있다.

  6. 관심사 분리의 장점 (SoC)
    : 외부와의 연결에 문제가 생기면 어댑터를 확인하면 될 것이고,
    인터페이스의 정의를 변경하고자 한다면 포트를,
    비즈니스 로직이 제대로 동작하지 않는다면 도메인 로직만 확인하면
    되기 때문에 책임이 완벽하게 분리되어 있어 코드의 이해와 수정이 용이하고 변화에 빠르게 대응할 수 있으며 쉬운 테스트도 가능하게 한다.

헥사고날 아키텍처의 단점

  1. 코드가 많아진다. (구현 복잡성)
    : 도메인 계층이 영속성, UI 같은 외부계층과 철저히 분리되어야 하기 때문에 엔티티에 대한 모델을 각 계층에서 유지보수 해야 한다.
    ex) 도메인 계층은 영속성 계층을 모르기 때문에, 두 계층에서 각각 엔티티를 만들어주어야 하고, 도메인 계층과 영속성 계층 간 데이터를 주고받을 때 두 엔티티를 서로 매핑하는 과정이 생기게 된다.

  2. 불필요한 오버헤드. (초반 개발 시간의 증가)
    아키텍처를 도입하기 전 포트, 어댑터 등 알아야하는 개념이 생기고,
    아키텍처를 구현하기 위해 포트(인터페이스)를 생성해야 하고 도메인 모델의 여러 표현 사이를 매핑할 객체를 만들어야 한다.

3계층 아키텍처와의 차이점

  • 가장 대중적으로 사용하는 3계층 아키텍처는 비즈니스 로직, 데이터 액세스, 프레젠테이션 계층으로 구성된다.
  • 이와 달리 헥사고날 아키텍처는 비즈니스 로직에 중점을 두고 외부와 격리되어 있어, 더욱 유연하고 테스트하기 쉽다.

예시 1

3계층 아키텍처

  • UserService : 비즈니스 로직
  • UserRepository : 인터페이스를 통해 데이터 액세스 계층과 소통

헥사고날 아키텍처

  • CreateUserUseCase : 인터페이스를 통해 비즈니스 로직을 정의
  • CreateUserUseCaseImpl : 클래스에서 구현
  • UserRepositoryAdapter : UserRepository 인터페이스를 구현하며, 외부와의 소통을 담당

예시 2

주로 카프카 같은 외부 시스템을 연결하는 예제를 통해 헥사고날 아키텍처의 장점을 알 수 있다.

3계층 아키텍처 with Kafka 적용

  • KafkaProducer : 카프카에 사용자 정보 전송 로직
  • UserService : 카프카와 직접 연결되어 있어, 카프카에 대한 의존성을 가지게 된다.

헥사고날 아키텍처 with Kafka 적용

  • OutputPort : 이 인터페이스를 통해 외부 시스템과의 의존성을 분리한다.
  • KafkaAdapter : 이 클래스에서 카프카와 연결하는 로직을 처리한다. 내부에 KafkaProducer 를 인자로 가지고 있다.
  • CreateUserUseCaseImpl : OutputPort 인터페이스를 통해 메세지를 전송한다.

→ 만약 KafkaProducer를 카프카 말고 다른 메세징 시스템인 RabbitMQ로 교체하려면 3계층 아키텍처에서는 UserService 를 수정해야 한다. 헥사고날 아키텍처에서는 OutputPort 인터페이스를 구현한 새로운 어댑터를 만들어서 주입하면 된다.

결론

→ 헥사고날 아키텍처는 비즈니스 로직과 외부 요소를 격리시키기 때문에 유연성과 테스트 용이성이 향상되는 장점이 있다.

profile
Fail Fast, Fail Often

0개의 댓글