강의

멘토링

로드맵

인프런 커뮤니티 질문&답변

권상웅님의 프로필 이미지
권상웅

작성한 질문수

토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1

MemberRegisterRequest 에 대해서

작성

·

131

0

 

토비님, 항상 좋은 강의 감사드립니다. 강의 내용을 학습하던 중 궁금한 점이 생겨서 질문드립니다!

강의 코드에서 MemberRegisterRequestdomain 패키지 안에 직접 정의되어 있고, 이 동일한 객체를 adapter 계층의 컨트롤러에서 @RequestBody로 직접 받는 것을 확인했습니다.

제가 접해온 일반적인 계층형 아키텍처에서는, 웹 계층을 위한 DTO를 별도로 두고 서비스 계층에서 이를 도메인 객체로 변환하여 도메인 계층이 웹 DTO에 의존하지 않도록 분리하는 방식을 주로 사용했습니다.

그래서 강의에서 보여주신 설계 방식에 대해 궁금한 점이 두 가지 있습니다.

  1. 이처럼 요청(Request) 자체를 도메인의 일부로 보고 domain 패키지에 포함시키는 설계가 갖는 이점은 무엇인지 궁금합니다.

  2. 이러한 설계가 계층 간의 결합도를 높일 수 있다는 우려에 대해서는 어떻게 생각하시는지, 그리고 어떤 상황에서 이러한 실용적인 접근이 더 효과적이라고 판단하시는지, 토비님의 설계 철학이나 기준에 대해 여쭙고 싶습니다.

감사합니다.

답변 1

1

토비님의 프로필 이미지
토비
지식공유자

강의 후반부까지 여러번 반복해서 계층형 아키텍처에서 반드시 지켜야할 가장 중요한 개념으로 한 방향의 의존 구조가 만들어져야 한다는 것을 강조해서 설명드립니다. 특히 의존성이 반대로 되는 것은 계층형 아키텍처의 가장 기본 원칙을 위배합니다.

강의에선 헥사고날 아키텍처를 사용하고, 거기에 도메인 계층을 추가해서 3계층을 사용합니다. 그리고 각각이 계층 구조로 단방향을 유지하도록 합니다. 웹, 서비스, 도메인이라면 웹->서비스->도메인의 의존 구조가 되어야 하고, 계층형 아키텍처 중에서 완화된 방식을 사용하기 때문에 웹->도메인의 의존관계도 허용됩니다. 모던 아키텍처는 이런 완화된 방식의 계층형 아키텍처가 일반적입니다.

말씀하신 웹 계층의 DTO가 어떤 용도로 만들어진 것인지 모르겠습니다. 만약 사용자의 요구를 받아서 오브젝트에 담는 용도라면, 이게 서비스로 전달 되겠죠? 그리고 서비스에서 도메인 엔티티로 변경하는 작업을 할 것이고요. 이런 순서로 설계를 했다면 이건 계층형 아키텍처 위반이고 잘못된 결합을 가진 경우입니다. 왜냐하면 웹 계층에 서비스 계층 코드가 의존하기 때문이죠. 나중에 웹이 아닌 다른 경로로 해당 서비스를 이용하게 되면 의존관계가 더 복잡하게 꼬여버립니다.

DTO는 두 계층 사이의, 혹은 두 시스템 사이의 데이터 전송을 단순하게 만들기 위해서 여러 정보를 묶어서 데이터를 이동시키는 용도로 사용합니다. 중요한 건 어떤 데이터를 담을 것인가를 결정하는 계층과 이걸 사용하는 계층의 의존관계가 명확해야 합니다. 웹 계층은 서비스 계층을 의존하는 구조에서 웹 계층의 DTO라는 게 존재하면 이걸 서비스 계층이 사용하는 순간 의존성이 양방향으로 만들어지고, 강한 결합이 생기는 거죠.

그래서 굳이 웹과 서비스 사이에 DTO를 쓴다면, 서비스 계층에 정의해야 합니다. 그걸 웹 계층에서 요청을 받을 때 사용할 수 있죠. 웹 계층이 서비스 계층에 존재하는 DTO를 쓰는 건 의존관계 위반이 아니니까요.

같은 원리가 도메인까지도 확장될 수 있습니다. 서비스에서 정의한 DTO(여기서는 Request라고 치죠)에서 정보를 꺼내서 도메인의 메소드를 호출하도록 파라미터를 꺼내서 쓴다면 아무 문제 없습니다.

그런데 이번 강의에서는 도메인부터 시작을 했습니다. 이 도메인 엔티티를 누가 쓸지는 전혀 신경 쓰지 않습니다. 기본 등록 서비스에서 쓸 수도 있지만, 또 다른 서비스에서 활용할 수도 있겠죠. 어쨌든 도메인 관점에서 회원을 생성하려고 할 때 받아야할 필수 정보를 나열하고 보니, String 타입이 계속 연속으로 나오고 이건 읽기도 부담이 되고, 이후에 변경이나 추가가 생겼을 때 실수하기 쉬운 구조가 됩니다. 그래서 Parameter Object라는 리팩터링을 적용해서 같이 다니는 (여기서는 생성 정보) 파라미터 값을 오브젝트에 담아서 전달하도록 만든 겁니다.

그런데 그러고 나서 서비스를 개발해보니 거기서도 이렇게 만든 Request 오브젝트를 그대로 사용해도 좋겠다는 판단을 했습니다. 특별히 따로 받아야 할 정보도 없으니 다른 DTO 류의 클래스를 만들어서 이걸 또 매핑아닌 매핑을 하는 쓸모없는 코드를 넣을 필요가 없어 보이니까요. 이때도 아무 문제가 없습니다. 서비스가 도메인에 의존하는 것은 아키텍처 설계에 자연스러우니까요.

그리고 최종적으로 웹 계층을 만들 때도 보니 이렇게 서비스와 도메인에서 사용한 Request를 API 입력을 받을 때 사용해도 좋다는 판단을 했습니다. 나중에 API가 복잡해지면 다른 오브젝트로 받을 수도 있겠지만, 우선은 단순하게 시작하니까 이렇게 한 것이죠.

결과적으로 웹-서비스-도메인의 의존관계를 지키면서, 각각 필요한 오브젝트를 설계했고, 이걸 의존관계를 따라서 도메인쪽에서 그쪽 필요에 의해 정의된 것을 활용한 것입니다. 이걸 두고 결합도가 높아졌다고 말할 아무런 근거가 없습니다. 나중에 각 계층에서 사용하는 정보의 종류나 타입이 달라지면 그때 변경해도 됩니다만, 그걸 지금 억지로 예측해서 나중에 바뀔지도 모르니까 계층간 DTO를 매번 다르게 만들자, 이건 그냥 시간과 자원 낭비죠.

보통 현장에서 개발할 때 DTO를 어떤 식으로 만들어 쓰는지 저도 잘 알고 있습니다. 그런 관습이 사실은 계층형 아키텍처를 위반하고, 심지어 로직의 강한 결합을 만드는(이건 엔티티를 웹 계층까지 보내는 방식을 설명 할 때 깊이 다룹니다) 문제가 있습니다.

기본 원칙에 충실한 방식으로 설계하고 코드를 만들고, 최소한의 접근 방법을 선택하고 필요할 때 확장하는 방식으로 개발해보시면 시스템이 점점 복잡해지더라도 이게 얼마나 견고하게 코드를 잘 지켜주고, 변화에 잘 대응할 수 있는지를 경험하실 수 있을 겁니다.

이해하시는 데 도움이 되셨기를 기대해봅니다.

그래도 더 궁금하시면 계속 질문해주세요.

권상웅님의 프로필 이미지
권상웅
질문자

정성스러운 답변 너무 감사드립니다!

답변도 정말 재미있게 읽었습니다!

PART2 기다리면서 열심히 복습하겠습니다!

권상웅님의 프로필 이미지
권상웅
질문자

안녕하세요! 토비 강사님! 다른 것에 대해서 궁금증이 생겨서 질문드립니다!

컨드톨러 어뎁터에서 dto(MemberRegisterReponse)를 만드셨잖아요.

만약에 복잡한 리포트성 조회결과로 인해 애플리케이션에서도 dto로 리턴을 하면 애플리케이션에 만든 dto를 컨트롤러 어뎁터에서 다시 새로운 어뎁터용 dto를 만들어서 리턴을 하게되는걸까요?

감사합니다.

토비님의 프로필 이미지
토비
지식공유자

안녕하세요.

리포트성 조회 결과는 엔티티와는 구조 자체가 다르기 때문에 JPA에서부터 별도의 빈(자바 오브젝트라고 해도 됩니다)으로 프로젝션해서 받거나 도메인 로직에 의해서 가공해서 그 결과를 담을 수 있는 오브젝트에 넣고, 그리고 애플리케이션 서비스 계층 밖으로 리턴합니다.

질문하신 내용은 애플리케이션 서비스 계층에서 리턴된 것을 받은 웹 어댑터 계층, 그러니까 컨트롤러에서 이걸 API 리턴 값으로 사용할 때, 또 다시 xxResponse 같은 오브젝트를 만들고 거기로 옮긴 뒤에 리턴을 해야하는가 일텐데요.

이번 강의 예제에서 Response 오브젝트를 만든 이유는 리턴 받은 엔티티와 API의 응답 데이터 구조가 다르기 때문에 이를 매핑해서 API 응답으로 사용할 필요가 있기 때문이었습니다.

그런데 이미 정형화된 리포트쿼리 응답 오브젝트를 리턴 받았고, 여기서 덧붙일 정보가 없다면 이를 그대로 리턴하면 됩니다. 굳이 API 응답 오브젝트는 무조건 웹 계층에서 다시 정의하고 리턴된 정보를 다시 매핑해서 사용해야한다라고 한다면, 이건 정말 쓸모없는 작업이 아닐까요? 불필요한 중복이 발생하는 코드는 아무리 단순하더라도 이후 변경에 일일히 대응해야 하고, 그 과정에서 실수하면 데이터가 제대로 복제 안 되는 등의 버그가 들어가기도 쉽습니다. 반대로 그렇게 똑같은 구조의 웹 응답용 Response 오브젝트를 만들었을 때 얻게되는 이익이 없습니다.

웼 어댑터 계층은 아키텍처 원칙에 따라 애플리케이션 서비스 계층에서 공개된 오브젝트에 의존할 수 있습니다. 원칙적으로도 문제는 없죠.

그런데, 이런 경우 간혹 서비스 계층에서 비즈니스 로직에 의해서 만들어지고 리턴되는 정보 중에서 일부 정보는 API에서 리턴을 해야하지 않아야 하기 때문에 JsonIgnore 애노테이션을 붙여두기도 합니다. 이건 JSON 직렬화에 대한 정보인데, 도메인의 관심사라고 보기 어렵습니다. 굳이 필요없는 정보라면 리포트 오브젝트를 생성하는 시점에서 아예 제거하는 것이 맞습니다.

그런데 API 요청에 따라, 혹은 이를 원격으로 이용하는 다른 파트너사나 시스템에 따라 부분 정보만 요청하는 경우라면, 이때는 Response 객체를 정의해서 써야 합니다. 이 결정에 대한 관심사는 웹 계층에 속하는 것이기 때문입니다. API 응답 요청에서 특별한 형식으로 포맷팅을 요구하는 경우도 마찬가지입니다.

리포트 데이터에 대한 결과에는 Response를 다시 만들 것인가 아닌가가 중요한 게 아닙니다. 각 계층이 자신의 기능과 책임에만 충실한 결과를 만들어 내고, 의존 관계를 지킨다면 가능한 단순한 구조의 선택을 하는 것이 좋습니다. 하지만 억지로 단순함을 추구하려고 계층의 책임을 넘어서거나 아키텍처 원칙을 위반하는 것을 허용하면 안 됩니다. 이걸 기억하시고 적절한 결정을 해보시고, 코드를 이해하고 변경하는데 어떤 영향이 있는지 판단해보세요.

 

권상웅님의 프로필 이미지
권상웅
질문자

좋은 답변 정말 정말 감사합니다!

좋은 하루 되십시오 토비 강사님!

권상웅님의 프로필 이미지
권상웅

작성한 질문수

질문하기