만들면서배우는클린아키텍처

2장 의존성 역전하기

v0o0v 2022. 3. 31. 13:20

02 의존성 역전하기

2장에서는 1장의 계층형 아키텍처의 문제점에 대한 대안에 대해 얘기한다.

단일 책임 원칙

Single Responsibility Principle(SRP)

하나의 컴포넌트는 오로의 한가지 일만 해야 하고, 그것을 올바르게 수행해야 한다.

라고 알고있지만 사실 실제 정의는

컴포넌트를 변경하는 이유는 오직 하나뿐이어야 한다.

http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

아키텍처에서 가지는 의미 : 다른 이유로 변경된다면 이 컴포넌트는 안 변한다.

그런데... SRP를 따르다보니 변경할 이유가 컴포넌트 간의 의존성을 통해 너무 쉽게 전파된다.

image

E에 비해 A는 의존성 때문에 변경할 확률이 높다.

시간이 갈수록 바꿀건 많아지는데 SRP 때문에 변경이 어렵고 비용이 올라간다.
그리고, 변경할 이유가 많아지면 다른 컴포넌트가 실패하는 원인이 될 수도 있다.

의존이 많이 걸리는 컴포넌트의 변경은 위험하다. 의존의 방향이 매우 중요하다!

부수 효과에 관한 이야기

클라이언트가 핵심적인 컴포넌트 변경에 따른 위험을 두려워해서 더 이상하고 비용이 많이 드는 방식을 주문한 이야기. 변경에 대한 부수효과가 이렇게나 무섭습니다. 우리는 좋은 아키텍처를 통해 좋은 소프트웨어를 만들어봅시다.

의존성 역전 원칙

image

계층형 아키텍처인 경우에 의존성 방향에 따라 상위 계층이 하위 계층에 비해 변경 확률이 높다.
따라서, 영속성 계층 변경 시 도메인 계층도 변경된다. 도메인 코드를 바꾸고 싶지 않기 때문에 의존성을 제거하고 싶다. => 의존성 역전 원칙 사용

DIP : 코드상의 어떤 의존성이든 그 방향을 바꿀 수(역전시킬 수) 있다.

  1. 엔티티를 도메인 계층으로 옮긴다 => 순한 의존성 발생
  2. 도메인 계층에 리포지터리 인터페이스 만들고 리포지터리 구현체를 영속성 계층에 만든다(DIP)

image

의존성으로부터 도메인 로직 해방!

DIP는 뒤에 나올 두 가지 아키텍처 스타일(클린 / 헥사고날)의 핵심기능.

클린 아키텍처

image

  • 비즈니스 규칙이 독립적(프레임워크, 데이터베이스, UI, 다른 앱 등등 으로부터)
  • 비즈니스 규칙 테스트 용이
  • 도메인 코드가 바깥으로 향하는 의존성이 없음(DIP를 통해 안으로)
  • 도메인 엔티티가 코어에 존재. SRP로 인해 엔티티는 세분화 => 넓은 서비스 문제 해결
  • 도메인 주변으로 앱의 다른 모든 컴포넌트(디비/UI 등)가 존재
  • 바깥쪽 계층은 서드파티에 어댑터 제공
  • DDD 적용 가능
  • 단점
    • 도메인 엔티티가 독립되어야 하기 때문에
    • ORM 같은 경우에 영속성 계층에서 도메인 엔티티를 따로 가지고 있고 계층간에 변환해서 사용해야 함
    • 하지만 당연해 보임. 예를 들어 JPA에서 관리하는 엔티티에 기본생성자는 도메인과 상관없지만 프레임워크에는 필요함.

클린 아키텍처의 모호함을 더 구체적으로 푼 육각형 아키텍처를 알아보자

육각형 아키텍처(헥사고날 아키텍처)

image

  • 육각형 안에는 도메인 엔티티와 이와 상호작용하는 유스케이스 존재
  • 육각형에서 외부로 향하는 의존성 없음. 모든 의존성은 코어를 향함
  • 육각형 바깥에는 앱과 상호작용하는 다양한 어댑터 존재
  • 웹브라우저와 상호작용하는 웹 어댑터 / 데이터베이스와 상호작용하는 어댑터 / 외부시스템과 상호작용하는 어댑터 등
  • 왼쪽은 코어를 호출하는 어댑터(앱을 주도하는)
  • 오른쪽은 코어가 호출하는 어댑터(앱에 주도되는)
  • 코어와 어댑터간 통신은 포트를 통함
  • 포트
    • 주도하는 어댑터(왼쪽/인풋)에게는 코어에 있는 유스케이스에 의해 구현되고 어댑터로부터 호출되는 인터페이스
    • For driving adaptors, such a port might be an interface that is implemented by one of the use case classes in the core and called by adaptor.
    • 주도되는 어댑터(오른쪽/아웃풋)에게는 어댑터에 의해 구현되고 코어에 의해 호출되는 인터페이스
  • 포트와 어댑터 아키텍처로 알려져 있음
  • 계층
    • 바깥 : 어댑터. 앱과 다른 시스템간의 번역 담당
    • 중간 : 포트와 유스케이스 구현체. 애플리케이션 계층. 인터페이스 정의
    • 안 : 도메인 엔티티

유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까?

  • DIP로 인해 도메인 코드가 다른 어떤 컴포넌트에도 의존하지 않게 함으로써 코드를 변경할 이유의 수를 줄인다.
  • 도메인 코드는 비즈니스 문제에만 집중하고 영속성 코드와 UI 코드도 각자의 문제이 집중할 수 있다.