만들면서 배우는 클린 아키텍처를 읽고 공부한 내용을 정리해 보자.
단일 책임 원칙
하나의 컴포넌트는 오로지 한 가지 일만 해야 하고, 그것을 올바르게 수행해야 한다.
보통 단일 책임 원칙은 위의 내용처럼 해석하기 쉽지만 실제 의도는 아래에 가깝다
컴포넌트를 변경하는 이유는 오직 하나뿐이어야 한다.
책임은 사실 한 가지 일만 하는 것보다는 변경할 이유로 해석해야 한다. 컴포넌트가 변경할 이유가 오로지 한 가지라면 컴포넌트는 자연스럽게 한 가지 일만 하게 된다. 변경할 이유가 오직 한 가지라는 것은 아키텍처에서 어떤 의미일까? 컴포넌트를 변경할 이유가 한 가지라면 어떤 다른 이유로 소프트웨어를 변경하더라도 이 컴포넌트에 대해서는 전혀 신경 쓸 필요가 없다. 소프트웨어가 변경되더라도 기대한 대로 동작할 것이기 때문이다.
하지만 변경할 이유라는 것은 컴포넌트 간의 의존성을 통해 쉽게 전파된다. 컴포넌트의 의존성 각각은 해당 컴포넌트를 변경하는 이유 하나씩에 해당한다. 컴포넌트 E를 변경할 유일한 이유는 E의 기능을 바꿔야 할 때뿐이다. 반면 컴포넌트 A는 모든 컴포넌트에 의존하고 있기 때문에 다른 어떤 컴포넌트가 바뀌든지 같이 바뀌어야 한다.
의존성 역전 원칙
계층형 아키텍처에서 계층 간 의존성은 다음 계층인 아래 방향을 가리킨다. 단일 책임 원칙을 고수준에서 적용할 때 상위 계층들이 하위 계층들에 비해 변경할 이유가 더 많다는 것을 알 수 있다. 그러므로 영속성 계층에 대한 도메인 계층의 의존성 때문에 영속성 계층을 변경할 때마다 잠재적으로 도메인 계층도 변경해야 한다.
그러나 도메인 코드는 애플리케이션에서 가장 중요한 코드다. 어떻게 이 의존성을 제거할 수 있을까? 바로 의존성 역전 원칙이다.
코드상의 어떤 의존성이든 그 방향을 바꿀 수(역전시킬 수) 있다.
사실 의존성의 양쪽 코드를 모두 제어할 수 있을 때만 역전시킬 수 있다. 만약 서드파티 라이브러리에 의존성이 있다면 제어할 수 없기 때문에 역전시킬 수 없다. 도메인 코드와 영속성 코드 간의 의존성을 역전시켜서 영속성 코드가 도메인 코드에 의존하고, 도메인 코드를 “변경할 이유”의 개수를 줄여보자.
엔티티는 도메인 객체를 표현하고 도메인 코드는 이 엔티티들의 상태를 변경하는 일을 중심으로 하기 때문에 먼저 엔티티를 도메인 계층으로 올린다. 그러나 이제는 영속성 계층의 리포지토리가 도메인 계층에 있는 엔티티에 의존하기 때문에 두 계층 사이에 순한 의존성이 생긴다. 따라서 DIP를 적용하여 도메인 계층에 리포지토리 대한 인터페이스를 만들고, 실제 리포지토리는 영속성 계층에서 구현하게 하는 것이다.
클린 아키텍처
로버트 마틴은 클린 아키텍처라는 용어를 정립했다. 클린 아키텍처에서는 설계나 비즈니스 규칙의 테스트를 용이하게 하고, 비즈니스 규칙은 프레임워크, 데이터베이스, ui 기술, 그 밖의 외부 애플리케이션이나 인터페이스로부터 독립적일 수 있다고 이야기했다. 이 말은 도메인 코드가 바깥으로 향하는 어떤 의존성도 없어야 함을 의미한다.
대신 의존성 역전 원칙의 도움으로 모든 의존성이 도메인 코드를 향하고 있다. 클린 아키텍처에서 중요한 규칙은 의존성 규칙으로, 계층 간의 모든 의존성이 안쪽으로 향해야 한다는 것이다. 아키텍처의 코어에는 주변 유스케이스에서 접근하는 도메인 엔티티들이 있다. 유스케이스는 앞에서 서비스라고 불렀던 것들인데, 단일 책임을 갖기 위해 조금 더 세분화돼 있다. 이를 통해 넓은 서비스 문제를 피할 수 있다.
도메인 코드에서는 어떤 영속성 프레임워크나 UI 프레임워크가 사용되는지 알 수 없기 때문에 특정 프레임워크에 특화된 코드를 가질 수 없고 비즈니스 규칙에 집중할 수 있다. 그래서 도메인 코드를 자유롭게 모델링할 수 있다. 예를 들어, DDD를 순수한 형태로 적용해 볼 수도 있다.
하지만 클린 아키텍처에는 외부 계층과 철저하게 분리돼야 하므로 애플리케이션 엔티티에 대한 모델을 각 계층에서 유지보수해야 한다. 가령 영속성 계층에서 ORM 프레임워크를 사용한다고 해보자. 도메인 계층은 영속성 계층을 모르기 때문에 도메인 계층에서 사용한 엔티티 클래스를 영속성 계층에서 함께 사용할 수 없고 두 계층에서 각각 엔티티를 만들어야 한다. 즉, 도메인 계층과 영속성 계층이 데이터를 주고받을 때, 두 엔티티를 서로 변환해야 한다는 뜻이다. 이는 도메인 계층과 다른 계층들 사이에서도 마찬가지다. 하지만 이것은 바람직한 일이다. 이것이 바로 도메인 코드를 프레임워크에 강결합이 제거된 상태이다.
예를 들어 JPA에서 인자가 없는 기본 생성자를 강제하는 것
헥사고날 아키텍처
헥사고날 아키텍처는 애플리케이션 코어가 각 어댑터와 상호작용하기 위해 특정 포트를 제공하기 때문에 포트 어댑터 아키텍처라고도 불린다. 육각형 안에는 도메인 엔티티와 이와 상호작용하는 유스케이스가 있다. 육각형에서 외부로 향하는 의존성이 없기 때문에 클린 아키텍처에서 제시한 의존성 규칙이 그대로 적용된다는 점을 주목하자. 대신 모든 의존성은 코어를 향한다.
육각형 바깥에는 애플리케이션과 상호작용하는 다양한 어댑터들이 있다. 애플리케이션 코어와 어댑터들 간의 통신이 가능해지려면 코어가 각각의 포트를 제공해야 한다.
- 코어를 주도하는 어댑터(driving adapter)에게는 포트가 코어에 있는 유스케이스 클래스 중 하나에 의해 구현되고 어댑터에 의해 호출되는 인터페이스가 될 것이다.
- 코어에 의해 주도되는 어댑터(driven adapter)에게는 포트가 어댑터에 의해 구현되고 코어에 의해 호출되는 인터페이스가 될 것이다.
헥사고날 아키텍처도 클린 아키텍처처럼 계층으로 구성할 수 있다. 가장 바깥쪽에 있는 계층을 애플리케이션과 다른 시스템 간의 번역을 담당하는 어댑터로 구성되어 있다. 다음으로 포트와 유스케이스 구현체를 결합해서 애플리케이션 계층을 구성할 수 있다. 마지막 계층에는 도메인 엔티티가 위치한다.
결국 핵심은? 의존성
결국 어떤 아키텍처라고 불리든 의존성을 역전시켜 도메인 코드가 다른 바깥쪽 코드에 의존하지 않게 함으로써 영속성과 UI에 특화된 모든 문제로부터 도메인 로직의 결합을 제거하고 코드를 변경할 이유의 수를 줄일 수 있다. 도메인 코드는 비즈니스 문제에 딱 맞도록 자유롭게 모델링 될 수 있고, 영속성 코드와 UI 코드도 영속성 문제와 UI 문제에 맞게 자유롭게 모델링 될 수 있다.
'design & development' 카테고리의 다른 글
[만들면서 배우는 클린 아키텍처] 4. 유스케이스 구현하기 (0) | 2024.11.16 |
---|---|
[만들면서 배우는 클린 아키텍처] 3. 코드 구성하기 (1) | 2024.11.14 |
[만들면서 배우는 클린 아키텍처] 계층형 아키텍처의 문제점 (0) | 2024.11.12 |
DDD? 클린 아키텍처? 헥사고날 아키텍처? (1) | 2024.11.11 |
캐시 전략 (1) | 2024.10.30 |