CH06 응용 서비스와 표현 영역
Last updated
Last updated
표현 영역은 사용자의 요청을 해석하여 응용 영역이 이를 잘 수행할 수 있도록 응용 영역이 필요한 형식으로 사용자의 요청을 변환 및 전달하는 역할을 한다. 또 응답의 관점에서도 응용 영역이 응답을 위해 처리해서 전달해주는 데이터나 처리 결과를 변환해서 사용자에게 전달하는 역할을 한다.
응용 영역의 주된 역할은 사용자의 요청에 대해서 기능을 수행하는 것이다. 단, 기능 수행을 한다고 해서 도메인 로직이 응용 영역에 구현되어 있는 것은 아니며 기능 수행을 위해서 도메인 로직이 구현되어 있는 도메인 계층을 가져와서 이를 실행시키고 결과를 조합하고 결과를 만드는 역할을 하는 것이다.
따라서, 필요한 애그리거트를 조회하고 애그리거트를 통해서 해당 애그리거트의 도메인 기능을 실행시키며 때로는 도메인 서비스를 불러와서 2개 이상의 애그리거트가 협업하는 기능을 실행시킨다. 그래서 결국 응용 서비스가 트랜잭션 단위가 된다.
책에서 계속 강조하고 있지만 응용 영역에 도메인 로직이 들어가서는 안된다. 코드의 응집성을 깨트리는 행동이다. 도메인 계층에 도메인 로직을 응집시키고 고립시켜두지 않고, 필요하다고 막 응용 계층에 도메인 로직을 넣게 되면 같은 로직이 여러 군데에 중복되어 구현 될 수 있고 그만큼 수정 비용 및 사이드 이펙트 발생 가능성이 커진다. DDD의 핵심 원리를 어기는 행동이다.
원제는 '응용 서비스의 크기'인데 원제가 내용을 구체적으로 표현하지 못하는 것 같아서 내가 고쳤다. 핵심은 하나의 응용 서비스가 책임져야 할 범위를 어떻게 설계 할 것인가에 대한 이야기이다. 예를 들어 Member라는 애그리거트가 비밀번호 변경, 비밀번호 초기화, 탈퇴를 처리해야한다고 할때 아래와 같이 MemberService 를 설계할 수 있다.
위와 같이 설계할 경우 장점은 세 로직(비밀번호 변경, 비밀번호 초기화, 회원 탈퇴)에서 모두 사용되는 공통 로직을 해당 클래스 내에서 Private 으로 선언해서 사용할 수 있다는 점이다. 단점은 저렇게 설계를 해두면 앞으로 생기는 추가 로직들도 MemberService 에 생길 가능성이 크기 때문에 점점 클래스 의 크기가 커져서 관리 비용이 증가될 수 있다는 것이다.
아래와 같이 기능별로 서비스를 분리해서 설계할 수 있다.
장점으로는 클래스 단위가 작을 수 밖에 없어서 관리상 이점이 있다는 것이다. 관리상 이점이 있다는 의미는 로직이 한데 모여 있는 경우보다 변경시 사이드 이펙트에 대한 위험도가 떨어지는 등 유지, 보수가 더 용이하다는 것이다. 책에서는 이 방식을 추천하고 있다.
하지만 세 로직(비밀번호 변경, 비밀번호 초기화, 회원 탈퇴)에서 공통적으로 사용되는 로직이 있을 수 있는데 이러한 로직은 따로 클래스를 두고 거기에 선언해서 끌어다 사용하는 방식을 책에서 권하고 있다.(예를 들어 MemberServiceHelper 와 같은)
위와 같이 여러 기능들을 클래스별로 쪼개서 구현을 할 경우 상단에 인터페이스를 둘 것이냐에 대한 내용이었다. 아래 코드는 인터페이스를 두는 경우에 대한 예시 코드이다.
결론적으로 책에서는 인터페이스를 따로 두지 않을 것을 권하고 있다.
이유는 인터페이스를 둔다는 것은 런타임에 구현 객체를 교체할 필요성이 있다는 이야기인데 실제로 응용 서비스에서 런타임에 구현 객체를 교체할 일은 거의 없다는 것이다. 애초에 구현 객체가 두 개 이상인 경우가 드물다. 오히려 인터페이스를 생성 하면서 불필요하게 계층이 하나 더 생겨서 복잡도만 올라가게 된다.
응용 영역이 표현 영역에 의존하지 않는 것이 이론적으로는 매우 타당하고 쉬운 이야기이지만 판단하기가 참 애매하다. 책에서도 사실 응용 영역이 '표현 영역의 기술을 사용하지 않도록 해야한다'는 이야기를 할 뿐이다. HttpServletRequest 와 같은 객체를 응용 영역에서 직접 사용해서는 안된다는 것이다. 사실 당연한 이야기다.
내가 다른 사람들의 의견을 들어보고 싶은 부분(혹은 저자의 의견)은 표현 영역에서 직접 받는 Query와 Command객체들을 응용 영역에서 직접 사용하는 것에 관한 의견이다. 이걸 직접 사용하면 이는 곧 응용 영역이 표현 영역에 의존하고 있다고 말할 수 있는 부분인데, 이를 끊어내려면 표현 영역에서 응용 영역에 위치한 어떠한 DTO로 데이터를 변환해서 응용 영역에 전달해줘야한다.
문제는 이게 크게 유의미한 행동이냐라는 것이다. 직접 의존은 끊어낼 수 있지만 결국 사용하는 필드가 똑같거나 유사할 확률이 높다. 만약 사용자가 타사라서 API 스펙 자체가 변경될 확률이 크다면 요청 스펙을 그대로 사용하는 것보다 응용 영역에서 따로 사용할 DTO 로 변경해서 전달해주는 것이 더 좋겠지만 요청자가 자사 앱 혹은 웹이라면 요청 스펙이 바뀔 일이 없을 수있다. 설령, 바뀐다고 해도 의존하는 형태로 갔을때 수정 비용이 엄청나게 클 것 같지도 않다. 반대로 응용 영역에서 Query와 Command를 그대로 사용함으로써 개발 효율이 더 클 수 있다.
표현 영역이 응용 영역에 일반적으로 사용되는 개별 파라미터로 전달하는 방법은 취하기가 좀 어려운 것이 일단 전달할 파라미터가 너무 많아서 클래스 단위로 묶어서 줘야 할 경우가 더 많다.
단순한 조회시 응용 영역에서 별다른 로직을 실행할 일이 없다면 표현 영역에서 조회를 담당하는 서비스의 결과를 그대로 return 하는 형태를 다룬 부분이었는데 개인적으로 선호하지는 않는 패턴이다. 아래가 예시 코드이다.
서비스가 명목상 존재하고 별달리 기능이 없는데 괜히 거치는 계층이 많아진다는 것은 동의를 하지만 위와 같은 방식대로 하려면 굳이 특별한 어떤 응답 케이스를 위해서 View 객체를 따로 만들어줘야하고 이에 대한 조회 로직도 따로 구현해줘야한다. 이 말인즉, 이를 위해서 만든 로직들은 범용성이 떨어져서 재활용이 어려울 가능성이 크다.
응용 영역에서 조회 로직을 거쳐서 가져온 데이터를 필요한 양식에 맞게 표현 영역에서 변환해서 전달해주는 구조가 적절한 것 같다.