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

789456jang님의 프로필 이미지
789456jang

작성한 질문수

Java/Spring 주니어 개발자를 위한 오답노트

Controller / application Service / Domain 의 역할 구분

작성

·

1.2K

1

안녕하세요 강의 잘 듣고 있습니다.

헥사고날 아키텍쳐 부분 강의를 듣고, 궁금한 부분이 새겨서 질문 남깁니다!

Application Service(이하 서비스) 에서 도메인부분을 분리 하여 객체의 상태 변화에 대한 부분을 처리하도록 하고, 서비스에선 Repository와 소통해서 필요한 객체나 컨트롤러에서 정보(예를 들어 수정 정보 등등) 등등을 얻어서 도메인 부분에 위임한다고 이해했습니다. 그렇게 도메인 영역을 분리하면 테스트에 용이하다는 것은 이해했습니다. 그런데 실제로 토이프로젝트에 적용해보려고 코드를 수정하는 중에 예를 들어 게시글을 수정하는 과정이라면 컨트롤러에서 처음 요청을 받을 때 postId, updateRequest를 받아서 서비스에 업데이트를 요청하고, 다시 서비스에선 아이디를 통해 Post를 찾고, PostDomainService(가칭)에 Post와서 updateRequest를 넘겨 수정한다면 거의 같은 내용의 파라미터를 굳이 2 계층을 건너 불필요한 위임이 반복 된다고 생각했습니다. 궁금한 것은 2가지 입니다.

  1. 이런 경우엔 PostDomainService 클래스를 만들기보단 Post 객체 자체에서 업데이트를 처리하는 것이 맞을까요? - (1번 코드)

  2. 도메인과 도메인 서비스의 차이는 객체의 상태(도메인)와 객체의 행동(도메인 서비스) 일까요? 엔티티와 도메인의 구분이 잘 이해가 되지 않습니다.

    //== 업데이트 로직 ==//
    public void updateInfo(PostUpdateReqDto updatePost) {
        this.title = updatePost.getTitle();
        this.content = updatePost.getContent();
    }
    // 기존 서비스에서의 로직
    @Transactional
    public PostUpdateResDto updatePost(final Long postId, final PostUpdateReqDto postUpdateReqDto) {
        Post findPost = postRepository.findWithMemberByPostId(postId);
        Post updatedPost = postDomainService.updatePost(findPost, postUpdateReqDto);
        // 위 과정에 생기면 사라지는 메서드
        // findPost.updateInfo(postUpdateReqDto);
        checkForbiddenWord(findPost);
        return new PostUpdateResDto(findPost);
    }

 

답변 2

2

김우근님의 프로필 이미지
김우근
지식공유자

안녕하세요. 답변이 늦어 죄송합니다. 근래에 다른 일로 바빠 정신이 없었습니다. 2달이 넘어간 글이지만 뒤늦게 답변 달아봅니다.

우선 올려주신 내용 중 이론에 관해 이야기해주신 부분들은 모두 제대로 이해하고 계신 것이 같습니다. 다만 오류가 하나 있는 것이 ‘애플리케이션 서비스는 무조건 도메인 서비스를 통해 도메인을 호출해야 한다’라고 생각하고 계시는 것 같습니다.

이는 아닙니다. 애플리케이션 서비스는 도메인 서비스에 그 책임을 위임할 수도 있지만, 단순히 도메인에 일을 시키고 끝날 수도 있습니다. 다시 말해 아래와 같은 코드가 되어도 된다는 의미입니다.

controller -> application service -> domain

따라서 첫 번째 질문에 대한 대답은 ‘Post 객체 자체에서 업데이트를 처리하는 것이 맞다’입니다. 이는 강의 내용 중 객체 파트에서 나온 TDA 원칙에도 부합합니다.

두 번째 질문은 도메인과 도메인 서비스의 차이를 물어보시는 것 같습니다. 이러한 의문이 드는 것은 당연합니다. 왜냐하면 강의(스프링에서 OOP와 안티 패턴 : Transaction script)에서도 한 차례 나오지만, PriceCalculator를 Cashier라는 이름으로 바꾸었을 뿐인데, 도메인 서비스가 도메인으로 변경되기 때문입니다. 고작 이름 하나 바꿨을 뿐인데 도메인 서비스가 도메인이 될 수 있다니, 왠지 이해가 안되지 않나요?

그래서 도메인과 도메인 서비스의 경계를 엄격하게 나누는 것은 의미가 없습니다. 대신 아래와 같이 정리해 드립니다.

  1. 서비스는 객체로 표현하기 애매한 알고리즘 그 자체이다. 이 알고리즘을 어딘가에 두긴 해야 하는 데 마땅히 둘 곳이 없으니, 서비스라는 이름으로 클래스를 만들고 거기에 넣어둔 것뿐이다.

  2. 그런데 조금만 더 고민하다 보면 서비스는 사실 객체로 표현할 수 있는 경우가 훨씬 많다. 예를 들어 ‘가격 계산’은 알고리즘이라고 생각하기 쉬워 PriceCalculator 같은 이름의 서비스로 만들어지기 쉽다. 하지만 알고 보면 이는 가격 계산을 하는 새로운 도메인을 만들어 해결할 수 있다. 이러한 착각이 발생하는 이유는 개발자들이 성급하게 절차 지향적인 사고로 결론을 내려버리기 때문이다.

그러므로 도메인과 도메인 서비스는 의미론적으로 분류하는 것이 맞습니다.

그리고 세 번째 질문도 있는 것 같습니다. 도메인과 엔티티의 차이에 대해서도 물어보셨는데요. 개발 세계에서 말하는 엔티티는 도메인 엔티티입니다.

혼란스러운 내용을 대신 정리해 드리려고 만든 강의인데, 또 다른 여러 오해를 만들어 드린 것 같습니다. 그만큼 영상의 내용이 말하는 바를 확실하게 전달하지 못하고 있다는 의미인 것 같아 죄송하기도 하네요.

최종적으로 정리하겠습니다.

  • 도메인: 애플리케이션이 풀고자 하는 문제 영역

  • 도메인 모델: 도메인을 해결하기 위해 만들어진 객체들

  • 도메인 서비스: 도메인을 해결하기 위해 필요한 로직이지만, 이 로직을 할당할 수 있는 자연스러운 도메인 모델에 찾지 못해 만들어지는 객체

  • 개발 세계의 엔티티 = 도메인 모델

  • 개발 세계의 엔티티 ≠ 영속성 엔티티

  • 애플리케이션 서비스는 항상 도메인 서비스를 통해 도메인에 일을 시켜야 하는 것은 아니다.

답변이 조금이라도 도움이 되셨길 바랍니다.

789456jang님의 프로필 이미지
789456jang
질문자

자세한 설명 감사합니다!
반복적으로 고민하고 공부하면서 완전히 제 지식으로 습득하도록 하겠습니다!
좋은 강의 감사합니다!

-1

안녕하세요, 인프런 AI 인턴입니다.
도메인 영역을 분리하는 이유는 변경을 용이하게 하기 위해서인데, 변경되는 부분을 최소화하는 것이 이상적입니다.
따라서, 단순한 CRUD 작업은 도메인 객체가 아닌 Repository에서 처리하는 것이 좋습니다.
만약, Post 객체 자체에서 업데이트를 처리하게 된다면, 해당 객체의 책임 범위가 넓어지므로 단일 책임 원칙을 준수하지 않게 됩니다.
따라서, 업데이트 작업을 하려면 컨트롤러에서 서비스를 호출해 파라미터를 전달하는 것이 일반적입니다.
불필요한 위임이 있다면, 이는 설계상의 문제가 있을 수 있으므로, 해당 부분은 리팩토링을 통해 개선하는 것이 좋습니다.
감사합니다.

789456jang님의 프로필 이미지
789456jang

작성한 질문수

질문하기