Posts [클린아키텍처] 22장 클린아키텍처
Post
Cancel

[클린아키텍처] 22장 클린아키텍처

clean-architecture-book

‘클린 아키텍처’ 기술 서적에 대해 학습했던 내용을 정리하기 위한 목적의 TIL 포스팅입니다.🙆‍♂️

  • 지난 수십년간 시스템 아키텍처와 관련된 여러 가지 아이디어를 봐왔다.
    • 육각형 아키텍처(Hexagonal Architecture): 포트와 어댑터라고도 알려졌으며, 앨리스터 코오번이 개발했다.
    • DCI(Data, Context and Interaction)
    • BCE(Boundary-Control-Entity)
  • 모두 세부적인 면에선 다소 차이가 있더라도 그 내용은 상당히 비슷하다.
  • 이들의 목표는 모두 같은데, 바로 관심사의 분리(separation of concerns)다.
    • 이들은 모두 소프트웨어를 계층으로 분리함으로써 관심사의 분리라는 목표를 달성할 수 있다.
  • 각 아키텍처는 최소한의 업무 규칙을 위한 계층 하나와, 사용자와 시스템 인터페이스를 위한 또 다른 계층 하나를 반드시 포함한다.
  • 이들 아키텍처는 모두 시스템이 다음과 같은 특징을 지니도록 만든다.
프레임워크 독립성
  • 아키텍처는 프레임워크의 존재 여부에 의존하지 않는다.
  • 이를 통해 이러한 프레임워크를 도구로 사용할 수 있으며, 프레임워크가 지닌 제약사항안으로 시스템을 욱여 넣도록 강제하지 않는다.

테스트 용이성

  • 업무 규칙은 UI, DB, 웹서버 또는 여타 외부 요소 없이도 테스트 가능해야 한다.

UI 독립성

  • 시스템 나머지 부분을 변경하지 않고도 UI를 쉽게 변경할 수 있어야 한다.
  • 예를 들어, 업무 규칙을 변경하지 않은채 웹 UI를 콘솔 UI로 대체할 수 있어야 한다.

데이터베이스 독립성

  • 오라클이나 MSSQL 서버를 몽고DB, 빅테이블, 카우치DB 등으로 교체할 수 있어야 한다.
  • 업무 규칙은 DB 에 결합되지 않는다.

모든 외부 에이전시에 대한 독립성

  • 실제로 업무 규칙은 외부 세계와의 인터페이스에 대해 전혀 알지 못한다.

  • 아래 이미지(그림 22.1)의 다이어그램은 이들 아키텍처를 저부 실행 가능한 하나의 아이디어로 통합하려는 시도다.

image

의존성 규칙

  • 그림22.1에서 각각의 동심원은 소프트웨어에서 서로 다른 영역을 표현한다.
  • 보통 안으로 들어갈수록 고수준의 소프트웨어가 된다.
  • 바깥쪽 원은 메커니즘이고, 안쪽 원은 정책이다.
  • 이러한 아키텍처가 동작하도록 하는 가장 중요한 규칙은 의존성 규칙(Dependency Rule) 이다.

소스 코드 의존성은 반드시 안쪽으로, 고수준의 정책을 향해야 한다.

  • 내부 원에 속한 요소는 외부 원에 속한 어떤 것도 알지 못한다.
  • 특히 내부 원에 속한 코드는 외부 원에 선언된 어떤 것에 대해서도 그 이름을 언급해선 절대 안된다.
    • 여기엔 함수, 클래스, 변수, 그리고 소프트웨어 엔티티로 명명되는 모든 것이 포함된다.
  • 같은 이유로, 외부 원에 선언된 데이터 형식도 내부 원에서 절대로 사용해선 안된다.
    • 특히, 그 데이터 형식이 외부 원에 있는 프레임워크가 생성한 것이라면 더더욱 안된다.

엔티티

  • 엔티티는 전사적인 핵심 업무 규칙을 캡슐화한다.
  • 엔티티는 메서드를 가지는 객체이거나 일련의 데이터 구조와 함수의 집합일 수도 있다.
  • 기업의 다양한 애플리케이션에서 엔티티를 재사용할 수만 있다면, 그 형태는 그다지 중요치 않다.
  • 전사적이지 않은 단순한 단일 애플리케이션을 작성하고 있다면 엔티티는 해당 애플리케이션의 업무 객체가 된다.
    • 이 경우 엔티티는 가장 일반적이며 고수준인 규칙을 캡슐화한다.
  • 외부 무언가가 변경되더라도 엔티티가 변경될 가능성은 지극히 낮다.
    • 예를 들어 페이지 네비게이션이나 보안 관련 변경사항이 발생하더라도 업무 객체가 영향을 받지 않을 것이다.
  • 운영 관점에서 특정 애플리케이션에 무언가 변경이 필요하더라도 엔티티 계층에는 절대 영향주어선 안된다.

유스케이스

  • 유스케이스 계층은 애플리케이션에 특화된 업무 규칙을 포함한다. 그리고 시스템의 모든 유스케이스를 캡슐화하고 구현한다.
  • 유스케이스는 엔티티로 들어오고 나가는 데이터 흐름을 조정하며, 엔티티가 자신의 핵심 업무 규칙을 사용해서 유스케이스의 목적을 달성하도록 이끈다.
  • 이 계층에서 발생한 변경이 엔티티에 영향을 줘선 안된다.
    • 또한 DB, UI 또는 여타 공통 프레임워크와 같은 외부 요소에서 발생한 변경이 이 계층에 영향을 줘서도 안된다.
    • 유스케이스 계층은 이러한 관심사들로부터 격리되어 있다.
  • 하지만 운영 관점에서 애플리케이션이 변경된다면 유스케이스가 영향을 받으며, 따라서 이 계층의 소프트웨어에도 영향을 줄 것이다.
    • 유스케이스의 세부사항이 변하면 이 계층의 코드 일부는 분명 영향을 받을 것이다.

인터페이스 어댑터

  • 일련의 어댑터들로 구성된다.
  • 어댑터는 데이터를 유스케이스와 엔티티에게 가장 편리한 형식에서 DB나 웹과 같은 외부 에이전시에게 가장 편리한 형식으로 변환된다.
  • 예를 들어, 이 계층은 GUI의 MVC 아키텍처를 모두 포괄한다.
    • 프레젠터(Presenter), 뷰(View), 컨트롤러(Controller)는 모두 인터페이스 어댑터 계층에 속한다.
    • 모델은 그저 데이터 구조 정도이며, 컨트롤러에서 유스케이스로 전달되고, 다시 유스케이스에서 프레젠터와 뷰로 되돌아간다.
  • 마찬가지로 이 계층은 데이터를 엔티티와 유스케이스에게 가장 편리한 형식에서 영속성용으로 사용 중인 임의 프레임워크(즉, 데이터베이스)가 이용하기에 가장 편리한 형식으로 변환한다.
  • 이 원 안에 속한 어떤 코드도 데이터베이스에 대해 조금도 알아선 안된다.
  • 예컨대 SQL 기반의 DB 를 사용한다면 모든 SQL 은 이 계층을 벗어나선 안된다.
  • 특히 이 계층에서도 DB를 담당하는 부분으로 제한되어야 한다.

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

프레임워크와 드라이버

  • 그림22.1에서 가장 바깥쪽 계층은 일반적으로 DB나 웹 프레임워크 같은 프레임워크나 도구들로 구성된다.
  • 일반적으로 이 계층에선 안쪽 원과 통신하기 위한 접합 코드 외에는 특별히 더 작성해야할 코드가 그다지 많지 않다.
  • 프레임워크와 드라이버 계층은 모든 세부사항이 위치하는 곳이다.
  • 웹과 DB는 세부사항이며 모두 외부에 위치시켜서 피해를 최소화해야 한다. 그리고 가장 선택사항으로 열어둬야될 부분이다.

원은 네 개여야만 하나?

  • 그림22.1에서 표시한 원들은 그저 갠며을 설명하기 위한 하나의 예시일 뿐이며, 네 개보다 더 많은 원이 필요할 수도 있다.
  • 원이 네 개여야만하는 규칙은 없지만 어떤 경우에도 의존성 규칙은 적용된다.
    • 소스 코드 의존성은 항상 안쪽을 향한다.
    • 안쪽으로 이동할수록 추상화와 정책의 수준은 높아진다.
    • 가장 바깥쪽 원은 저수준의 구체적인 세부사항으로 구성된다.
    • 그리고 안쪽으로 이동할수록 소프트웨어는 점점 추상화되고 더 높은 수준의 정책들을 캡슐화한다.
    • 따라서 가장 안쪽 원은 가장 범용적이며 높은 수준을 가진다.

경계 횡단하기

  • 그림22.1의 우측 하단 다이어그램에 원의 경계를 횡단하는 방법을 보여주는 예시가 있다.
  • 이 예시에선 컨트롤러와 프레젠터가 다음 계층에 속한 유스케이스와 통신하는 모습을 확인할 수 있다.
  • 우선 제어흐름에 주목해보자. 컨트롤러에서 시작해서, 유스케이스를 지난 후, 프레젠터에서 실행되면서 마무리된다.
  • 소스 코드 의존성은 유스케이스를 향해 안쪽을 가리킨다.
  • 이처럼 제어흐름과 의존성의 방향이 명백히 반여야 하는 경우, 대체로 의존성 역전 원칙(DIP)를 사용하여 해결한다.
  • 예를 들어 자바 같은 언어에선 인터페이스와 상속 관계를 적절히 배치함으로써, 제어흐름이 경계를 가로지르는 바로 그 지점에서 소스 코드 의존성을 제어흐름과는 반대가 되게 만들 수 있다.

  • 예를 들어 유스케이스에서 프레젠터를 호출해야 한다고 가정해보자.
  • 이때 직접 호출하면 의존성 규칙(내부 원에서 외부 원에 있는 어떤 이름도 언급해선 안된다)을 위배하기 때문에 안된다.
  • 따라서 우리는 유스케이스가 내부 원의 인터페이스(그림 22.1의 ‘유스케이스 출력 포트’)를 호출하도록 하고, 외부 원의 프레젠터가 그 인터페이스를 구현하도록 만든다.

  • 아키텍처 경계를 횡단할때 언제라도 동일한 기법을 사용할 수 있다.
  • 우리는 동적 다형성을 이용하여 소스 코드 의존성을 제어흐름과는 반대로 만들 수 있고, 이를 통해 제어흐름이 어느 방향으로 흐르더라도 의존성 규칙을 준수할 수 있다.

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

  • 경계를 가로지르는 데이터는 흔히 간단한 데이터 구조로 기본적인 구조체나 간단한 데이터 전송 객체(data transfer object)등 원하는 대로 고를 수 있다.
  • 또는 함수를 호출할 때 간단한 인자를 사용해서 데이터로 전달할 수도 있다.
  • 그게 아니라며 데이터를 해시맵으로 묶거나 객체로 구성할 수도 있다.
  • 중요한점은 격리되어 있는 간단한 데이터 구조가 경계를 가로질러 전달된다는 사실이다.
  • 꾀를 부려서 엔티티 객체나 데이터베이스의 행(row)을 전달하는 일은 원치 않는다.
  • 우리는 데이터 구조가 어떤 의존성을 가져 의존성 규칙을 위배하게 되는일은 바라지 않는다.

  • 예를 들어 데이터를 행(row)구조 포맷으로 응답 받아서 내부에 전달 할 경우 내부 원에서는 외부 원 DB의 무언가를 알아야 하기 때문에 의존성 규칙이 위배될 수 있기 때문이다.
  • 따라서 경계를 가로질러 데이터를 전달할 때, 데이터는 항상 내부 원에서 사용하기에 가장 편리한 형태를 가져야만 한다.

전형적인 시나리오

image

  • 위 이미지(그림 22.2)의 다이어그램은 데이터베이스를 사용하는 웹 기반 자바 시스템의 전형적인 시나리오이며 상세 설명은 여기를 참고하자.
  • 주목할 점은 의존성 방향이다. 모든 의존성은 경계선을 안쪽으로 가로지르며, 따라서 의존성 규칙을 준수한다.

결론

  • 이상의 간단한 규칙들을 준수하는 일은 어렵지 않으며, 향후에 겪을 수많은 고통거리를 덜어줄 것이다.
  • 소트프웨어를 계층으로 분리하고 의존성 규칙을 준수한다면 본질적으로 테스트하기 쉬운 시스템을 만들게 될 것이며, 그에 따른 이점을 누릴 수 있다.
  • 데이터베이스나 웹 프레임워크와 같은 시스템의 외부 요소가 구식이 되더라도, 이들 요소를 야단스럽지 않게 교체할 수 있다.

Reference

This post is licensed under CC BY 4.0 by the author.

[클린아키텍처] 21장 소리치는아키텍처

[클린아키텍처] 23장 프레젠터와 험블 객체