5장 웹 어댑터 구현하기
의존성 역전
웹 어댑터는 '주도하는' 혹은 인커밍 어댑터다. 그림을 살펴보면 의존성 역전 원칙이 적용된 것을 발견할 수 있다.
어댑터와 유스케이스 사이에 또 다른 간접 계층을 넣어야 하는 이유는 애플리케이션 코어가 외부 세계와 통신할 수 있는 곳에 대한 명세가 포트이기 때문이다. 위 사진에서는 어댑터가 엄밀히 말하면 아웃고잉 포트의 역할까지 같이 수행한다.
5장에서는 웹 어댑터가 일반적인 인커밍 어댑터 역할만 한다고 가정한다.
웹 어댑터의 책임
웹 어댑터의 일반적인 역할은 다음과 같다
- HTTP 요청을 자바 객체로 매핑
- 권한 검사
- 입력 유효성 검증
- 입력을 유스케이스의 입력 모델로 매핑
- 유스케이스 호출
- 유스케이스의 출력을 HTTP로 매핑
- HTTP 응답 반환
웹 어댑터는 URL, HTTP 메서드, 컨텐츠 타입과 같이 특정 기준을 만족하는 HTTP 요청을 수신해야 한다. 보통은 웹 어댑터가 인증과 권한 부여를 수행하고 실패할 경우 에러를 반환한다. 앞에서 유효성 검증은 유스케이스 입력 모델의 책임이라고 언급했다.
유스케이스 입력 모델은 유스케이스의 맥락에서의 검증이고 여기서 얘기하는 유효성은 웹 어댑터의 입력 모델이다. 유스케이스와의 입력 모델과 구조나 의미가 다를 수 있으므로 다른 유효성 검증을 수행한다.
웹 어댑터의 입력 모델을 유스케이스읨 입력 모델로 변환할 수 있다. 는 것을 검증해야하며 이 변환을 방해하는 모든 것은 유효성 검증 에러라고 볼 수 있다.
이는 자연스럽게 웹 어댑터의 다음 책임, 즉 변환된 입력 모델로 특정한 유스케이스를 호출하는 것으로 연결된다. 어댑터는 유스케이스의 출력을 반환받고, HTTP 응답으로 직렬화해서 호출자에게 전달하고 과정 중 문제가 생기면 예외를 던져야 한다.
컨트롤러 나누기
모든 요청에 응답할 수 있는 하나의 컨트롤러를 만들 필요는 없다. 클래스들이 같은 소속이라는 것을 표현하기 위해 같은 패키지 수준에 놓아야한다. 너무 적은 것보다는 많은게 낫고, 컨트롤러가 가능한 좁고 다른 컨트롤러와 가능한 적게 공유하는 웹 어댑터 조각을 구현해야 한다.
유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까?
애플리케이션의 웹 어댑터를 구혈할 떄는 HTTP 요청을 애플리케이션의 유스케이스에 대한 메서드 호출로 변환하고 결과를 다시 HTTP로 변환하고 어떤 도메인 로직도 수행하지 않는 어댑터를 만들고 있다는 점을 생각해야 한다.
애플리케이션 계층은 HTTP에 대한 상세 정보를 노출시키지 않도록 HTTP와 관련된 작업을 하면 안 된다. 이렇게 하면 웹 어댑터를 다른 어댑터로 쉽게 교체할 수 있다. 웹 컨트롤러를 나눌 때는 모델을 공유하지 않는 여러 작은 클래스들을 만드는 것을 두려워해서는 안 된다. 작은 클래스들이 더 파악하기 쉽고, 테스트하기 쉽고, 동시 작업을 지원한다.
만들때는 힘들겠지만 유지보수하는 동안에 빛을 볼 것이다.
6장 영속성 어댑터 구현하기
레이어드 아키텍처의 단점인 '데이터베이스 주도 설계'를 의존성 역전시키기 위해 영속성 계층을 애플리케이션 계층의 플러그인으로 만드는 방법을 다룬다.
의존성 역전
애플리케이션 서비스에서는 영속성 기능을 사용하기 위해 포트 인터페이스를 호출한다. 이 포트는 실제로 영속성 작업을 수행하고 데이터베이스와 통신할 책임을 가진 영속성 어댑터 클래스에 의해 구현되며, 육각형 아키텍처에서 영속성 어댑터는 '주도되는' 혹은 '아웃고잉' 어댑터다. 애플리케이션에 의해 호출될 뿐, 애플리케이션을 호출하지는 않는다.
포트는 영속성과 애플리케이션 사이의 간접 계층이다. 영속성 계층에 대한 코드 의존성을 없애기 위해 이런 간접 계층을 추가하고 있는 것이다. 영속성 코드를 리팩터링 하더라도 코어 코드를 변경하는 결과로 이어지지 않는다.
영속성 어댑터의 책임
- 일반적으로 영속성 어댑터가 하는 역할은 다음과 같다.
- 입력을 받는다
- 입력을 데이터베이스 포맷으로 매핑한다.
- 입력을 데이터베이스로 보낸다
- 데이터베이스 출력을 애플리케이션 포맷으로 매핑한다.
- 출력을 반환한다.
영속성 어댑터는 포트 인터페이스를 통해 입력을 받는다. 입력 모델은 인터페이스가 지정한 도메인 엔티티나 특정 데이터베이스 연산 전용 객체다. 영속성 어댑터는 데이터베이스를 쿼리하거나 변경하는 데 사용할 수 있는 포맷으로 입력 모델을 매핑한다.
자바에서는 JPA를 사용하면 입력 모델을 데이터베이스 테이블 구조를 반영한 JPA 엔티티 객체로 매핑할 것이다. JPA나 다른 객체-관계 매핑 프레임워크 대신, 데이터베이스와 통신하기 위해 어떤 기술을 사용해도 상관없고, 핵심은 영속성 어댑터의 입력 모델이 영속성 어댑터 내부에 있는 것이 아니라 애플리케이션 코어에 있기 때문에 영속성 어댑터 내부를 변경하는 것이 코어에 영향을 미치지 않는다는게 중요하고, 영속성 어댑터는 데이터베이스에 쿼리를 날리고 쿼리 결과를 받아온다.
마지막으로, 데이터베이스 응답을 포트에 정의된 출력 모델로 매핑해서 반환한다. 출력 모델은 영속성 어댑터가 아니라 애플리케이션 코어에 위치하는 것이 중요하다.
포트 인터페이스 나누기
그림처럼 특정 엔티티가 필요로 하는 모든 데이터베이스 연산을 하나의 레포지토리 인터페이스에 넣어 두는 게 일반적인 방법이다. 하나의 아웃고잉 포트 인터페이스에 모든 데이터베이스 연산을 모아두면 모든 서비스가 실제로는 필요하지 않은 메서드에 의존하게 된다.
데이터베이스 연산에 의존하는 각 서비스 인터페이스에서 단 하나의 메서드만 사용하더라도 하나의 '넓은' 인터페이스에 의존성을 갖게 된다.
로버트 C. 마틴 : 필요없는 화물을 운반하는 무언가에 의존하고 있으면 예상하지 못했던 문제가 생길 수 있다.
인터페이스 분리 원칙(Interface, Segregation Principle, ISP)이 이 문제의 해답이다. 클라이언트가 오로지 자신이 필요로 하는 메서드맘ㄴ 알면 되도록 넓은 인터페이스를 특화된 인터페이스로 분리해야 한다.
인터페이스 분리 원칙을 적용하면 불필요한 의존성을 제거하고 기존 의존성을 눈에 더 잘 띄게 만들 수 있다.
이렇게 나누면, 서비스는 각 실제로 필요한 메서드에만 의존하고, 포트의 이름이 역할을 명확하게 나타내고, 테스트에서는 어떤 메서드를 모킹할지 고민할 필요가 없다.
이런 방식으로 매우 좁은 포트를 만드는 것은 코딩을 plug-and-play 경험으로 만든다. 서비스 코드를 짤 때는 필요한 포트에 그저 '꽂기만' 하면 된다.
영속성 어댑터 나누기
이전 그림에서는 모든 영속성 포트를 구현한 단 하나의 영속성 어댑터 클래스가 있었다. 모든 영속성 포트를 구현하는 한, 하나 이상의 클래스 생성을 금지하는 규칙은 없다.
영속성 연산이 필요한 도메인 클래스 하나당 하나의 영속성 어댑터를 구현하는 방식을 선택할 수 있다. (DDD의 애그리거트)
위 사진처럼 구현하면 영속성 어댑터들은 각 영속성 기능을 이용하는 도메인 경계를 따라 자동으로 나뉘어 진다. 영속성 어댑터를 훨씬 더 많은 클래스로 나누는 경우는 JPA나 OR 매퍼를 이용한 영속성 포트도 구현하면서 성능을 개선하기 위해 평범한 SQL을 이용하는 다른 종류의 포트도 함께 구현하는 경우다.
도메인 코드는 영속성 포트에 의해 정의된 명세를 어떤 클래스가 충족시키는지에는 관심이 없고, 모든 포트가 구현되어 있으면 영속성 계층에서 하고 싶은 어떤 작업이든 해도 된다.
데이터베이스 트랜잭션은 어떻게 해야 할까?
트랜잭션은 하나의 특정한 유스케이스에 대해서 일어나는 모든 쓰기 작업에 걸쳐 있어야 한다. 그래야 작업 중 실패하면 롤백되기 때문이다.
영속성 어댑터는 어떤 데이터베이스 연산이 같은 유스케이스에 포함되는지 알지 못하기 때문에 언제 트랜잭션을 열고 닫을지 결정할 수 없다. 이 책임은 영속성 어댑터 호출을 관장하는 서비스에 위임해야 하며, 가장 쉬운 방법은 @Transactional 애너테이션을 서비스 클래스에 붙이는 것이다. 서비스가 오염되지 않고 깔끔하게 유지되길 원한다면 AspectJ 같은 도구를 이용해 트랜잭션 경계를 코드에 위빙(weaving)할 수 있다.
유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까?
도메인 코드에 플러그인처럼 동작하는 영속성 어댑터를 만들면 도메인 코드가 영속성과 관련된 것들로부터 분리되어 풍부한 도메인 모델을 만들 수 있다.
좁은 포트 인터페이스를 사용하면 포트마다 다른 방식으로 구현할 수 있는 유연함이 생기고 포트 뒤에서 애플리케이션이 모르게 다른 영속성 기술을 사용해도 된다. 포트의 명세만 지켜진다면 영속성 계층 전체를 교체할 수도 있다.
'etc' 카테고리의 다른 글
[react] openlayers - react - vworld (3) | 2024.10.22 |
---|---|
[Network] react sprignboot CORS (9) | 2024.10.09 |
[만들면서 배우는 클린 아키텍처] 8장, 9장, 10장 정리 (0) | 2024.09.14 |
[만들면서 배우는 클린 아키텍처] 7장 정리 (1) | 2024.09.14 |
[만들면서 배우는 클린 아키텍처] 1장, 2장, 3장, 4장 정리 (0) | 2024.09.14 |