Post
EN

자바 스프링과 헥사고날 아키텍처

오랜 시간동안 헥사고날 아키텍처는 주변에서 들려왔다. 이 주제의 기본 소스가 한동안 오프라인 상태였으며 최근에 아카이브에서 구조 된 것만으로 충분합니다.

나는 실제 애플리케이션을 헥사고날 아키텍처 스타일로 적은 리소스를 들여 구현하는 것을 발견했습니다.

이 글의 목표는 자바 스프링을 이용한 핵사고날 아키텍처 형태의 웹 애플리케이션 구현 방법의 의견을 제시하는 것 입니다.

만약 주제가 너무 깊다면 나의 책을 살펴보세요.

book

코드 예제

GitHub

헥사고날 아키텍처란 무엇인가? (what is ‘hexagonal architecture’?)

헥사고날 아키텍처의 핵심은 기존 계층화된 아키텍처 스타일과 반대로, 컴포넌트 사이에 의존성을 도메인 객체 내부를 가리키는 것 입니다.

존재하지 않는 이미지입니다.

헥사고날 아키텍처는 코어 애플리케이션의 도메인 객체 구성을 묘사하는 매력적인 방법입니다. 유즈 케이스는 그 위에서 동작하며, 입력과 출력 포트는 인터페이스로 바깥 세계로 제공 됩니다.

이 아키텍처 스타일의 부분들을 살펴보겠습니다.

도메인 객체 (domain object)

비즈니스 규칙이 풍부한 도메인에서 도메인 객체는 애플리케이션의 핵심 입니다.

도메인 객체들은 행동과 상태를 포함할 수 있습니다. 동작이 상태에 가까울 수록 코드 이해, 추론 및 유지 관리가 쉬워 집니다.

도메인 객체는 외부 의존성을 가지고 있지 않습니다. 그것은 기본 자바에서 유즈 케이스에서 작동하기 위한 API를 제공합니다.

그러므로 도메인 객체는 애플리케이션의 다른 계층과 의존성이 없고, 다른 계층에서 변경이 생겨도 영향을 받지 않습니다. 이러한 것들로 의존성 없이 진화 할 수 있습니다. 이것은 단일 책임 원칙(“SOLID”의 “S”)을 나타내는 주요 예이며 구성요소는 변경해야 할 이유는 하나만 있어야 합니다.

도메인 객체의 경우, 비즈니스 요구사항이 변경되었다는 것과 같습니다.

단일 책임을 가진다는 것은 외부적인 의존성과 무관하게 도메인 객체를 진화 시킵니다.

이런 진화적인 헥사고날 아키텍처 스타일은 도메인 주도 개발을 연습할때 완벽하게 만듭니다.

개발하는 동안 자연스러운 종속성 흐름을 따릅니다. 도메인 객체에서 코딩을 시작하여 바깥쪽으로 이동합니다. 그것이 도메인 중심이 아니라면, 그것이 무엇인지 모릅니다.

유즈 케이스 (use case)

우리는 유저가 우리 소프트웨어로 하는 것들을 추상적으로 알고 있다. 헥사고날 아키텍처 스타일에서 우리 코드에서 일급객체의 형태로 유즈 케이스들을 승격시키는 것을 만들어줍니다.

이러한 의미에서 유스 케이스는 특정 유스 케이스를 중심으로 모든 것을 처리하는 클래스입니다.

‘뱅킹 애플리케이션에서 다른 계좌로 돈을 보낸다’는 유즈케이스 예를 생각해봅시다.

우리는 SendMoneyUseCase라는 클래스를 생성할 것이고, 사용자가 돈을 송금하는 것을 허락하는 API를 가지고 있습니다. 코드에는 사용 사례와 관련된 모든 비즈니스 규칙 유효성 검사 및 논리가 포함되어 있으므로 도메인 개체 내에서 구현할 수 없습니다. 모든 것들은 도메인 객체에 위임되어 집니다. (아마 여기서는 Acoount가 도메인 오프젝트 일 것이다.)

유사한 도메인 객체들, 하나의 유즈 케이스 클래스는 외부의 구성요소들과 의존성이 없습니다. 외부의 어떤 것이 필요할 때, 우리는 outpout port를 생성합니다.

입력과 출력 포트들 (Input and Ouput Ports)

헥사고날 안에 있는 도메인 객체와 유즈케이스들은 애플리케이션의 코어입니다.

모든 외부로부터의 의사소통들은 “포트”라는 것을 들을 통해서 합니다.

하나의 입력 포트는 외부 구성요소들을 호출 될 수 있고, 유즈 케이스에 의해 구현되는 간단한 인터페이스 입니다.

구성요소들을 호출하는 것을 입력 포트는 입력 어댑터 또는 “드라이빙” 어댑터로 불립니다.

하나의 출력 포트는 유즈 케이스에서 호출 할 수 있는 간단한 인터페이스 입니다. (데이터베이스 접근 하는 등)

이 인터페이스는 사용 사례의 요구에 맞도록 설계되었지만 출력 또는 “구동”어댑터라고하는 외부 구성 요소에 의해 구현됩니다.

이것은 친숙한 의존의 역전 법칙 입니다. (‘SOLID’ 의 ‘D’), 유즈케이스에서 출력 어뎁터는 인터페이스를 사용하는 것으로 의존성을 역전시키기 때문입니다.

입력 및 출력 포트가 있으면 데이터가 시스템에 들어오고 나가는 위치가 매우 뚜렷하여 아키텍처에 대한 추론을 쉽게 할 수 있습니다.

어뎁터 (Adapters)

어뎁터는 헥사고날 아키텍처의 외부 계층 입니다. 어뎁터는 핵심적인 부분이 아닙니다 그러나 연관되어 있습니다.

입력 어뎁터 또는 “driving” 어뎁터는 입력 포트를 호출하고 어떤 것을 합니다. 입력 어뎁터는 웹 인터페이스를 할 수 있습니다. 브라우저에서 사용자가 버튼을 클릭할 때, 웹 어뎁터는 확실한 입력 포트에서 대응되는 유즈 케이스를 호출 합니다.

출력 어뎁터 또는, “driven” 어뎁터는 이런 유즈케이스에서 호출되어 집니다. 아마도 데이터페이지로 부터 데이터를 제공해주는 형태로.

하나의 출력 어뎁터는 출력 포트 인터페이스의 집합으로 구현되어 있습니다.

인터페이스는 다른 방법이 아니라 사용 사례에 의해 결정됩니다.

어댑터를 사용하면 특정 응용 프로그램 계층을 쉽게 교환 할 수 있습니다. 응용 프로그램을 팻 클라이언트에서 웹에 추가로 사용할 수 있어야하는 경우 팻 클라이언트 입력 어댑터를 추가합니다. 응용 프로그램에 다른 데이터베이스가 필요한 경우 이전과 동일한 출력 포트 인터페이스를 구현하는 새로운 지속성 어댑터를 추가합니다.

그 밖의 코드 베이스 설명은 생략한다.

그리고 발로한 것 같은 번역에 주의한다 -_-;

예제 코드는 추후 올리도록 하겠음

출처 : https://reflectoring.io/spring-hexagonal/


DDD를 공부하면서 객체지향프로그래밍 기초 부분에 많이 연결 되는 점이 많다고 생각하였다.

그렇기 때문에 기존에 계층화된 아키텍처에서 벗어 나야겠다고 생각했지만, 이쁘게 나눠지지는 않았던 것 같다.

이러한 해법을 줄 수 있는 것이 헥사고날 아키텍처 스타일인 것 같다.

약간 동작별 나누는 것에 대해 이게 맞나 싶을 정도로 고민 했던 적이 있었는데, 이러한 것들은 헥사고날에서는

유즈케이스 형태로 나누었고, 그에 따른 구현체들을 만들고 있었다.

1가지 유즈 케이스에는 입력과 출력이 존재하고, 또는 입력만 존재할 수 있다.

그 밖의 다른 유즈케이스와는 중첩되지 않는 형태이기 때문에, 1가지 책임만 갖는 형태로 존재할 수 있다.

따라서, 유즈 케이스에 수정할 부분이 생기면 1가지만 수정이 가능해질 수 있다.

입력과 출력은 인터페이스 형태로 추상화 되어 있고, 그렇기 때문에 결합도는 낮아진다. (참조 되는 객체들과 관계는 독립적이다.) 이런 특징은 객체지향 5원칙의 의존성 역전의 법칙의 장점을 갖을 수 있게 해준다.

DDD를 공부하면서 Domain은 클래스 일 것이다 라는 것이 나의 잘못된 생각이라는 것을 이제 깨달았다.

만드는 애플리케이션이 다루는 것이 Domain이고 그 안에 구현되는 것들은 이러한 형태로 나뉘면 되었을 것을

이제라도 알게되서 다행인거 같다.

// 2023. 07. 03

https://mesh.dev/20210910-dev-notes-007-hexagonal-architecture/

[**헥사고날(Hexagonal) 아키텍처 in 메쉬코리아 :: MESH KOREA VROONG 테크 블로그**](https://mesh.dev/20210910-dev-notes-007-hexagonal-architecture/)

This article is licensed under CC BY 4.0 by the author.