상세 컨텐츠

본문 제목

[Architecture] 5부 아키텍처 : 23장 프레젠터와 험블 객체

프로그래밍/Architecture

by jisooo 2024. 1. 17. 21:39

본문

 

 

#클린아키텍처 #humbleObjectPattern #designPattern #험블객체

클린 아키텍처 책의 앞부분에서도 많이 강조된 내용이지만,

"테스트하기 좋은 코드", 즉 테스트 용이성은 좋은 아키텍처가 지녀야 할 속성이다.

실무를 하면서 팀의 분위기에 따라서 테스트 코드 작성을 권장하는 경우도 있고,

개개인이 자율적으로 테스트 코드를 짜거나,

심지어는 특정 테스트 커버리지에 달성할 때까지

git merge를 막아놓는 진행하는 팀도 있었다.

책에서도 이렇게 테스트 용이성에 대한 강조를 하고 있고,

실무에서 테스트 코드의 중요성은 많이 언급되기도 한다.

테스트 코드가 중요한 이유는 무엇일까?

개개인의 경험에 돌아보아 생각해보면

실무에서는 요구사항에 대한 변경 업무가 끝도 없이 들어오게 된다.

그 말은 즉, 기존의 비즈니스 로직에 대해 변경이 필요한 상황인데,

실제로 업무를 할 때 개발에 들어가는 비용 못지 않게, 아니면 심지어 개발 비용보다

수정한 코드가 각 환경에서 정상적으로 동작하는지 테스트하는 비용이 상당히 많이 든다.

또한 이러한 테스트를 코드가 아닌 수동으로 할 생각을 하면

아주 번거로운 일이 아닐 수 없다.

보통 수동 테스트는 기획팀에서 검증을 해주시는 편이고,

개발자 입장에서 코드의 수정사항에 대한 검증은 "테스트 코드"를 작성하여

다양한 케이스에 대한 방어 로직이 적용되었는지,

다른 기능이나 로직에 영향 범위가 없는지 검증하는 책임을 갖는게 맞다.

(하지만 이 마저도 찝찝해서 개발자도 테스트 코드 검증 뿐 아니라 꼭 phase 별로 수동 테스트도 같이 병행한다.)

그리고 만약 기존에 여러 비즈니스 로직에 대한 테스트 코드가

이미 작성이 되어있어 검증이 완료된 상태이면,

추후 특정 비즈니스 로직에 대한 변경이 필요할 때,

해당 테스트 코드를 실행하여,

비즈니스 로직이 원하는 검증 케이스에 대해서 정상적으로 동작하는지,

또는 다른 비즈니스 로직에 변경에 대한 영향도가 전파되는지

쉽게 검증할 수 있다.

클린 아키텍처 23장에서는 이렇게

"테스트 용이성"을 제공할 수 있는 디자인 패턴인 "험블 객체 패턴"에 대해 설명한다.

험블 객체 패턴

책에서 설명하는 "험블 객체 패턴"의 원리는 단순하다.

테스트하기 쉬운 코드와 테스트하기 어려운 코드를 분리하는 것이다.

분리해서 테스트하기 어려운 코드는 함수나 클래스로 분리하여 mocking, stub, test-double로 적당히 교체한다.

그리고 개발자가 진정으로 테스트하고 싶은 핵심 비즈니스 로직을 담은 부분은 테스트를 작성하면 된다.

이렇게 두가지 행위로 분리할 때, 두 모듈 중 하나가 "험블"이다.

가장 기본적인 본질(핵심 업무 규칙 / 비즈니스 로직)은 남기고,

테스트하기 어려운 행위를 모두 험블 객체로 옮긴다.

그리고 나머지 모듈에는 험블 객체에 속하지 않은,

테스트하기 쉬운 행위를 모두 옮긴다.

프레젠터와 뷰

뷰는 험블 객체이도 테스트하기가 어렵다.

따라서 뷰에 포함된 코드는 가능한 간단한게 유지한다.

뷰는 데이터를 GUI로 이동시키지만, 데이터를 직접 처리하지 않는다.

반면 프레젠터는 테스트하기 쉬운 객체다.

프레젠터의 역할은 애플리케이션으로부터 데이터를 받아

화면에 표현할 수 있는 포맷으로 만드는 것이다. (데이터 처리)

이를 통해 뷰는 데이터를 화면으로 전달하는 간단한 일만 처리하도록 만든다.

예를 들어 애플리케이션에서 어떤 필드에 날짜를 표시하고자 한다면?

애플리케이션은 프레젠터에 Date 객체를 전달하고,

프레젠터는 해당 Date 객체를 화면에 원하는 문자열의 형태로 노출하기 위해

적절한 문자열로 변형시킨다.

그러면 테스트 코드를 짤 때 개발자가 검증하고자 하는 내용은 아래 내용일 것이다.

Date 객체가 원하는 문자열의 형태로 프레젠터에서 변형되는지 검증할 수 있다.

반면 뷰 모델은 단순히 변형된 데이터를 담는 단순한 역할만 하고,

뷰는 뷰 모델에서 이 데이터를 찾는다.

테스트와 아키텍처

본문의 서론에서 언급했듯이,

"테스트하기 쉬운 코드"는 좋은 아키텍처가 지녀야 할 핵심 속성이다.

그리고 험블 객체 패턴이 그 좋은 예시이다.

행위를 테스트하기 쉬운 부분과

테스트하기 어려운 부분으로 분리하면

아키텍처의 경계가 정의되기 때문이다.

위 예시처럼 프레젠터와 뷰 사이의 경계는 이러한 경계의 예시 중 하나이며,

아래에 쭉 설명이 나오겠지만 이 밖에도 수많은 경계가 존재한다.

데이터베이스와 게이트웨이

유스케이스 인터렉터와 데이터베이스 사이에는 데이터베이스 게이트웨이가 위치한다.

이 게이트웨이는 다형적 인터페이스로

애플리케이션이 데이터베이스에 수행하는 CRUD 작업과 관련된

모든 메서드들을 포함한다. (Repository Interface)

반면 핵심 업무 규칙을 캡슐화하고 있느 유스케이스 계층에서는

세부사항인 SQL을 허용하지 않으며,

유스케이스 계층은 필요한 메서드를 제공하는 게이트웨이 메서드를 호출한다.

그리고 인터페이스의 구현체는 데이터베이스 계층에 위치하며,

이 구현체를 "험블 객체"라고 할 수 있다.

구현체에서는 직접 SQL을 사용하거나

데이터베이스에 대한 임의의 인터페이스를 통해 게이트웨이에 정의된 메서드에서 필요한

데이터에 접근한다.

이와 달리 유스케이스 인터렉터는

애플리케이션에 특화된 업무 규칙을 담기 때문에 험블 객체가 아니다.

따라서 바로 이 유스케이스 인터렉터가

작업자가 핵심으로 테스트하고자하는 대상이고,

유스케이스에 대한 테스트를 작성 할 때,

게이트웨이 로직은 Mocking, Stub이나 test-double로 적당히 교체하여 검증 할 수 있다.

데이터 매퍼

하이버네이트와 같은 Object Relational Mapper는 어느 경계 속에 속할까?

물론 데이터베이스 계층에 속하며

실제로 ORM은 게이트웨이 인터페이스와 데이터베이스 사이에서

일종의 또 다른 험블 객체 경계를 형성한다.

서비스 리스너

애플리케이션이 다른 서비스와 통신하는 구조의 경우에도,

서비스 경계를 생성하는 험블 객체 패턴을 발견할 수 있다.

애플리케이션은 데이터를 간단한 데이터 구조 형태로 로드한 후,

이 데이터 구조를 경계를 가로질러 특정 모듈로 전달한다.

특정 모듈은 데이터를 받아 서비스에서 원하는 포맷으로 데이터를 만들어서

외부 서비스로 전송한다.

반대로 외부 서비스로부터 데이터를 수신하는 서비스의 경우,

서비스 리스너가 서비스 인터페이스로부터 데이터를 수신하고,

데이터를 애플리케이션에서 사용할 수 있게

간단한 데이터 구조로 포맷을 변경한다.

후에 데이터 구조는 서비스 경계를 가로질러

내부로 전달되고,

내부에서는 이 데이터를 활용하여 비즈니스 로직을 처리한다.

외부 서비스와 통신하여 비즈니스를 처리하는 로직에 대한

테스트를 짠다고 가정할 경우,

테스트 코드에서는 외부 서비스를 호출하여 특정 응답 객체를 리턴하도록

mocking 할 수 있다.

그러면 응답 객체를 어떻게 활용하여 비즈니스에 활용하는지에

집중하여 테스트 코드로 검증 할 수 있을 것이다.

결론

각 아키텍처 경계마다 경계 가까이 숨어있는 험블 객체 패턴을 발견할 수 있다.

경계를 넘나드는 통신은

거의 모두 간단한 데이터 구조를 수반할 때가 많고,

대개 그 경계는 테스트하기 어려운 무언가와 테스트하기 쉬운 무언가로 분리될 것이다.

그리고 이러한 아키텍처 경계에서

험블 객체 패턴을 잘 활용하면

전체 시스템의 테스트 용이성을 크게 높일 수 있다.

테스트 코드를 짤 때 가장 번거로운 일 중 하나는,

검증하고자 하는 비즈니스 로직이 다른 부가적인 로직들과 의존성이 맞물려있을 때이다.

이 때 의존성 때문에 테스트하기 어렵고 테스트 복잡도가 올라가는 케이스가 굉장히 많은데,

이럴때 의존성을 가진 로직을 험블 객체로 분리하여 테스트를 작성하도록 할 수 있다.

관련글 더보기

댓글 영역