영속성 코드를 위해 계층이라는 얇은 수평 조각으로 나뉜다.
각 계층은 주로 패키지로 묶는 도구로 사용된다.
의존성은 모두 아래를 향하게 된다.
다음과 같은 자바 타입이 존재한다.
- OrdersController : 웹 컨트롤러이며 웹 기반 요청을 처리한다.
- OrderService : 업무 규칙을 정의하는 인터페이스
- OrderServiceImpl : OrderService의 구현체
- OrderRepository : 영구 저장된 주문 정보에 접근하는 방법을 정의
- JdbcOrdersRepository : OrderRepositroy의 구현체 이 방법을 이용하면 엄청난 복잡함을 겪지 않고 무언가를 작동시켜주는 아주 빠른 방법이다. 업무 도메인에 대해 아무것도 이야기 해주지 않는 문제가 발생한다. 업무 도메인이 다르더라도 모두 아키텍쳐가 동일하게 보인다.
서로 연관된 기능, 도메인 개념(Aggregate Root)에 수반하여 수직의 얇은 조각으로 코드를 나눈다.
이전 이미지와 인터페이스 클래스 구조가 같지만 3개의 패키지가 아닌 하나의 패키지에 속해있다. 코드는 상위 수준 구조가 업무 도메인에대해서 알려주게 된다.
이점으로 코드가 하고자 하는 목적을 명확히 할 수 있으며, 유스케이스가 변경될 경우 변경되야할 코드를 찾는 작업이 쉬워질 수 있다.
업무/도메인에 초점을 둔 코드가 프레임워크나 데이터베이스 같은 기술적인 세부 구현과 독립적이며 분리된 아키텍처를 만들기 위해 나온 구조이다.
내부 - 도메인 개념을 모두 포함
외부 - UI, DB, 서드파티 통합
여기서 핵심되는 이유는 외부가 내부에 의존하며 절대 반대가 되어서는 안된다.
com.mycompany.myapp.domain 패키지가 내부이며 나머지 패키지는 외부이다.
보면 알겠지만 orderRepository에서 orders라는 간단한 이름으로 변경된 것을 알 수 있다.
도메인 주도 방식에서 사용하는 언어로 내부에 존재하는 언어는 모두 유비쿼터스 도메인 언어 관점에서 기술해야한다. 그 도메인은 "주문"을 이야기 하는거지 "주문 레포지터리"에 대해서 말하는 것이 아니다.
대다수 전통적인 아키텍처의 특징은 계층형 아키텍처를 기반으로 했다. 계층형 아키텍처는 목적이 기능이 같은 코드끼리 서로 분리하는 것이다. 웹관련 코드는 업무로직과는 분리하고 업무 로직은 데이터 접근으로부터 분리한다. 이러한 계층 기반 구조를 보면 의존성의 화살표가 반드시 아래로 향해 비순환 의존성 그래프를 만들 수 있을거라 생각하지만 여러가지 편법을 사용하더라도 "겉보기"에는 완벽한 구조를 유지한다고 보일 수도 있다. 예를들어 컨트롤러에서 서비스를 거치지않고 Repository를 직접적으로 조회하는 경우가 생긴다. 이러한 경우가 허용되기 때문에 완화된 계층형 아키텍처라 부른다. 이는 CQRS패턴을 지키려고 시도하는 경우가 있는데 이는 의도된 것이지만 이를 제외하고는 업무로직을 우회하는 경우는 좋지 않다. 그래서 우리는 "웹 컨트롤러는 절대로 저장소에 접근해서는 안된다"와 같은 원칙이 필요하다. 이러한 규율을 정적 분석 도구에 강제해 놓을 수 있다.
이 접근법은 큰단위의 단일 컴포넌트와 관련된 모든 책임을 하나의 자바 패키지로 묶는 데 주안점을 둔다. 이 접근법을 통해 서비스 중심적인 시각으로 소프트웨어 시스템을 바라본다.
마이크로서비스 아키텍처도 이와 같은 시각을 공유한다. 포트와 어뎁터에서 웹을 그저 또 다른 전달 메커니즘으로 취급하게 되는데, 컴포넌트 기반 패키지에서도 사용자 인터페이스를 큰 단위의 컴포넌트로부터 분리해 유지한다. 아래 그림을 보면 더 알기 쉬울 것 이다.
이 접근법에서는 업무로직과 업무 로직과 관련된 코드를 하나로 묶으며, 이 책에서는 '컴포넌트'라구 부른다.
이전에 배포가 가능한 가장 작은 단위를 컴포넌트라고 정의 하였다.
이때 분리되는 가장 큰 기준은 바로 "직교적인 관심사"이다.
이를 통해서 주문과 관련된 무언가를 코딩할 때, OrderComponent만 보면 된다는 장점을 가지고 있다. 또한 업무로직은 데이터 영속성과 분리되어있다. 하지만 이는 컴포넌트 구현과 관련된 세부사항으로 사용자들은 알 필요가 없다.
이렇게 결합 분리 모드가 잘 되어 있으면 모노리틱 어플리케이션을 마이크로로 전환하기위한 발판으로 삼을 수 있다.
이 장들을 보다보면 접근법들이 모두 다른 것 처럼 보이지만 세부사항을 잘못 구현하면 이러한 견해도 빠르게 흐트러질 수 있다. 이를 해치는 가장 큰 문제점은 public 키워드이기 때문에 적절하게 사용해야 한다.
자바에서 전부 public으로 선언하면 패키지는 단순히 조직화를 위한 매커니즘일 뿐 캡슐화가 불가능해진다. 모두 public으로 선언하면 아래의 그림의 아키텍처 접근법은 모두 동일해 지게 된다.
public로 모두 선언하면 따라서 수평형 아키텍처를 4가지로 표현 가능하다는 표현과 같지만 public으로만 선언하면 굉장히 피곤해진다. 따라서 접근자는 잘 사용하는것이 맞다.
왼쪽 아키텍처부터 하나씩 보자.
- '계층 기반 패키지' 접근법에서 Order Service와 OrdersRepository는 인터페이스는 외부 패키지의 클래스로부터 자신이 속한 패키지 내부로 들어오는 의존성이 존재한다. 그러므로 반드시 public이어야 한다. 그러나 구현체는 더 제한적으로 선언할 수 있다.(외부에서 구현체를 알 필요는 없기 때문)
- '기능 기반 패키지' 이 경우에는 컨트롤러만 접근가능한 패키지이므로 나머지 모두 protected로 지정할 수 있다.
- '포트와 어댑터' 접근법의 경우 OrderService와 Orders는 외부로 부터 들어오는 의존성을 가지므로 public을 지정해야 한다. 그 외의 것들은 protected로 지정하며 런타임에 의존성을 주입할 수 있다.
- '컴포넌트 기반 패키지' 은 컨트롤러에서 OrdersComponent 인터페이스로 향하는 의존성을 가지며 나머지는 패키지 protected로 지정 가능하다.
public을 제한적으로 사용함으로서 불필요한 의존성의 수가 적어진다. 이재 패키지 외부에서는 OrdersRepository에 접근할 방법이 없다.
아키텍처를 강제할때는 반드시 컴파일러에 의지하도록 하자.
언어가 제공하는 방법 말고도 소스 코드 의존성을 분리하는 방법이 존재할 수 있다. 예를들어 OSGi 같은 모듈 프레임워크나 자바9에서 제공하는 새로운 모듈 시스템이 있다. 모듈 시스템을 통해 외부에 공표할 타입을 분리할 수 있다. 이러한 모듈을 사용하는 방법 외에 소스코드 수준에서 의존성을 분리할 수 있다. 정확하게는 서로 다른 소스 코드 트리로 분리하는 방법이다. 다음 예를 보자. 다음 예는 포트와 어댑터를 예로 들었다.
- 업무와 도메인용 소스코드(OrdersService, OrdersServiceImpl, Orders)
- 웹용 소스코드(OrdersController)
- 데이터 영속성용 소스코드(JdbcOrderRepository)
마지막 두 소스 코드 트리는 엄무와 도메인 코드에 대해 컴파일 시점에 의존성을 가지며 업무와 도메인 코드 자체는 웹이나 데이터 영속성 코드에 대해 알지 못한다. 이를 구현관점에서 빌드도구(메이븐이나 그레들)을 통해 분리되도록 구성하며 애플리케이션을 구성하는 모든 컴포넌트 각각을 개별적인 소스트리로 구성해야한다. 하지만, 이는 이상적인 해결책이며 이렇게 나누면 유지보수가 매우 힘들어 질 수 있다.
이를 간단하게
- 도메인 코드('내부')
- 인프라 코드('외부') 로만 나눠서 포트 어댑터 접근법을 적용하기도 한다.
하지만 위 처럼 구성하면 인프라 코드가 한소스코드에 몰려있게되고 도메인을 거치지 않고 웹 컨트롤러 영역에서 영속성 영역에 바로 접근하도록 설계할 수 있기 떄문에 적절한 접근 지시자를 잘 사용해야 한다.