1장은 계층형 아키텍처에 대한 단점들을 이야기 했으며 이번 장은 대안에 대해 소개하는 부분이다.
객체지향의 5대원칙 중 단일 책임 원칙과 의존성 역전 원칙에 대해 알아본다.
단일 책임 원칙
하나의 컴포넌트는 오로지 한 가지 일만 해야 하고, 그것을 올바르게 수행해야 한다.
단일책임 원책의 일반적인 해석은 위와 같다. 좋은 조언이지만 단일 책임 원칙의 실제 의도는 아니다.
“오로지 한 가지 일만 하는 것”은 단일 책임이라는 말을 가장 직관적으로 해석한 것이므로, 단일 책임 원칙을 자주 위의 해석으로 많이 사용되는데 오해의 여지가 있다.
단일 책임 원칙의 실제 정의는 다음과 같다.
컴포넌트를 변경하는 이유는 오직 하나뿐이어야 한다.
“책임”은 사실 “오로지 한 가지 일만 하는 것”보다는 “변경할 이유”로 해석해야 한다. 즉 컴포넌트를 변경할 이유가 오로지 한 가지라면 컴포넌트는 딱 한가지 일만 하게되는 것이다. 핵심은 바로 변경할 이유가 오직 한 가지라는 점이다.
❓ 이게 아키텍처에서 어떤 의미일까?
만약 컴포넌트를 변경할 이유가 한 가지라면 우리가 어떤 다른 이유로 소프트웨어를 변경하더라도 이 컴포넌트에 대해서는 신경 쓸 필요가 없다. 변경되더라도 우리가 기대한 대로 동작할 것이기 때문이다.
변경할 이유라는 것은 컴포넌트 간의 의존성을 통해 너무도 쉽게 전파된다.
위의 그림을 보면 컴포넌트 A는 다른 여러 컴포넌트에 의존하는(직접이든, 전이된 것이든)반면 E는 의존하는 것이 전혀 없다.
컴포넌트 E를 변경할 유일한 이유는 새로운 요구사항에 의해 E의 기능을 바꿔야 할 때 분이다. 반면 컴포넌트 A의 경우는 모든 컴포넌트에 의존하고 있기 때문에 다른 어떤 컴포넌트가 바뀌든지 같이 바뀌어야 한다.
많은 코드는 단일 책임 원칙을 위반하기 때문에 시간이 갈수록 변경하기가 더 어려워지고 그로 인해 변경 비용도 증가하게 된다. 또한 시간이 갈수록 컴포넌트를 변경할 이유가 더 많아지고 그 후에는 컴포넌트 하나를 바꾸는 것이 다른 컴포넌트가 실패하는 원인으로 작용할 수 있다.
부수효과에 관한 이야기
단일책임 원칙을 지키지 않고 개발을 진행하면 소프트웨어를 변경하는데 더 많은 비용을 지불하도록 만드는 경우가 발생할 수 있다. 따라서 좋은 소프트웨어를 만들도록 노력해야 한다.
의존성 역전 원칙
계층형 아키텍처에서 계층 간 의존성은 항상 다음 계층인 아래 방향을 가리키게 된다. 단일 책임 원칙을 고수준에서 적용할 때 상위 계층들이 하위 계층들에 비해 변경할 이유가 더 많다는 것을 알 수 있다.
그러므로 영속성 계층에 대한 도메인 계층의 의존성 때문에 영속성 계층을 변경할 때마다 잠재적으로 도메인 계층도 변경해야 한다. 그러나 도메인 코드는 애플리케이션에서 가장 중요한 코드다. 영속성 코드가 바뀐다고 해서 도메인 코드까지의 변경하는 것은 좋지 못하며 의존성을 역전시켜야 한다.
1장에 나왔던 왼쪽의 그림을 개선시키는 과정이다. 도메인 계층에 영속성 계층의 엔티티와 레포지토리와 상호작용하는 서비스가 하나 있다는 가정
먼저 엔티티는 도메인 객체를 표현하고 도메인 코드는 이 엔티티들의 상태를 변경하는 일을 중심으로 하기 때문에 엔티티를 도메인 계층으로 올리게 된다.
그러나 영속성 계층의 리포지토리가 도메인 계층에 있는 엔티티에 의존하기 때문에 두 계층 사이에 순환 의존성이 생긴다. 이 부분을 의존성 역전 원칙을 사용하는 부분으로 도메인 계층에 리포지토리에 대한 인터페이스를 만들고 실제 리포지토리는 영속성 계층에서 구현하여 해결할 수 있다.
클린 아키텍처
로버트 마틴은 클린 아키텍처에서는 설계가 비즈니스 규칙의 테스트를 용이하게 하고, 비즈니스 규칙은 프레임워크, 데이터베이스, UI 기술, 그 밖의 외부 애플리케이션이나 인터페이스로부터 독립적일 수 있다고 이야기 했다.
이는 도메인 코드가 바깥으로 향하는 어떤 의존성도 없어야 함을 의미하며 대신 의존성 역전 원칙의 도움으로 모든 의존성이 도메인 코드를 향하고 있다.
아키텍처의 코어
클린 아키텍처 계열에서 도메인 계층과 애플리케이션 계층을 합쳐 application core라고 부른다.
아키텍처의 코어 주변 유스케이스에서 접근하는 도메인 엔티티들이 있다.
유스케이스는 앞에서 서비스라고 불렸던 것들인데, 단일 책임을 갖기 위해 조금 더 세분화 되어 있다.
이를 통해서 이전에 이야기 했던 넓은 서비스 문제를 피할 수 있게 된다.
이 코어 주변으로 비즈니스 규칙을 지원하는 애플리케이션의 다른 모든 컴포넌트들을 확인할 수 있다. 여기서 “지원”은 영속성을 제공하거나 UI를 제공하는 것 등을 의미한다. 또한 바깥쪽 계층들은 다른 서드파티 컴포넌트에 어댑터를 제공할 수 있다.
ORM 프레임워크를 사용한다면?
도메인 계층은 영속성 계층을 모르기 때문에 도메인 계층에서 사용한 엔티티 클래스를 영속성 계층에 함께 사용하지 않고 두 계층에서 각각 엔티티를 만들면 된다. 도메인 계층과 영속성 계층이 데이터를 주고받을 때, 두 엔티티를 서로 변환해야 한다는 뜻이다.
육각형 아키텍처(핵사고날 아키텍처)
육각형 아키텍처는 애플리케이션 코어가 각 어댑터와 상호작용하기 위해 특정 포트를 제공하기 때문에 포트와 어댑터 아키텍처라고도 한다.
육각형 내부에는 도메인 엔티티와 이와 상호작용하는 유스케이스가 있다.
육각형에서 외부로 향하는 의존성이 없기 때문에 마틴이 클린 아키텍처에서 제시한 의존성 규칙을 그대로 적용된다
육각형 바깥에서는 애플리케이션과 상호작용하는 다양한 어댑터들이 있다.
왼쪽에 있는 어댑터들은 애플리케이션을 주도하는 어댑터들이다.
오른쪽에 있는 어댑터들은 애플리케이션에 의해 주도되는 어댑터들이다.
코어와 어댑터들 간의 통신을 가능하게 하기 위해 애플리케이션 코어가 각 포트를 제공한다.
결론
클린 아키텍처, 육각형 아키텍처 혹은 포트와 어댑터 아키텍처 중 무엇으로 불리든 의존성을 역전시켜 도메인 코드가 다른 바깥족 코드에 의존하지 않게 함으로써 영속성과 UI에 특화된 모든 문제로부터 도메인 로직의 결합을 제거하고 코드를 변경할 이유의 수를 줄일 수 있다. 그리고 변경할 이유가 적을수록 유지보수성은 더 좋아진다.
도메인 코드는 비즈니스 문제에 딱 맞도록 자유롭게 모델링 될 수 있고, 영속성 코드와 UI 코드도 영속성 문제와 UI문제에 맞게 자유롭게 모델링 될 수 있다.