Featured image of post 22. 클린 아키텍처

22. 클린 아키텍처

5부 - 아키텍처

  • 육각형 아키텍처(Hexagonal Architecture)
  • DCI(Data, Context and Interaction)
  • BCE(Boundary-Control-Entity)

위와 같은 아키텍처들의 목적은 관심사의 분리(Separation of concerns)이다.

소프트웨어를 계층으로 분리함으로써 관심사의 분리라는 목표를 달성할 수 있었다.

각 아키텍처는 최소한 업무 규칙을 위한 계층 하나와, 사용자와 시스템 인터페이스를 위한 또 다른 계층 하나를 반드시 포함하며, 시스템이 다음과 같은 특징을 가지도록 만든다.

  • 프레임워크 독립성
  • 테스트 용이성
  • UI 독립성
  • 데이터베이스 독립성
  • 모든 외부 에이전시에 대한 독립성

의존성 규칙

그림의 각 동원은 소프트웨어의 서로 다른 영역을 표현하는데, 안으로 들어갈수록 고수준의 소프트웨어가 된다.(바깥쪽은 메커니즘, 안쪽은 정책)

이러한 아키텍처가 동작하도록 하는 가장 중요한 규칙은 소스 코드 의존성은 반드시 안쪽으로, 고수준의 정책을 향해야 한다는 의존성 규칙(Dependency Rule)이다.

  • 내부의 원에 속한 요소는 외부의 원에 속한 어떤 것도 알지 못한다.
    • 함수, 클래스, 변수, 엔티티 등
    • 외부의 원에 선언된 데이터 형식도 내부의 원에서 절대 사용해서는 안된다.

외부 원에 위치한 어떤 것도 내부 원에 영향을 주지 않아야한다.

엔티티

엔티티는 전사적인 핵심 업무 규칙을 캡슐화한 것이다.(단순한 단일 애플리케이션이라면 애플리케이션의 업무 객체)

  • 메서드를 가지는 객체
  • 일련의 데이터 구조와 함수의 집합

기업의 다양한 애플리케이션에서 엔티티를 재사용할 수만 있다면, 그 형태는 그다지 중요하지 않다.

운영 관점에서 특정 애플리케이션에 무언가 변경이 필요하더라도 엔티티 계층에는 절대로 영향을 주어서는 안 된다.

유스케이스

유스케이스 계층의 소프트웨어는 애플리케이션에 특화된 업무 규칙을 포함하며, 시스템의 모든 유스케이스를 캡슐화하고 구현한다.

  • 엔티티로 들어오고 나가는 데이터 흐름을 조정한다.
  • 엔티티가 자신의 핵심 업무 규칙을 사용해서 유스케이스의 목적을 달성하도록 이끈다.

유스케이스 레이어를 잘 격리하여 발생한 변경이 엔티티에 영향을 줘서는 안될 뿐만 아니라, 외부 요소에서 발생한 변경이 이 계층에 영향을 줘서도 안된다.

유스케이스 레이어는 아래와 같은 상황에서만 영향을 받아야한다.

  • 운영 관점에서 애플리케이션이 변경되어 유스케이스 자체가 영향을 받은 경우
  • 유스케이스의 세부 사항이 변경된 경우

인터페이스 어댑터

인터페이스 어댑터 레이어는 일련의 어댑터들(Controllers, Gateways, Presenters)로 구성된다.

어댑터는 데이터를 유스케이스와 엔티티에게 가장 편리한 형식에서 데이터베이스나 웹 같은 외부 에이전시에게 가장 편리한 형식으로 변환한다.

  • ex) GUI의 MVC 아키텍처

모델은 데이터 구조 정도에 지나지 않으며, 컨트롤러에서 유스케이스로 전달되고, 다시 유스케이스에서 프레젠터와 뷰로 되돌아간다.

인터페이스 어댑터 레이어는 데이터를 엔티티와 유스케이스에게 가장 편리한 형식에서 영속성용으로 사용 중인 임의의 프레임워크(ex. DB)가 이용하기에 가장 편리한 형식으로 변환한다.

또한 데이터를 외부 서비스와 같은 외부적인 형식에서 유스케이스나 엔티티에서 사용되는 내부적인 형식으로 변환하는 또 다른 어댑터가 필요하다.

프레임워크와 드라이버

가장 바깥 레이어인 프레임워크와 드라이버 레이어는 모든 세부사항이 위치하는 곳으로, 일반적으로 데이터베이스나 웹 프레임워크 같은 프레임워크나 도구들로 구성된다.

이 계층에서는 안쪽 원과 통신하기 위한 접합 코드 외에는 특별히 더 작성해야 할 코드가 그다지 많지 않다.

웹, 데이터베이스와 같은 세부사항을 모두 외부에 위치시켜서 피해를 최소화한다.

원은 네 개여야만 하는가?

그림의 원들은 하나의 예시일 뿐이며 더 많은 원이 필요할 수 있다.

하지만 어떤 경우에도 의존성 규칙은 적용된다.

  • 소스코드 의존성은 항상 안족을 향한다.
  • 안쪽으로 이동할수록 추상화와 정책의 수준은 높아진다.
  • 가장 바깥쪽 원은 저수준의 구체적인 세부사항으로 구성된다.
  • 안쪽으로 이동할수록 소프트웨어는 점점 추상화되고 더 높은 수준의 정책들을 캡슐화한다.

경계 횡단하기

위 예시에서 컨트롤러와 프레젠터가 다음 계층에 속한 유스케이스와 통신하는 모습을 확인할 수 있다.

  1. 컨트롤에서 시작
  2. 유스케이스를 지남
  3. 프레젠터에서 실행

유스케이스가 내부 원의 인터페이스를 호출하도록 하고, 외부 원의 프레젠터가 그 인터페이스를 구현하도록 만들어 의존성 흐름을 역전시켰다.

이처럼 제어흐름과 의존성의 방향이 명백히 반대여야 하는 경우, 의존성 역전 원칙을 사용하여 해결한다.

  • 인터페이스와 상속 관계 등

아키텍처 경계를 횡단할 때 언제라도 동일한 기법을 사용할 수 있다.

경계를 횡단하는 데이터는 어떤 모습인가?

경계를 가로지르는 데이터는 흔히 간단한 데이터 구조로 이루어져 있다.

  • 기본적인 구조체, 간단한 데이터 전송 객체(DTO) 등
  • 함수 호출 시 간단한 인자
  • 해시맵, 객체

중요한 점은 엔티티 객체나 데이터베이스의 행을 전달하는 것이 아닌 격리되어 있는 간단한 데이터 구조가 경계를 가로질러 전달되어야 한다.

위와 같은 경우 결과적으로 어떠한 형태로든 전달되는 데이터 구조를 통해 내부의 원에서 외부 원의 무언가를 알게 되므로 의존성 규칙을 위배하게된다.

따라서 경계를 가로질러 데이터를 전달할 때, 데이터는 항상 내부의 원에서 사용하기에 가장 편리한 형태를 가져야한다.

결론

위와같은 간단한 규칙들을 준수하는 것은 어렵지 않으며, 향후에 겪을 수많은 고통거리를 덜어준다.

소프트웨어를 계층으로 분리하고 의존성 규칙을 준수한다면 본질적으로 테스트하기 쉬운 시스템을 만들게 될 것이며, 그에 다른 이점을 누릴 수 있다.

  • 데이터베이스나 프레임워크와 같은 시스템의 외부 요소를 교체하더라도 훨씬 수월해진다.