저자의 SOLID 원칙의 정확한 의미
책에서는 초반에 SOLID 원칙 중 2개를 먼저 애기하고 정확한 의미는 이렇다하고 살짝 수정을 해야하겠다고 말을 한다.
나는 이 부분에 굉장히 동의를 했다.
단일 책임 원칙
하나의 컴포넌트는 오로지 한 가지 일만 해야 하고, 그것을 올바르게 수행해야 한다.
컴포넌트를 변경하는 이유는 오직 하나뿐이어야 한다.
- 책임 -> 변경할 이유
- 단일 책임 원칙 -> 단일 변경 이유 원칙(Single Reason to Change Principal)
의존성 역전 원칙
도메인 코드는 애플리케이션에서 가장 중요한 코드다. 따라서 의존성을 역전시켜 의존성으로부터 보호(격리)해야 한다.
코드상의 어떤 의존성이든 그 방향을 바꿀 수(역전시킬 수) 있다.
- 사실 의존성의 양쪽 코드를 모두 제어할 수 있을 때만 의존성을 역전시킬 수 있다.
클린 아키텍처에서는 설계가 비즈니스 규칙의 테스트를 용이하게 하고, 비즈니스 규칙은 프레임워크, 데이터베이스, UI 기술, 그 밖의 외부 애플리케이션이나 인터페이스로부터 독립적일 수 있다.
- 도메인 코드에는 바깥으로 향하는 어떤 의존성도 없어야 한다. 클린 아키텍처에서 모든 의존성은 도메인 로직을 향해 안쪽 방향으로 향해야 한다.
처음에는 굉장히 어렵지만 책임과 역할을 정확히 바라보고 나서야 그 위의 이미들이 이해가 가기 시작했다.
헥사고날 아키텍처
'헥사고날 아키텍처'는 알리스테어 콕번이 만든 용어로, 로버트 C. 마틴이 클린 아키텍처에서 좀 더 일반적인 용어로 설명한 것과 동일한 원칙을 적용한다.
출처
Hexagonal Architecture with Java and Spring (reflectoring.io)
Hexagonal Architecture with Java and Spring
The term 'Hexagonal Architecture' has been around for a long time. But would you know how to implement this architecture style in actual code? This article provides such a way.
reflectoring.io
애플리케이션 코어가 육각형으로 표현되어 이 아키텍처의 이름이 됐다. '헥사고날(육각형) 아키텍처' 라고 지어진 이유는 애플리케이션이 다른 시스템이나 어댑터와 연결되는 4개 이상의 면을 가질 수 있음을 보여주기 위해서 사각형 대신 헥사고날(육각형)을 사용했다고 한다.
육각형 안에는 도메인 엔티티, 이와 상호작용하는 유스케이스가 있다. 육각형에서 외부로 향하는 의존성이 없기 떄문에 로버트 C. 마틴이 클린 아키텍처에서 제시한 의존성 규칙이 그대로 적용되었다. 대신 모든 의존성이 코어(도메인 계층 과 애플리케이션 계층)로 향한다.
입력포트를 통해 코어에 의존성이 있다면 애플리케이션을 주도하는 어댑터이고 출력포트를 통해 코어에 의존성이 있다면 애플리케이션이 주도하는 어댑터이다.
이런 아키텍처 스타일은 포트와 어댑터 아키텍처 라고 불린다. 가장 바깥쪽에 있는 계층은 애플리케이션과 다른 시스템 간의 번역을 담당하는 어댑터로 구성돼 있다. 다음으로 포트와 유스케이스 구현체를 결합해서 애플리케이션 계층을 구성할 수 있는데, 이 두가지가 애플리케이션의 인터페이스를 정의하기 떄문이다. 마지막 계층에는 도메인 엔티티가 위치한다.
정말 쉽게 대충 애기하자면?
HTTP 요청이 온다면 IN 어뎁터 라는 애가 IN 포트 를 호출 하고 구현체인 유스케이스 에서 핵심 비즈니스 로직이 존재하고 도메인에 대한 의존성이 있으며 도메인은 어떠한 의존성도 없는 존재 이며 유스케이스 만 도메인을 아는상태이다. 그 후 OUT PORT 를 호출(위임) 을 해서 영속성 어뎁터나 의부 의존성 어뎁터를 사용을 해서 데이터를 가져와 반환하는 흐름이다.
핵심은 도메인은 아무 의존성이 없고 본인 혼자만 알고 있고 외부에 열어놓아 유스 케이스에서만 사용을 하게 만드는 것이다.
경계간 매핑
이 책에서 굉장히 중요한 내용 중 하나인 경계깐 매핑 전략에 대해서도 애기를 해준다.
이 매핑 전략자체가 굉장히 경계간 의존성을 관리하기에 핵심인것 같다라고 느껴진다.
매핑하지 않기
웹 계층, 애플리케이션 계층 모두 Account 클래스에 접근하고 있다.
웹 계층에서는 Rest로 모델을 노출시킨다면, 직렬화 관련 애너테이션을 붙일 수 있다. 영속성 계층은 DB매핑을 위한 특정 애너테이션이 필요할 것이다.
각 관심사 분리를 위해 계층을 나눴지만, Account 도메인 모델은 모든 관심사를 포함하고 있어야 한다. 즉, 단일 책임 원칙을 위반하게 된다.
모든 계층이 정확히 같은 구조라면 고려해볼만하지만, 구조는 언제든 변경될 수 있기에 추천하진 않는다.
양방향 매핑 전략
각 계층은 도메인 모델과 다른 구조의 전용 모델을 가지도록 한다. 웹 계층에서 인커밍 포트(SendMoneyUseCase)에 필요한 도메인 모델로 매핑하고, 인커밍 포트에 의해 반환된 도메인 객체를 다시 웹 모델로 매핑한다.
하지만 아래와 같은 단점이 존재한다.
- 보일러플레이트 코드가 많아짐
- 도메인 모델이 계층 경계를 넘어서 통신하는데 사용됨
인커밍 포트와 아웃고인 포트는 도메인 객체를 입력 파라미터와 반환값으로 사용하므로 도메인 모델의 필요에 의해서 변경되지 않고 바깥쪽 계층의 요구에 따른 변경에 취약해진다.
완전 매핑 전략
각 연산마다 별도의 입출력 모델을 사용하는 것이다. 통신할 때 또한 특화된 모델을 사용한다. (command, Request)
- 웹 계층: 애플리케이션 계층의 커맨드 객체로 매핑, 해당 커맨드 객체는 검증 로직 + 전용 필드 가짐
- 애플리케이션 계층: 커맨드 객체를 유스케이스에 따라 도메인 모델을 변경하기 위해 필요한 무언가로 매핑
웹 계층과 애플리케이션 계층 사이에서 상태 변경 유스케이스의 경계를 명확하게 할 때 가장 빛을 발한다. 애플리케이션 계층과 영속성 계층 사이에서는 매핑 오버해드 때문에 사용하지 않는 것이 좋다.
단방향 매핑 전략
모든 계층이 같은 인터페이스를 구현하고 있다. 이 인터페이스는 Attribute에 대한 getter 메서드를 제공해 도메인 모델의 상태를 캡슐화한다.
이 매핑은 팩터리라는 DDD 개념과 잘 어울리며, 계층 간의 모델이 비슷할 때(읽기 전용) 가장 효과적이다.
언제 어떤 매핑 전략을 사용할 것인가?
시간이 지나며 변화를 거듭하기에 고정된 매핑 전략으로 유지하기보다, 간단한 전략으로 시작해 계층 간 결합을 떼어내는 것도 도움이 된다.
- 변경 유스케이스 작업을 한다면
- 웹 - 애플리케이션 사이: 완전 매핑
- 애플리케이션 - 영속성 사이: 매핑하지 않기 or 양방향 (애플리케이션에서 영속성 문제를 다뤄야 한다면)
- 쿼리
- 웹 - 애플리케이션 사이: 매핑하지 않기
- 애플리케이션 - 영속성 사이: 매핑하지 않기
- 애플리케이션에서 웹 or 영속성 문제 다룬다면 각각 양방향 매핑 정략
정말 어려운 문제인 것 같다.
회사의 성장 속도나 도메인의 성숙도 등등 아키텍처는 변하기에 선택하라면 정말 좋은 선택을 내가
할 수 있을까란 생각이 굉장히들었다...
팀원들과의 대화를 많이해보고 최선의 전략을 당시에 맞게 사용을 하자 !
(사실 마감 기한이 가장 중요하다...)
나의 생각
책을 다 읽은 후 아키텍처를 사용하는 프로젝트가 어느정도나 있을까 라는 생각이 든다.
저자가 "도메인의 중요도가 크지 않은 프로젝트에서는 사용 하지 않는 것이 더 좋다."
라는 말을 본인도 하기에 우리나라의 어느정도 정착된 서비스 나 도메인 전문가가 있는 경우 아닌 이상
굉장히 도입이 어렵겠다 생각이 들었다.
하지만 그렇다고 내가 그런 프로젝트에 안 할거라는 생각은 하지 않는다.
아니면 그런 프로젝트가 아니더라도 미래를 위해 적용을 한 프로젝트들의 사례가 있다고도 하니 좋다.
책 제목에서는 클린 아키텍처라고는 하지만 기본적인 DDD 라던가 SOLID의 애기를 자주 한다.
이런 부분이 굉장히 좋았다. 정말 실무적인 패키지의 구조나 자바와 스프링을 사용한 코드를 보여주는것이
이 책의 내용을 이해 하기에 가장 좋았던 부분이기도 하다. 이 책의 클린 아키텍처를 배운것 이 외에도 다른 많은 것들을 얻어 간것 같다. 테스트의 방법 아키텍처를 바라보는 시야 등등 책 제목이상의 가치를 가진 것 같다.