본문 바로가기
Architecture

Clean Architecture (2)

by Heesu.lee 2021. 9. 13.

지난 Clean Architecture 1편에서는 아키텍처의 사용 이유와 여러 사례들을 통해 어떻게 의존성을 제어하며, 단일 책임 원칙을 지켜나갈 것인지 살펴 보았다.

 

강사분은 위와 같은 원칙들을 지키며 Clean Architecture 를 구현한 하나의 인스턴스로 Hexagonal Architecture 를 소개하였다.

해당 Hexagonal Architecture 가 꼭 Clean Architecture 의 정답이 아닌 하나의 예시라고 강조하였다.

 

이제부터 Hexagonal Architecture 이 어떻게 앞서 설명한 원칙들을 지키며 Clean Architecture 의 하나의 인스턴스가 되는지 살펴보도록 해보자

 

Hexagonal Architecture

Hexagonal Architecture Style

Hexagonal Architecture 의 아키텍처를 자세히 살펴보기 앞서 Clean Architecture 의 Circular Style 를 잠시 떠올려보자.

Clean Architecture 의 Circular Style

 

혹시 어떤 둘 간의 어떤 비슷한 점을 찾지 않았는가?

바로 도메인 계층 혹은 비즈니스 계층(빨간색) 이 아키텍처 가장 중심부에 있다는 점이다.

 

이를 통해 Hexagonal Architecture 이 현재 거시적 관점(Architecture Level)에서의 SRP 과 DIP 원칙을 잘 지키고 있음을 확인할 수 있다.

 

가장 중심부로 도메인 계층을 밀어넣어 버림으로써 그 이외의 외부 특정 기술과 디테일한 사항들이 도메인 계층에 대해 의존성을 가지며 도메인 계층은 특정 기술 혹은 디테일한 사항에 대해 의존성이 없는 상태인 것이다.

 

또한 광범위한 서비스(클래스) 사용이 아닌 여러 Use Case 들로 나누어 미시적 관점(Class Level)의 SRP 원칙 역시 잘 지켜지고 있음을 확인할 수 있다.

 

즉, Hexagonal Architecture 의 Outer Layer(외부 계층 - 영속, UI 등)의 모든 의존성들이 핵심 비즈니스 혹은 도메인 계층으로 향하며, 핵심 도메인 계층은 외부에 대한 어떠한 의존성도 가지고 있지 않은 순수한 Java 로 작성될 수 있어야 한다. (Java 프로젝트 기준) 이러한 도메인 계층은 Use Case 별 포트만 외부 기술 혹은 계층에게 제공하면 된다. 각 포트는 인터페이스로 작성된다.

 

이렇게 계층을 구성하게 되면 마틴이 그의 저서와 블로그에서 언급했던 Clean Architecture 에 가까워질 수 있다.

  • 프레임워크 독립적: 도메인 계층은 특정 프레임워크 혹인 라이브러리 존재 여부를 모르며 의존하지 않는다.
  • 테스트 용이: 도메인 계층은 외부 계층에 대한 어떠한 의존성이 없이 테스트가 가능하다. (Mocking 등)
  • UI 독립적: 시스템의 다른 부분을 고려하지 않고 UI 만을 변경할 수 있다.
  • Database 독립적: 비즈니스 혹은 도메인 계층은 데이터베이스에 얽매이지 않는다.
  • 외부 기능 독립적: 비즈니스 혹은 도메인 계층은 외부 어떤 기능에 대해 의존성을 갖지 않는다.

 

이제부터 Hexagonal Arcitecture 의 구성요소들에 대해 살펴보자

 

 

위 Hexagonal Architecture 의 왼쪽은 INPUT PORT 로 오른쪽은 OUTPUT PORT 가 존재한다.

 

INPUT PORT provides an API to outward layers
Let's the outward adapters call out application logic

INPUT PORT 는 외부 기능 혹은 계층에게 제공되는 API (Application Programming Interface) 이다.

이를 통해 외부 Adapter (WEB, CLI, RPC) 등은 해당 인터페이스를 통해 Application 비즈니스 로직를 호출 한다.

 

OUTPUT PORT provides functionality for example persistence
Let's our application core(domain) call somthing else(outward)

OUTPUT PORT 는 Application Core (비즈니스 or 도메인) 에서 외부 계층을 호출하는 인터페이스 혹은 기능이다.

해당 대표적인 예시로 영속성 (Persistence) 관리 등이 있다. (e.g. DB 저장, 변경, 조회 등)

 

반드시 위와 같이 육각형(Hexagon) 모양을 갖춰야 하는가?

정답은 아니다. 이는 필요에 의해서 자유롭게 변경될 수 있는 구조이다.

즉 필요 외부 계층에 대한 PORT 의 개수에 따라 Polygon(다각형) 이 결정되는 아키텍처 구조를 가지고 있는 것이다.

여담으로는 해당 아키텍처를 고안한 사람은 단순히 멋있어서 육각형을 사용했다고 한다. ㅎㅎ

 

PORT 가 위와 같이 정의가 되면 이제 도메인 계층 쪽을 한번 살펴보자. (빨간색)

우선 다시한번 이야기하지만, 강사분은 도메인 Entity 와 JPA Entity 를 서로 다르다고 이야기하고 있다.

JPA Entity 는 특정 기술을 종속하기 때문에 JPA 만의 변경 포인트가 발생될 수 있기 때문이다. (SRP, DIP 이슈)

따라서, 도메인 Entity 는 특정 기술로부터 의존하지 않는 순수 자바 객체로(POJO) 작성되어야 한다.

 

Broad Service should be cut into multiple USE CASEs that only have a single actor

도메인 계층의 USE CASE 는 반드시 하나의 행위 혹은 기능(사용자)만을 위해 작성되어야 한다.

이러한 원칙을 지킨 USE CASE 는 하나의 일급 객체(First-Citizen Class) 로 다루어질 수 있다.

 

그렇다면, USE CASE 는 무엇을 하는가?

 

A Use Case implements an INPUT PORT that is just a java interface

USE CASE 는 각각 정의된 INPUT PORT 를 구현하여 사용자에게 기능(Application Logic)을 제공해주면 된다.

그리고 USE CASE 는 이 과정에서 도메인 모델 혹은 엔티티를 수정할 수 있는 여러가지 방법을 제공해야 한다.

즉 다시 말해, 정의된 USE CASE 를 통해 도메인 엔티티를 조회, 수정, 생성하는 것이다. (OUT PORT 활용)

 

다소 USE CASE 가 무엇인지 헷갈릴 수 있기에 제가 이해한 부분을 적어봅니다.

USE CASE 는 기존 우리에게 익숙한 서비스 계층의 클래스 입니다. 단 대게 USE CASE 는 인터페이스로 작성되며, 이를 구현하는 Service 가 별도로 존재합니다. 해당 Service 는 USE CASE 를 구현하며 OUT PORT를 사용하여 해당 USE CASE (기능) 을 구현하게 됩니다.

 

이렇게 Service 를 사용 기능별로 USE CASE 로 나눈 사례를 보겠다.

Application without actual use cases

위 Application 은 어떤 일을 하는가? 혹은 어떤 기능을 사용자에게 제공하는가?

시간 기록? 시간? 무엇인가 시간을 기록한다는 것 이외에 어떠한 것도 알 수 없다.

 

split service up in use cases instead

위 Application 은 어떤 기능을 제공하는가?

시간을 조회하고, 승인, 저장, 거절 등 해당 Application 이 Time 에 대한 어떤 기능을 제공하는지 확실히 알 수 있다.

 

이번 게시글에서 Hexagonal Architecture 를 통해 도메인 중심적인 아키텍처에 대해 살펴보았다.(Domain Centric Architecture)

도메인이 가장 중심부에 있는 아키텍처는 SRP, DIP 원칙을 적용하여 보다 더 안정적인 코드를 작성할 수 있게 해준다.

  • 도메인 계층이 특정 기술에 의존하지 않도록 만든다.
  • 이를 통해, 오로지 사용자의 요구사항을 통해 코드는 변경된다.
  • 팀간 동시 작업에 있어 편의를 준다.

그렇지만, 이렇게 명확히 계층을 나누어 발생할 수 있는 문제점 역시 존재한다.

  • 계층 간 Mapping 이 많아진다.
  • 다소 많은 코드 중복이 생길 수 있다.
  • 정말 진정한 비즈니스 로직을 작성해야 한다.

여러 계층이 정의되며 이러한 계층에선 명확히 다른 Entity 혹은 DTO 를 필요로 하기에 각 계층별 매핑 로직이 반드시 필요한다.

기존 Service 를 USE CASE 로 쪼개게 된다면 여러 기능에서 쓰일 수 있는 공통 로직은 중복될 수 있다.

제가 이해한 마지막 내용은 도메인 모델 혹은 엔티티를 재대로 설계하여 각 모델이 어떠한 기능을 수행해야한다고 이해하였습니다.

즉, 도메인 모델 혹은 엔티티가 자기 자신에 대한 책임을 제공해야하는 것 입니다.

 

결론

이번 시간엔 지난 시간의 핵심 요소를 구현한 하나의 INSTANCE 를 살펴보았다. (Hexagonal Architecture)

가독성, 안정성 여러 측면에서 위 아키텍처는 개발자로 하여금 지속 가능한 개발을 할 수 있도록 해준다.

 

다만, 위 아키텍처 혹은 도메인 중심적인 아키텍쳐가 반드시 옳고 정답이다라고 생각하진 않는다.

결국 아키텍쳐의 존재 이유는 보다 앞서 강사분이 언급하였 듯이 효율적인 개발을 하는데 있다고 생각한다.

 

어떤 아키텍처인지 팀간의 원활한 소통을 통해 필요에 맞게 계층을 정의하고 만들어 사용하는 것이 가장 훌륭한 아키텍쳐가 아닐까 싶다.

 

참고

  • Clean Architecture with Spring by Tom Hombergs @Spring I/O 2019 - 링크

 

추가로 해당 강사분이 Clean Architecture 를 구현한 예제 레포지토리를 함께 공유 드린다.

현재는 강의 내용과 많이 다른 부분도 있어 함께 살펴보면 도움이 되실 것이라 생각합니다.

https://github.com/thombergs/buckpal

 

GitHub - thombergs/buckpal: An example approach for implementing a Clean/Hexagonal Architecture

An example approach for implementing a Clean/Hexagonal Architecture - GitHub - thombergs/buckpal: An example approach for implementing a Clean/Hexagonal Architecture

github.com

 

※ 주니어 개발자이기에 부족한 점이 많습니다. 틀린 부분이나 부족한 부분에 대해 자유롭게 글 남겨주시면 감사하겠습니다.

'Architecture' 카테고리의 다른 글

Clean Architecture (1)  (2) 2021.08.28

댓글