얼마전에 계층형 아키텍처의 문제점에 다뤘었다.
https://devkingdom.tistory.com/340
오늘은 계층형 아키텍처의 문제점을 해결할 수 있는 방법을 좀 공유드리려 한다.
혹시 객체지향 공부하신분들은 SOLID 원칙이라는 것을 들어보신적 있을거다. 혹시 해당 내용을 잘 모르신다면 아래링크를 통해 공부를 좀 하고 오도록하자.
https://devkingdom.tistory.com/296?category=838914
계층형 아키텍처의 문제점을 해결하기 위해서는 SOLID 중 S와 D,
단일 책임 원칙 ( Single Reason to Change Principle, SRP) 과 의존성 역전 원칙 (Dependency Inversion Principle, DIP) 에 대해 잘 알고 있어야한다.
단일 책임 원칙
일반적으로 단일책임원칙을 아래와 같이 아는 경우가 많다.
하나의 컴포넌트는 오로지 한가지 일만 해야하고, 그것을 올바르게 수행해야 한다.
하지만 실제로 단일 책임 원칙의 정의는 아래와 같다.
컴포넌트를 변경하는 이유는 오직 하나 뿐이어야 한다.
위의 말처럼 컴포넌트를 변경할 이유가 딱 하나라면 컴포넌트는 딱 한 가지 일만 하게 된다. 그리고 더 중요한 것은 변경할 이유가 딱 하나라는 것 그 자체이다.
컴포넌트를 변경할 이유가 한가지라는 것은 컴포넌트와 무관한 어떤 이유로 소프트웨어를 변경하더라도 우리는 이 컴포넌트에 대해서는 신경쓸 필요가 없다. 소프트웨어가 변경이 되어도 여전히 우리의 기대대로 잘 동작할 것이기 때문이다.
하지만 이렇게 단일책임원칙을 지켜서 개발하는게 쉽지가 않다. 변경할 이유라는 것이 컴포넌트간의 의존성을 통해 아주 쉽게 전파되기 때문이다.
위의 그림을 보면 컴포넌트 A는 직접 의존이든 전이 의존(transitive dependency) 이든 다른 여러 컴포넌트에 의존하고 있다. 다른 컴포넌트가 변경된다면 A도 같이 바뀌어야한다.
반면에 컴포넌트 E는 어떤 컴포넌트든 의존하고 있지 않다. 즉 E가 변경될 유일한 이유는 E의 기능을 바꿔야할 때일 뿐이다.
A 컴포넌트 처럼 변경할 이유가 많아지면 단일책임원칙을 위반하기 때문에 코드가 오래되면 될수록 변경이 어려워지고 비용이 증가한다. 그리고 시간이 지나면 지날 수록 변경할 이유가 많아진다. 이렇게 변경할 이유가 많아지고 어려워지면 나중에 유지보수가 힘들어지게 된다.
의존성 역전 원칙
계층형 아키텍처에서는 웹 계층은 도메인 계층을, 도메인 계층은 영속성 계층을... 이렇게 의존성은 아래 방향을 가리킨다. 단일 책임원칙을 고수준에서 적용하면 상위 계칭이 하위계층에 비해 변경할 이유가 많다.
일반적인 계층형 아키텍처에서는 영속성 계층에 대해 도메인 계층의 의존성 때문에 영속성 계층이 바뀌면 도메인 계층도 바꿔줘야한다. 이 의존성을 제거해준다면 설계의 효율은 극도로 올라간다.
이러한 의존성은 의존성 역전을 통해 해결이 가능하다.
의존성 역전 원칙은 보통 아래와 같이 정의된다.
코드상의 어떤 의존성이든 그 방향을 바꿀 수(역전시킬 수 ) 있다
이 원칙을 이용해 도메인 - 영속성 계층간에 존재하는 의존성을 역전시켜 영속성 코드가 도메인 코드에 의존하게 만들어 도메인 코드를 '변경할 이유' 자체를 없앨수 있다.
의존성 역전은 인터페이스를 통해 가능하다. 아래 그림을 보자.
도메인 계층에 Entity에 영속성 계층의 Repository가 의존한다면 두 계층사이에 순환의존성이생기게 되는데,
그렇게 하지 않고 도메인 계층에 Repository에 대한 인터페이스를 만들고, 실제 Repository의 구현체를 영속성 계층에 구현하게 만들면 도메인이 영속성에 의존 하던 구조를 영속성이 도메인에 의존하도록 만들 수 있다. 이렇게 되면 도메인 계층에서는 순수한 비지니스 로직에만 신경 쓸수 있게 된다.
클린 아키텍처
이번에는 조금 더 넓은 개념으로 살펴보자.
아래 그림은 클린 아키텍처가 어떻게 생겼는지를 추상적으로 보여주는 그림이다.
그림에서 보이는 것처럼 클린아키텍처에서 모든 의존성은 도메인 로직을 향해 안쪽 방향으로 향한다. entity가 존재하는 코어를 주변으로 비즈니스 규칙을 지원(영속성을 제공하거나 UI를 제공하는 등)하는 다른 여러 컴포넌트들이 존재한다.
그리고 바깥 쪽의 계층에서는 다른 서드파티 컴포넌트에 어댑터를 제공할 수도 있다.
이러한 아키텍처에서는 도메인 코드에서 어떤 영속성프레임워크나 UI 프레임워크가 사용되는지를 전혀 알수가 없기에 순수하게 도메인계층에서는 비지니스 규칙에 집중할 수가 있다. 클린 아키텍처에서는 도메인 주도 설계 (Domain Driven Design,DDD) 가 가능하다.
하지만 클린 아키텍처는 외부계층과 도메인 계층을 철저하게 분리해야 함으로 각 계층에서 엔티티에 대한 모델을 유지보수 해줘야하는 비용이 발생한다.
예를들어 JPA 같은 ORM 프레임워크를 사용한다고 가정했을 때, 전용 엔티티 클래스가 필요하다. 클린아키텍처에서 JPA를 사용하려면 도메인 계층은 영속성 계층을 모르기 때문에 각각 엔티티 클래스를 만들어 관리해줘야한다. 이는 도메인과 영속성 계층 뿐만 아니라 모든 계층에서 마찬가지이다.
비용은 발생하지만 이렇게 구현하는 것은 매우 바람직한 일이다. 이러한 클린 아키텍처는 좀 추상적이기 때문에 조금더 자세하고 구체적인 내용인 '헥사고날(육각형) 아키텍처' 에 대해 말씀드리겠다.
헥사고날(육가형) 아키텍처
아래 그림은 헥사고날 아키텍처의 구조를 보여준다.
해당 아키텍처의 컨셉은 어플리케이션의 코어가 각 어댑터와 상호작용하기 위해 특정 포트들을 제공하는 것이다. 육각형 내부에는 도메인 엔티티와 상호작용하는 유스케이스(서비스)가 있다. 육각형 아키텍처에서 모든 의존성은 코어(도메인)을 향한다.
육각형 바깥에는 웹과 상호작용하는 웹 어댑터, 외부시스템과 상호작용하는 어댑터, DB와 상호작용하는 어댑터 등 어플리케이션과 상호작용하는 다양한 어댑터들이 있다.
그림에서 왼쪽에 있는 어댑터는 어플리케이션 코어를 호출하는 어플리케이션을 주도하는 어댑터(driving adapter)들이고 오른쪽에 있는 어댑터들은 어플리케이션 코어에 의해 호출되기 때문에 어플리케이션에 의해 주도되는 어댑터(driven adapter)들이다.
주도하는 어댑터에게는 포트가 코어에 있는 유스케이스 클래스에 의해 구현이 되고 호출되는 인터페이스가 될 것이고,
주도되는 어댑터에게는 이런 포트가 어댑터에 의해 구현이 되고 코어에 의해 호출 되는 인터페이스가 될것이다.
요약하자면 가장 바깥쪽에 있는 계층은 애플리케이션과 다른 시스템간의 번역을 담당하는 어댑터로 구성되어 있고,
다음은 포트와 유스케이스 구현체가 있는 어플리케이션 계층, 그리고 마지막에는 도메인 엔티티가 있는 도메인 계층으로 나누어 볼 수 있다.
오늘 설명드린 개념을 실제로 구현하고자하는 시스템의 아키텍처로 적용할 수 있을지 검토해보고 한번쯤은 꼭 적용시켜보자. 한번 제대로 설계된 프로그램은 정말 오래 쓸 수 있으니 말이다.
끝.
Ref.
톰 홈버그, 『만들면서 배우는 클린 아키텍처』, 위키북스(2021), p12~22
'Develop > Design' 카테고리의 다른 글
[Clean Architecture] 육각형 아키텍처 - 웹 어댑터 (컨트롤러) (0) | 2022.04.16 |
---|---|
[Clean Architecture] 육각형 아키텍처 (헥사고날 아키텍처) 패키지 구조 (0) | 2022.04.12 |
[Clean Architecture] 계층형 아키텍처의 문제점 (1) | 2022.04.10 |
클린코드를 위하여 (3) | 2022.01.28 |
동시성 프로그래밍에 대하여 (1) | 2022.01.19 |
댓글