[Clean Architecture] 22장. 클린 아키텍처
|Introduction
- 지난 수십 년간 우리는 시스템 아키텍처와 관련된 여러 가지 아이디어를 봄
- 육각형 아키텍처
- hexagonal architecture
- 포트와 어댑터 ports and adapters 라고도 알려져 있음
- Alistair Cockburn 이 개발
- Steve Freeman 과 Nat Pryce 가 “테스트 주도 개발로 배우는 객체 지향 설계와 실천”에서 차용
- DCI
- Data, Context and Interaction
- James Coplien 과 Trygve Reenskaug 가 만듦
- BCE
- Boundary-Control-Entity
- 이바 야콥슨이 “Object Oriented Software Engineering” 에서 소개함
- 순서를 바꿔 ECB 라고도 많이 씀
- 육각형 아키텍처
- 이들 아키텍처는 모두 세부적인 면에서는 다소 차이가 있더라도 그 내용은 상당히 비슷
- 이들의 목표는 모두 같음 → 관심사의 분리 separation of concerns
- 소프트웨어를 계층으로 분리함으로서 관심사의 분리라는 목표를 달성할 수 있었음
- 이들 아키텍처는 모두 시스템이 다음과 같은 특징을 지니도록 만듦
- 프레임워크 독립성
- 아키텍처는 프레임워크의 존재 여부에 의존하지 않음
- 이를 통해 프레임워크를 도구로 사용할 수 있음
- 프레임워크가 지닌 제약사항안으로 시스템을 욱여 넣도록 강제하지 않음
- 테스트 용이성
- 업무 규칙은 UI, 데이터베이스, 웹 서버, 또는 외부 요소가 없이도 테스트할 수 있음
- UI 독립성
- 시스템의 나머지 부분을 변경하지 않고도 UI 를 쉽게 변경할 수 있음
- 데이터베이스 독립성
- 기존 데이터베이스를 다른 데이터베이스로 교체할 수 있음
- 업무 규칙은 데이터베이스에 결합되지 않음
- 모든 외부 에이전시에 대한 독립성
- 실제 업무 규칙은 외부 세계와의 인터페이스에 대해 전혀 알지 못함
- 프레임워크 독립성
의존성 규칙
- 클린아키텍처 그림
- 각각의 동심원은 소프트웨어에서 서로 다른 영역을 표현
- 보통 안으로 들어갈수록 고수준의 소프트웨어가 됨
- 바깥쪽 원은 메커니즘이고, 안쪽 원은 정책
- 이러한 아키텍처가 동작하도록 하는 가장 중요한 규칙은 의존성 규칙 (Dependency Rule)
소스 코드 의존성은 반드시 안쪽으로, 고수준의 정책을 향해야 한다
- 내부의 원에 속한 요소는 외부의 원에 속한 어떤 것도 알지 못함
- 내부의 원에 속한 코드는 외부의 원에 선언된 어떤 것에 대해서도 그 이름을 언급해서는 안 됨
- 같은 이유로, 외부의 원에 선언된 데이터 형식도 내부의 원에서 절대로 사용해서는 안 됨
- 특히 그 데이터 형식이 외부의 원에 있는 프레임워크가 생성한 것이라면 더더욱 사용해서는 안됨
- 우리는 외부의 원에 위치한 어떤 것도 내부의 원에 영향을 주지 않기를 바람
엔티티
- 전사적인 핵심 업무 규칙을 캡슐화
- 메서드를 가지는 객체이거나 일련의 데이터 구조화 함수의 집합일 수도 있음
- 전사적이지 않은 단순한 단일 애플리케이션을 작성하고 있다면 엔티티는 해당 애플리케이션의 업무 객체가 됨
- 이 경우 엔티티는 가장 일반적이며 고수준인 규칙을 캡슐화
- 외부의 무언가가 변경되더라도 엔티티가 변경될 가능성은 지극히 낮음
- 운영 관점에서 특정 애플리케이션에 무언가 변경이 필요하더라도 엔티티 계층에는 절대로 영향을 주면 안 됨
유스케이스
- 유스케이스 계층의 소프트웨어는 애플리케이션에 특화된 업무 규칙을 포함함
- 유스케이스 계층의 소프트웨어는 시스템의 모든 유스케이스를 캡슐화하고 구현
- 엔티티로 들어오고 나가는 데이터 흐름을 조정
- 엔티티가 자신의 핵심 업무 규칙을 사용해서 유스케이스의 목적을 달성하도록 이끔
- 이 계층에서 발생한 변경이 엔티티에 영향을 줘서는 안 됨
- 외부요소에서 발생한 변경이 이 계층에 영향을 줘서도 안 됨
- 유스케이스 계층은 이러한 관심사로부터 격리되어 있음
- 운영 관점에서 애플리케이션이 변경된다면 유스케이스가 영향을 받으며, 따라서 이 계층의 소프트웨어에도 영향을 줄 것
- 유스케이스의 세부사항이 변하면 이 계층의 코드 일부는 분명 영향을 받을 것
인터페이스 어댑터
- 인터페이스 어댑터 Interface Adapter 계층은 일련의 어댑터들로 구성됨
- 어댑터는 데이터를 유스케이스와 엔티티에게 가장 편리한 형식에서 데이터베이스나 웹 같은 외부 에이전시에게 가장 편리한 형식으로 변환
- 이 계층은 데이터를 엔티티와 유스케이스에게 가장 편리한 형식에서 영속성용으로 사용 중인 임의의 프레임워크(즉, 데이터베이스)가 이용하기에 가장 편리한 형식으로 변환함
- 이 원 안에 속한 어떤 코드도 데이터베이스에 대해 조금도 알아서는 안 됨
- SQL 기반의 데이터베이스를 사용한다면 모든 SQL 은 이 계층을 벗어나서는 안 됨
- 특히 이 계층에서도 데이터베이스를 담당하는 부분으로 제한되어야 함
- 또한 이 계층에는 데이터를 외부 서비스와 같은 외부적인 형식에서 유스케이스나 엔티티에서 사용되는 내부적인 형식으로 변환하는 또 다른 어댑터가 필요함
프레임워크와 드라이버
- 일반적으로 이 계층에서는 안쪽 원과 통신하기 위한 접합 코드 외에는 특별히 더 작성해야 할 코드가 그다지 많지 않음
- 프레임워크나 드라이버 계층은 모든 세부사항이 위치하는 곳
- 웹, 데이터베이스는 모두 세부사항이고 이러한 것들을 모두 외부에 위치시켜서 피해를 최소화함
원은 네 개여야만 하나?
- 하나의 예시일 뿐 더 많은 원이 필요할 수도 있음
- 항상 네 개만 사용해야 한다는 규칙은 없음
- 단, 어떤 경우에도 의존성 규칙은 적용됨
- 소스 코드 의존성은 항상 안쪽으로 향함
- 안쪽으로 이동할수록 추상화와 정책의 수준은 높아짐
- 가장 바깥쪽 원은 저수준의 구체적인 세부사항으로 구성됨
- 안쪽으로 이동할수록 소프트웨어는 점점 추상화되고 더 높은 수준의 정책들을 캡슐화함
- 따라서 가장 안쪽 원은 가장 범용적이며 높은 수준을 가짐
경계 횡단하기
- 그림 22.1 우측하단
- 제어흐름과 의존성 방향이 명백히 반대여야 하는 경우, 의존성 역전 원칙을 사용하여 해결
- 별다른 조치 없이 제어흐름을 따라 구현하면 안쪽 원의 코드가 바깥쪽 원의 코드를 호출하게 됨
- 하지만 바로 이 지점에서 소스 코드 의존성을 역전시키면, 제어흐름과는 반대로 바깥쪽 원의 코드가 안쪽 원의 코드를 호출하게 만든다는 뜻
- 예를 들어 유스케이스에서 프레젠터를 호출해야 한다고 가정
- 직접 호출하면 의존성 규칙을 위배하게 됨
- 따라서 우리는 유스케이스가 내부 원의 인터페이스를 호출하도록 하고, 외부 원의 프레젠터가 그 인터페이스를 구현하도록 만듦
- 아키텍처 경계를 횡단할 때 언제라도 동일한 기법을 사용할 수 있음
- 우리는 동적 다형성을 이용하여 소스 코드 의존성을 제어흐름과는 반대로 만들 수 있고,
- 이를 통해 제어흐름이 어느 방향으로 흐르더라도 의존성 규칙을 준수할 수 있음
경계를 횡단하는 데이터는 어떤 모습인가
- 경계를 가로지르는 데이터는 흔히 간단한 데이터 구조로 이루어짐
- 중요한 점은 격리되어 있는 간단한 데이터 구조가 경계를 가로질러 전달된다는 사실
- 우리는 데이터 구조가 어떤 의존성을 가져 의존성 규칙을 위배하게 되는 일은 바라지 않음
- 경계를 가로질러 데이터를 전달할 때, 데이터는 항상 내부의 원에서 사용하기에 가장 편리한 형태를 가져야만 함
전형적인 시나리오
- 데이터베이스를 사용하는 웹 기반 자바 시스템의 전형적인 시나리오
- 의존성 방향에 주목
- 모든 의존성은 경계선을 안쪽으로 가로지르며, 따라서 의존성 규칙을 준수함
결론
- 이상의 간단한 규칙을 준사하는 일은 어렵지 않음
- 향후에 겪을 수많은 고통거리를 덜어줄 것
- 소프트웨어를 계층으로 분리하고 의존성 규칙을 준수하면, 본질적으로 테스트하기 쉬운 시스템을 만들게 될 것이며, 그에 따른 이점을 누릴 수 있음