[만들면서 배우는 헥사고날 아키텍처 설계와 구현] 1-4 외부와 상호작용하는 어댑터 만들기
[만들면서 배우는 헥사고날 아키텍처 설계와 구현] 1-4 외부와 상호작용하는 어댑터 만들기

[만들면서 배우는 헥사고날 아키텍처 설계와 구현] 1-4 외부와 상호작용하는 어댑터 만들기

Tags
다비비에이라
HexagonalArchitecture
Published
April 8, 2024
Author
lkdcode

외부와 상호작용하는 어댑터 만들기

소프트웨어 개발 중에는 시스템에서 지원해야 하는 기술을 결정해야 하는 순간이 있다. 기술적인 선택이 헥사고날 애플리케이션을 개발하는 주된 드라이버가 되어서는 안 된다. 실제로 헥사고날 아키텍처를 기반으로 하는 애플리케이션은 높은 수준의 변경 가능성을 갖고 있음을 의미하며, 최소한의 마찰을 통해 다양한 기술과 통합할 수 있다. 이것은 헥사고날 아키텍처 가 코드의 어느 부분이 비즈니스와 관련 있고, 어느 부분이 기술과 관련 있는지를 명확히 하기 때문이다.

어댑터 이해

GoF 디자인 패턴의 어댑터와 역할은 서로 다를 수 있지만 두 방법 모두 무언가를 다른 것에 적절하게 맞추도록 조정하는 같은 목적을 공유한다. 애플리케이션 기능을 노출하는 데 사용되는 어댑터를 입력 어댑터 라고 부른다. 사용자와 다른 시스템이 애플리케이션과 상호작용할 수 있도록 입력 어댑터를 정의할 수 있다. 같은 방법으로 헥사고날 애플리케이션에 생성된 데이터를 변환하고 외부 시스템과 통신하기 위한 출력 어댑터도 정의할 수 있다.

드라이빙 오퍼레이션 허용을 위한 입력 어댑터 사용

'변하지 않는 사실은 모든 것은 변한다는 사실뿐이다.' 비즈니스 규칙과 기술적 세부사항이 연결되도록 시스템이 설계된 경우 새로운 기술의 통합은 상당한 양의 리팩터링 없이는 쉽지 않다. 헥사고날 아키텍처에서 입력 어댑터는 소프트웨어를 다른 기술들과 호환되게 만드는 요소다. 헥사곤 외부에는 헥사곤 애플리케이션과 상호작용하는 사용자나 시스템이 있을 수 있다. 이러한 사용자나 시스템을 가리켜 애플리케이션 유스케이스를 형성하는 중추적인 역할을 하는 주요 액터라고 한다. 주요 액터와 헥사고날 애플리케이션 사이의 상호작용은 입력 어댑터를 통해 일어난다. 이러한 상호작용은 드라이빙 오퍼레이션으로 정의된다. 주요 액터가 이들을 드라이브하고, 헥사고날 시스템의 상태와 행위를 시작하게 하고 영향을 준다는 의미에서 드라이빙으로 표현한다.
 
DDD 기반 아키텍처의 일반적인 관심사는 레거시 시스템의 요소를 새로운 시스템에 통합하는 것이다. 이것은 도메인 모델에서 관련 지식이 모인 레거시 시스템이 몇 가지 중요한 문제를 해결하지만 설계 불일치를 보여주는 상황에서 발생한다. 레거시 시스템을 포기하고 싶지 않지만 새로운 시스템 설계가 레거시 시스템의 설계에 영향을 주는 것도 원하지 않을 것이다. 반부패 계층 은 레거시 시스템과 새로운 시스템 모두에서 경계 컨텍스트를 통합하는 데 사용되는 어댑터를 기반으로 한다. 주요 액터와 헥사고날 애플리케이션 사이의 연결이 입력 어댑터를 통해 발생한다.

입력 어댑터 생성

입력 포트 객체는 입력 어댑터가 보낸 자극을 통해 오퍼레이션을 수행하는 데 필요한 모든 데이터를 수신한다. 그러나 입력 데이터를 도메인 헥사곤과 호환되는 형식으로 변환하기 위해 이 단계에서 최종적인 변환이 발생할 수 있다.

기반 어댑터

기반 어댑터를 추상 클래스 혹은 인터페이스로 작성한다. 이 기반 어댑터는 어댑터와 관련된 입력포트와 통신을 위한 표준 오퍼레이션을 제공한다.(REST API, STDIN, gRPC 등)
  • 어댑터는 입력 포트를 직접 참조하지 않는다. 유스케이스 인터페이스 참조를 활용한다.
RouterNetworkOutputPort outputPort = RouterNetworkH2Adpater.getInstance(); RouterNetworkUseCase usecase = new RouterNetworkInputPort(outputPort); RouterNetworkAdapter inputAdpater = new RouterNetworkRestAdapter(usecase);
Rest 입력 어댑터가 결국 H2 인-메모리 데이터베이스 출력 어댑터를 필요로 한다는 것을 나타낸다.
RouterNetworkOutputPort outputPort = RouterNetworkFileAdpater.getInstance(); RouterNetworkUseCase usecase = new RouterNetworkInputPort(outputPort); RouterNetworkAdapter inputAdpater = new RouterNetworkCLIAdapter(usecase);
CLI 입력 어댑터가 결국 File 출력 어댑터가 필요로 한다는 것을 나타낸다. 이처럼 다양한 '정문'을 통해 애플리케이션 내부에 진입할 수 있는 통로를 생성할 수 있다. 입력 어댑터는 유스케이스 인터페이스 참조를 통해 입력 포트를 호출한다. 입력 어댑터는 헥사고날 애플리케이션이 제공하는 모든 기능에 액세스할 수 있는 정문에 해당한다. 입력 어댑터를 통해 비즈니스 로직을 방해하지 않으면서 다양한 기술을 통해 시스템에 쉽게 액세스할 수 있다.

다양한 데이터 소스와 통신하기 위한 출력 어댑터 사용

객체지향 시스템의 특징은 데이터와 동작을 밀접하게 관련된 것으로 다루는 능력에 있다. 데이터와 동작은 분리된 것으로 다뤄서는 안 되지만, 객체 내부에서 통합돼야 한다는 기본 원칙을 표현한다. 이러한 객체의 개념은 지난 수십 년에 걸쳐 방대하고 복잡한 시스템 개발의 기반을 마련했다. 이러한 시스템의 좋은 예는 엔터프라이즈 환경에서 실행되는 비즈니스 애플리케이션이다. 절차적 패러다임은 엔터프라이즈 요구사항에 비해 번거롭고 너무 낮은 수준이었다. 객체지향 언어 외에도 엔터프라이즈 소프트웨어는 데이터를 획득하고 유지하는 방법에 의존한다. 문제는 이러한 요구가 전체 소프트웨어 구조에 영향을 주고 지시하는 것이다.

출력 어댑터 생성

출력 어댑터는 입력 어댑터와 함께 프레임워크 헥사곤을 구성하는 두 번째 컴포넌트다. 헥사고날 아키텍처에서 출력 어댑터의 역할은 드리븐 오퍼레이션을 처리하는 것이다. 드리븐 오퍼레이션은 일부 데이터를 보내거나 받기 위해 외부 시스템과 상호작용하는 헥사고날 애플리케이션 자체에 의해 시작된 오퍼레이션이다. 이러한 드리븐 오퍼레이션은 유스케이스를 통해 서술되며, 유스케이스의 입력 포트 구현에 있는 오퍼레이션에 의해 트리거된다. 유스케이스에서 외부 시스템에 있는 데이터를 처리할 필요성이 언급될 수 있다. 이것은 유스케이스에서 외부 시스템에 있는 데이터를 처리해야 하는 필요성을 이야기할 때마다 헥사고날 애플리케이션이 이러한 요구사항을 만족하기 위해 적어도 하나 이상의 출력 어댑터와 출력 포트가 필요하다는 것을 의미한다. 도메인 헥사곤에서 만든 도메인 모델이 표현하는 요구사항만을 기반으로 하는 데이터에 대해 이야기했다. 결국 전체 헥사고날 시스템의 형태를 유도하는 것은 도메인 헥사곤의 도메인 모델이다. 결과적으로 애플리케이션 헥사곤은 도메인 헥사곤의 도메인 모델에 의존해야 한다.

adapter-output 구현하기

영속화를 위해 데이터베이스와 연결하는 계층이다. 기본키를 가지고 있지만 도메인 모델의 기술적인 배치를 부과하는 것이지 그 반대는 아니다. 테이블의 기본키를 도메인 모델에서는 참조로 사용하지 않는다. 도메인 엔티티를 데이터베이스에 바로 매핑할 수 없다.또한 데이터베이스 엔티티를 도메인 엔티티로 사용할 수도 없다. 이를 해결하기 위해 맵퍼 클래스를 사용한다. 도메인 모델이 우선이므로 시스템과 데이터베이스 기술의 결합을 원치 않는다는 것이 중요하다.

요약

  • 드라이빙 오퍼레이션을 허용하기 위해 두 개의 입력 어댑터를 구현한다.
  • 도메인 헥사곤을 수정할 필요없이 변경에 강한 시스템을 중심에 둘 수 있다.
  • 두 어댑터 모두 같은 입력 포트에 연결되어 헥사고날 시스템이 서로 다른 형식으로 들어오는 요청의 처리에 같은 로직을 사용하게 된다.
  • 헥사고날 애플리케이션이 다양한 데이터 소스와 통신할 수 있도록 H2 db, JSON file 등
  • 출력 어댑터를 통해 비즈니스 로직에 영향을 미치지 않고 구현할 수 있다.