1. 계층형 아키텍처의 단점
가. 계층 간 결합에 따른 데이터 중심 설계 → 비지니스 로직 몰이해
계층형 아키텍처의 도메인 계층은 영속성 계층에 의존하기 때문에 비지니스 로직에 대한 작업 전에 데이터베이스를 설계하는 것이 일반적입니다. 비지니스 로직을 탄탄하게 세우는 것이 데이터를 설계하는 것보다 우선되어야 합니다.
영속성 계층과 도메인 계층이 강하게 결합되면 한 쪽의 변경이 다른 한쪽의 변경을 강제하게 되어 유지보수 비용을 증가합니다.
나. 계층 간 의존 관계 붕괴 → 전계층에 도메인 분산
계층형 아키첵처에서 개발자의 필요에 따라 일방향 의존관계를 쉽게 허물수 있습니다. 일방향 의존관계가 깨졌을 때, 빌드가 실패하도록 강제해야 합니다. 그렇지 않다면 도메인이 전 계층에 흩어져서 추후 변경에 대한 비용이 기하급수적으로 커지게 됩니다.
도메인이 전 계층에 흩어지면 단위 테스트의 설정에 큰 비용이 들어갑니다. 테스트 설정에 큰 비용이 들어간다면 해당 프로젝트에서 테스트는 사라지기 쉽습니다.
다. 서비스 객체의 비대화 → 읽기 어려움, 지나친 의존성, 공동 작업 불가
계층형 아키텍처에서는 서비스 객체의 너비에 대한 규칙이 없기 때문에 비대해지기 쉽습니다. 서비스 객체가 비대해 지면 작업 대상 서비스를 찾기 어렵습니다. 더불어 하나의 서비스 객체가 지나치게 많은 객체를 의존하기 쉽습니다. 앞과 같은 이유로 다른 개발자와 함께 공동으로 작업하기 어렵습니다.
2. 클린 아키텍처의 대안
가. 계층 간 의존관계 변경 - SRP & DIP 활용
클린 아키첵처에서는 단일책임원칙(SRP)과 의존성역전원칙(DIP)의 관점에서 계층을 분리합니다. 다시 말해, 오직 단 하나의 이유만으로 객체를 변경(SRP)하기 위해 계층 간 의존관계를 변경하는 것이 클린 아키텍처의 핵심입니다.예를 들어, 도메인 계층에는 도메인 객체와 레파지토리의 인터페이스를 정의하고 영속성 계층에는 앞의 레파지토리의 구현체를 정의하는 방향으로 의존관계를 역전할 수 있습니다. 정리하자면 어플리케이션 계층은 인터페이스인 포트에 의존합니다. 포트의 구현체인 어댑터는 어플리케이션과 직접적인 의존 관계를 맺지 않습니다.
참고) 어플리케이션 계층과 영속성 계층의 의존성 역전
나. 의존관계 강제 - package-private 활용 & 멀티 빌드 모듈 활용
기능을 묶어서 새로운 패키지로 구성하되, 패키지 외부에서 접근을 제어하기 위해 package-private 접근 수준을 적극 활용해야 합니다. 예를 들어, application, domain 패키지 내 어탭터로 접근해야 하는 클래스는 public으로 설정해도 됩니다. 하지만 adapter 패키지 내의 클래스의 경우 package-private으로 접근수준을 정의해야 합니다.
참고) 접근 제어자 설계 예시
기능이 확장되면 하위 패키지로 클래스 파일을 묶기 마련입니다. package-private 접근 제어가 지정되면 상위 패키지에 대한 접근이 막히므로 기능 확장 시 다른 방법으로 의존성 관리가 필요합니다. ArchUnit과 같은 런터임 체크 툴 또는 멀티 빌드 모듈로 추출하는 방법을 권장합니다.
참고) 멀티 빌드 모듈 방법
다. 유스케이스 정의 - 작은 서비스 객체 활용
클린 아키텍처에서는 계층형 아키텍처의 서비스 객체를 다 작은 서비스 객체인 유스케이스로 구분합니다. 유즈케이스의 객체는 단 하나의 책임만을 가지며, 변경할 이유가 단 하나인 객체입니다. 다음과 같이 서비스 객체를 유스케이스 객체로 변경할 수 있습니다. UserService → RegisterUserService