육각형 아키텍처

in kr-dev •  2 years ago 

웹 어댑터란?

스프링이나 .NET 프레임워크 등을 활용해 MVC 프로젝트를 해본 분들이라면 웹어댑터는 쉽게 이해하실 수 있다.

웹어댑터는 보통 Controller라 불리우는 녀석을 의미한다.

아래 그림을 보자.

보통 처음 프로젝트를 하면 이런식으로 컨트롤러에서 서비스객체를 호출하는 형태로 개발을 하곤 한다. 하지만 설계적인 관점에서 이는 그리 좋지 못한 방식이다.

조금더 완성도 높은 설계를 위해서는 어댑터와 유스케이스 사이에 또다른 간접계층을 넣어주는 것이 좋다. 아래 그림처럼 말이다.

보통의 경우 제어의 흐름이 컨트롤러에서 서비스로 흘러가게 되는데, 이때 중간에 웹 어댑터가 통신할 수 있는 특정 포트를 제공함으로써 의존성 역전 원칙을 적용했다.

즉 서비스는 이 포트를 구현하고, 웹 어댑터는 이 포트를 호출하여 사용하는 것이다.

여기서 왜 포트와 같은 간접 계층을 넣어주는 것이 설계적으로 좋을까? 

어플리케이션을 개발하다보면 언젠가 그 시스템은 레거시가 되고 초기 제작자는 이미 사라져 버리고 누군가가 유지보수하게 될 가능성이 매우 높다.(지금 내가 회사에서 진행중인 프로젝트가 그렇다.)

이때 이러한 설계가 되어있다면 해당 프로젝트를 유지보수하는 엔지니어는 아주 행복할 것이다. 그 이유는 여기서 포트가 프로젝트의 명세 역할을 해주게 되기 때문이다.

웹어댑터의 역할

이번에는 웹 어댑터의 역할에 대해 알아보자. 일반적으로 웹어댑터는 HTTP 관련 처리를 할 책임이 있다.

구체적으로 말씀드리면 아래와 같은 역할을 한다.

1. HTTP 요청을 객체로 매핑

2. 인증 및 권한 부여

3. 입력 유효성 검증

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

5. 유스케이스 호출

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

7. HTTP 응답을 반환

웹어댑터는 URL, 경로, HTTP Method, Contents Type 등의 기준을 만족하는 HTTP 요청을 수신한다. 그리고 해당 요청으로 들어온 파라미터와 콘텐츠를 객체로 역직렬화 해준다.

그리고 어댑터가 요청한 유저가 정상적인 유저인지 등의 인증을 진행하고, 비지니스를 수행할 수 있는 권한을 부여한다. 이과정이 실패하면 별도의 에러를 리턴한다.

다음은 입력 유효성을 검증한다. 여기서 입력 유효성은 유스케이스(서비스)에서의 입력모델과는 구조나 의미가 다를 수도 있다. 여기서는 단순하게 웹어댑터에 입력되는 (파라미터로 들어오는) 모델에 대한 유효성을 검증하는 것이며, 유스케이스의 입력모델은 별도로  유효성 검증을 해줘야한다.

여기서는 웹어댑터의 입력 모델이 유스케이스의 입력모델로 정상적으로 변환할 수 있는지를 검증해야한다.  이 과정이 실패하면 유효성 검증에러를 발생시킨다.

여기서 입력 모델을 유스케이스의 입력모델로 매핑하여 해당 유스케이스를 호출한다. 여기서는 유스케이스가 무엇을 하는지 전혀 알지 못한다. 단순히 호출하고나서 해당 유스케이스의 출력의 결과물을 받아온다. 그리고 그 출력을 HTTP 응답으로 직렬화해서 호출한 곳으로 전달해준다.

위의 각과정에서 문제가 생길때에 대한 예외처리는 필수적이다. 각 에러에 대한 메시지 정의 역시 중요하니 잘 정의해 두도록하자.

어려운 내용이다. 여기서 중요한 것 딱 하나만 명심하자. 

HTTP 와 관련된 작업은 어플리케이션 계층의 전적인 책임이다.

개발을 하다보면 이러한 책임을 어기고 경계를 넘나드고 싶은 유혹에 빠질 수 있다. 여기서 이러한 유혹을 피하기 위해서는 웹계층 개발보다 도메인과 어플리케이션 계층부터 개발한 후에 추후에 웹계층을 개발하고 이를 어댑터로 연결만 해주면 안정적으로 개발이 가능하다.

컨트롤러 잘 만들기

보통 잘 못만들어진 프로젝트를 보면 컨트롤러 하나가 굉장히 큰 책임을 수행하고 있는 경우가 많다. 그리고 서비스 하나당 컨트롤러 하나 이런식으로 아주 규칙적으로 프로그래밍을 해놓은 경우도 많이 보았다.(전혀 그럴필요가 없고, 오히려 그래서는 안되는데 말이다..)

컨트롤러(웹어댑터)는 여러개 여도 된다. 너무 적게 만들어서 많은 책임을 주는 것보다는 많이 만드는게 천만배 낫다고 생각한다.

만약 10만줄짜리 거대한 컨트롤러가 있다고 생각해보자. 거기서 당신이 딱 메서드 하나를 추가했다고 생각해보자. 아무리 기존 코드가 깔끔하게 잘 짜여 있어도 시간이 지나면서 관리가 어려워지고 심지어 배포하는 과정에서도 부담이 된다. 그리고 테스트를 할때도 어렵다. 컨트롤러에 코드가 많으면 당연히 해당 컨트롤러에 대한 코드도 많을 것인데 이렇게 되면 관련된 테스트코드를 찾기가 어렵게 될 것이다. (보통은 테스트 코드가 더 추상적이기에 어려움이 많아진다.)

그리고 컨트롤러가 거대하면 많은 연산들이 특정 모델 클래스를 공유하게 되는데 이또한 문제가 많다. 예를 들어 어떤 연산에는 특정 필드 값이 필요하지 않아 그 연산에만 필드 값을 null로 한다던지 하는 이슈가 발생할 수 있다.

그러므로 컨트롤러를 분리하고 그 컨트로럴에 해당하는 자체 모델을 만들거나, 아예 원시값으로 처리하는 방식을 선택해보자. 전용 모델은 컨트롤러 패키지 내부에 private로 선언함으로써 다른곳에서 재사용되게하는 실수를 방지할 수도 있다. 그리고 컨트롤러를 분리하게 되면 여러 개발자들이 동시작업을 하는데에도 유리하다.

한가지 더 얘기해보면 컨트롤러의 명칭도 잘생각해서 만들자. 서비스 클래스도 마찬가지다. 각각의 컨트롤러를 만들 때는 생각에 생각에 생각을 해서 신중하게 명칭을 정해주자. 잘못 만든 명칭은 나중에 유지보수할때 두고두고 후회하게 될 것이다.

Ref.
톰 홈버그, 『만들면서 배우는 클린 아키텍처』, 위키북스(2021), p53~61

출처 :: https://devkingdom.tistory.com/343

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

[광고] STEEM 개발자 커뮤니티에 참여 하시면, 다양한 혜택을 받을 수 있습니다.