5 분 소요

만들면서 배우는 클린 아키텍쳐 5장 정리

웹 어댑터 구현하기

오늘날의 애플리케이션은 대부분 웹 브라우저를 통해 상호작용할 수 있는 UI나 다른 시스템에서 애플리케이션을 상호작용하는 HTTP API와 같은 웹 인터페이스를 제공합니다.

의존성 역전

웹 어댑터는 ‘인커밍’ 어댑터입니다.

클라이언트로부터 요청을 받아 애플리케이션 코어를 호출하고 무슨 일을 해야 할지 알려주기 때문에 애플리케이션을 주도하는 어댑터입니다.

애플리케이션 코어와 웹 어댑터 사이에 통신이 가능하려면 애플리케이션 코어가 웹 어댑터에 맞는 포트를 제공해야 합니다.

이 포트가 인터페이스이며, 애플리케이션 코어에서는 유스케이스 클래스가 이 인터페이스를 구현합니다.

이렇게 되면 웹 어댑터에서는 이 인터페이스를 가지고 메소드를 호출하고, 그러면 실제로는 이를 구현한 유스케이스 클래스의 메소드가 실행됨으로써 서로 통신을 하게 되는 것입니다.

이러한 제어 흐름은 웹 어탭터에 있는 컨트롤러에서 애플리케이션 계층에 있는 서비스로 흐르는데 여기서 의존성 역전 원칙이 적용됩니다.

그래서 굳이 웹 어댑터와 유스케이스 사이에 또 다른 간접 계층을 두지 않고 웹 어댑터에서 바로 유스케이스를 호출할 수 있습니다.

이것은 제가 우아한 스터디 모임 당시 3장에서 질문했던 사항입니다.

웹 어댑터가 유스케이스를 직접 호출하면 안되는 이유

애플리케이션 코어가 외부 세계와 통신할 수 있는 곳에 대한 명세가 포트이기 때문입니다.

포트를 적절한 곳에 위치시키면 외부와 어떤 통신이 일어나고 있는지 정확히 알 수 있고, 이는 레거시 코드를 다루는 유지보수 엔지니어에게는 매우 소중한 정보이기 때문입니다.

그렇다면 웹 소켓을 통해 실시간 데이터를 사용자의 브라우저로 보내는 경우 애플리케이션 코어와 컨트롤러 사이에 상호작용이 많이 발생하게 되는데 이 때는 어떻게 해야 할까요?

이 때 웹 어댑터는 실시간 상호작용으로 인해 인커밍 어댑터인 동시에 아웃고잉 어댑터 두 가지 역할 모두 수행핟게 됩니다.

하지만 이번 장의 나머지 부분에서는 웹 어댑터가 일반적으로 인커밍 어댑터 역할만 한다고 가정하고 설명합니다.

웹 어댑터의 책임

웹 어댑터는 일반적으로 다음과 같은 일을 합니다.

  1. HTTP 요청을 자바 객체로 매핑
  2. 권한 검사
  3. 입력 유효성 검증
  4. 입력을 유스케이스의 입력 모델로 매핑
  5. 유스케이스 호출
  6. 유스케이스의 출력을 HTTP로 매핑
  7. HTTP 응답을 반환

1. HTTP 요청을 자바 객체로 매핑

웹 어댑터는 URL, 경로, HTTP 메서드(POST, GET, UPDATE/PATCH, DELETE 등), 콘텐츠 타입과 같이 특정 기준을 만족하는 HTTP 요청의 파라미터와 콘텐츠를 객체로 역직렬화합니다.

2. 권한 검사

웹 어댑터는 인증과 권한 부여를 수행하고, 실패할 경우(권한이 없어서 인증이 되지 않는다면) 에러를 반환합니다.

3. 입력 유효성 검증

웹 어댑터는 객체의 상태 유효성 검증을 합니다.

이 유효성 검증은 유스케이스의 입력 모델이 하는 입력 유효성 검증과 다릅니다.

유스케이스의 입력 모델이 하는 입력 유효성 검증은 유스케이스의 맥락에서 유효한 입력만 검증합니다.

하지만 웹 어댑터의 입력 모델은 유스케이스의 입력 모델과는 구조나 의미가 완전히 다를 수 있기 때문에 별도로 또 다른 유효성 검증을 수행해야 합니다.

여기서 주의할 점은 웹 어댑터에의 입력 유효성 검증에서 유스케이스 입력 모델에서 했던 유효성 검증을 똑같이 반복하면 안된다는 것입니다.

대신에 웹 어댑터의 입력 유효성에서는 웹 어댑터의 입력 모델을 유스케이스의 입력 모델로 변환할 수 있다는 것을 검증해야 합니다.

4. 입력을 유스케이스의 입력 모델로 매핑

이렇게 웹 어댑터의 입력 유효성 검증이 통과되면 유스케이스의 입력 모델로 매핑시킵니다.

5. 유스케이스 호출

유스케이스의 입력 모델로 매핑이 큰나면 드디어 포트(인터페이스)를 호출하고, 실제로는 이를 구현한 유스케이스 클래스가 실행됩니다.

6. 유스케이스의 출력을 HTTP로 매핑

유스케이스 클래스가 실행되고, 그 결과를 반환받는데 이를 HTTP 형식에 맞게 다시 직렬화합니다.

7. HTTP 응답을 반환

HTTP형식에 맞게 직렬화된 데이터를 처음에 HTTP를 요청한 곳으로 반환합니다.

이 과정에서 하나라도 문제가 생기면 예외를 던지고, 웹 어댑터는 에러를 호출자에게 보여줄 메세지로 변환해야 합니다.

이러한 과정들은 애플리케이션 계층에서는 전혀 신경쓰면 안되는 것들입니다.

왜냐하면 애플리케이션 계층에서 위의 HTTP와 관련된 코드가 있을 경우 만약에 HTTP를 사용하지 않는 인커밍 어댑터를 새로 만든다면 도메인 로직을 다시 수정해야 하는 상황이 발생합니다.

즉, HTTP 코드가 도메인 로직에 들어가는 순간 도메인 로직은 HTTP 인커밍 어댑터에 종속되어 다른 인커밍 어탭터를 선택할 수 없게 됩니다.

좋은 아키텍처는 이러한 종속 관계 없이 선택의 여지를 남겨둬야 하기 때문에 도메인 로직에는 어떠한 HTTP 코드도 존재해서는 안됩니다.

컨트롤러 나누기

클라이언트의 모든 요청에 응답할 수 있는 하나의 만능 컨트롤러(웹 어댑터)는 좋지 않습니다.

그보다는 클라이언트의 각 요청마다 전용으로 컨트롤러를 만드는 것이 더 좋습니다.

즉, 컨트롤러는 가능한 한 좁고 다른 컨트롤러와 가능한 한 적게 공유하기 위해 클라이언트의 모든 요청마다 세분화하는 것이 좋습니다.

다만 컨트롤러 클래스들이 같은 소속이라는 것을 나타내기 위해서 같은 패키지에 넣어주면 됩니다.

모든 요청에 응답할 수 있는 하나의 만능 컨트롤러(웹 어댑터)의 안 좋은 점으로 일단 코드가 너무 많다는 것입니다.

이렇게 되면 당연히 이 컨트롤러를 테스트 하는 코드이 양도 엄청나게 많아집니다.

한 클래스에 코드량이 많아질수록 그 클래스를 파악하는데 시간이 많이 듭니다.

이는 유지보수를 하는 입장에서 좋은 일이 아닙니다.

또한 클라이언트의 모든 요청을 단일 컨트롤러에서 처리하다 보면 데이터 구조를 재활용하는 경우가 빈번히 발생합니다.

단일 컨트롤러에서 모든 처리를 하다 보니 이 모든 처리를 하기 위해 필요한 데이터를 저장하는 클래스를 만듭니다.

그렇게 되면 이 데이터 클래스는 모든 요청에 사용할 수 있습니다.

거기까지는 좋지만 이 데이터 클래스가 너무 크기 때문에 각각의 요청에 필요없는 정보도 담고 있다는 것이 문제입니다.

데이터를 새로 추가할 때 필요한 정보도 가지고 있고, 데이터를 갱신할 때 필요한 정보도 가지고 있기 때문에 나중에 프로그램이 복잡해졌을 때, 갱신을 하는 특정 상황에서 모든 상황을 다 care하는 큰 데이터 구조때문에 오히려 무엇이 진짜 필요한 정보이고, 무엇은 필요없는 정보인지 파악하는 것이 힘들어집니다.

그래서 이를 방지하기 위해서 클라이언트의 요청마다 별도의 패키지 안에 별도의 컨트롤러를 만드는 방식이 좋습니다.

이 때 가급적이면 메서드 명과 클래스 명은 유스케이스를 최대한 반영해서 잘 지어야 합니다.

이렇게 각 요청마다 전용 컨트롤러를 생성하게 되면 컨트롤러마다 각자에 꼭 필요한 데이터 구조를 가지는 전용 모델 클래스들을 별도로 만들게 될 확률이 높고, 그렇게 되면 앞에서 이야기했던 큰 데이터구조인한 불필요한 문제들을 방지할 수 있습니다.

또한 전용 모델클래스는 각 컨트롤러의 private 멤버로 설정되기 때문에 다른 곳에서 사용하는 실수를 방지할 수 있습니다.

이렇게 세분화 된 컨트롤러의 또다른 장점으로는 개발자들이 동시 작업을 수행하기 용이하다는 점입니다.

예를 들어 만능 컨트롤러의 경우 한 개발자가 CREATE부분을 수정할 때, 다른 개발자는 UPDATE부분을 수정할 수 있는데 문제는 이 두 개발자 모두 하나의 클래스에서 작업을 수행하기 때문에 동시성 문제가 발생할 수 있다는 것입니다.

하지만 CREATE 전용 컨트롤러와 UPDATE 전용 컨트롤러가 별도로 존재한다면 이러한 동시성 문제로 인한 병합 충돌을 피할 수 있습니다.

유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까?

웹 어댑터는 HTTP 요청을 애플리케이션의 유스케이스에 대한 메서드 호출로 변환하고 결과를 다시 HTTP로 변환하는 역할만 하도록 해야 구현해야 합니다.

여기에 어떠한 도메인 로직도 수행되서는 안됩니다.

반면에 애플리케이션 계층에서는 HTTP에 대한 코드가 있으면 안죕니다.

이렇게 유지해야만 웹 어댑터를 언제든지 다른 어댑터로 손쉽게 바꿀 수 있습니다.

또한 웹 컨트롤러를 나눌 때는 하나의 만능클래스보다는 작고 세분화된 기능을 가지는 여러 컨트롤러를 만들고, 각 컨트롤러마다 필요한 데이터 구조 모델을 별도로 두는 것이 좋습니다.

이렇게 함으로써 개발자가 더 코드를 파악하기 쉽고, 테스트 하기 쉽고, 동시 작업을 할 수 있습니다.

세분화된 컨트롤러를 만드는 일은 처음에는 노력이 더 필요하지만 장기적으로 유지 보수 관점에서 봤을 때는 분명히 더 좋습니다.

댓글남기기