객체 생성.. 어디서 할까?

kdkdhoho·2023년 9월 27일
0

개요

기존에 존재하는 음식점 조회 쿼리의 서브 쿼리를 분리하는 작업과 음식점 좋아요 데이터 역정규화 작업을 말랑이 작업해주었다. 👏

작업한 코드를 리뷰하는 도중, 내가 기존에 알고 있던 지식의 근간이 흔들리는 코드가 하나 있었다.
(해당 PR 바로가기)

public class RestaurantService {

    private final RestaurantLikeRepository likeRepository;
    
    ...

    public void clickLike(Restaurant restaurant, OAuthMember member) {
        RestaurantLike like = restaurant.clickLike(member);
        likeRepository.save(like);
    }
}

public class Restaurant {
    
    ....
    
    private int likeCount;
    
    public RestaurantLike clickLike(OAuthMember member) {
        likeCount++;
        return new RestaurantLike(this, member);
    }
}

바로 위 코드이다.

음식점 좋아요를 눌렀을 때의 로직인데, Service에서 음식점과 회원을 조회하고 그 결과가 존재하면 DB에서 제거한다. 존재하지 않으면 좋아요 수 + 1, DB에 영속 하는 작업을 처리해줘야 한다.

로직상 흠잡을 곳이 없지만 하나 어색하다고 느낀 부분이 있다.

바로 Restaurant 객체의 clickLike 메서드이다.

어색하다고 느낀 점

내가 갖고 있던 지식으로는 Restaurant는 단순히 좋아요 수 + 1만 처리해주고, Service에서 RestaurantLike 객체를 생성해줘야 한다고 생각했다.

이렇게 해야 함수가 하나의 일만 한다고 생각했고, 테스트하기에도 쉬울 것이라고 생각했다. (지금 생각해보니 테스트 편의성과는 상관이 없는 것 같다.)
그리고 Restaurant가 불필요하게 OAuthMember를 알게 된 점도 달갑진 않았다.

그래서 단순히 'restaurant는 단순히 좋아요 수만 1개 늘리고 RestaurantLike 객체 생성은 service에서 해주는건 어때'? 라고 리뷰를 남겼더니, 말랑이 이유를 물어봐주었다.

말랑의 생각

내 이유를 말하고 나니, 말랑은 다음과 같이 이야기해주었다.

(패턴이 항상 정답이 아니지만) GRASP 패턴에 보면 Creator 패턴이라는 것에 대한 설명이 나오는데, 쉽게 말하면 객체의 생성은 생성될 객체에 대한 정보를 가장 많이 담고 있는 객체에 부여하는 것이 좋다는 설명이 나와.
좋아요를 위한 객체에는 Member와 Restaurant가 필요하고, Member는 음식점이라는 컨텍스트에 한정된 객체라기보단 조금 더 범용적인 상황에서 쓰일 수 있는 객체라 생각해.
음식점은 좋아요와 같은 컨텍스트 내에 존재한다고 생각하고.
그래서 음식점 좋아요는 음식점이 생성하는 것이 좋다고 생각했어.

추가로 앞으로의 확장성을 고려해도, 음식점 좋아요를 했을 때 좋아요 수를 1 증가시키는 대신 이벤트등을 발생시킨다고 보았을 때, 서비스 계층의 로직이 바뀌는 것이 아니라, 도메인 객체의 로직이 변경되는게 나는 더 좋다고 생각해.

좋아요 생성 작업을 분리하면 결국은 좋아요 생성을 위한 작업을 여러 객체(음식점, 서비스)에서 분리해서 책임지게 되는데, 이는 오히려 단일 책임을 벗어난다고 생각했어!

서비스는 큰 범위 내에서 좋아요를 위한 작업을 처리할 뿐, 그 과정을 상세히 알고 있는 것은 좋지 않다고 생각해.

결론적으로 좋아요를 위한 작업이 변경되었을 때, 서비스와 도메인 객체가 모두 변경되는 것과, 도메인 객체만 변경되는 것의 차이인 것 같아

요약하자면, GRASP 패턴의 Creator 특징에 의거하여 RestaurantLike는 Restaurant에 논리적으로 포함되는 객체이므로 Restaurant가 RestaurantLike 객체를 생성해서 반환해도 좋다는 의견이었다.

또, 트랜잭션 스크립트 패턴과 도메인 모델 패턴을 이야기해주며 비즈니스 로직이 서비스에 설명되는 트랜잭션 스크립트 패턴보다, 도메인 안에 모든 로직이 포함되는 도메인 모델 패턴을 지향하는 관점을 덧붙여 이야기해주었다.

또, 좋아요의 로직을 Restaurant 객체의 메서드만 보면 되므로 로직을 파악하기도 쉽다고 얘기해주었다.

그리고 내 생각

원래 도메인 모델 패턴을 지향하는 사람으로서 의도가 무엇인지 파악했으나, RestaurantLike나 OAuthMember 객체와 협력하기보다 단순 객체 생성을 하는 거라면, 서비스에서 처리해주는 게 좋다고 생각한다.

또, 반대되는 로직인 dislike는 Restaurant 내에서 단순히 좋아요 수를 -1 해주는 로직만 가지고 있다. 그리고 영속은 Service에서 해주고 있다. 이 부분이 조금 통일성이 없다고 느꼈졌다.
사실 이건 괜히 트집잡는 거 같아 이야기는 하지 않았지만 허브가 이야기해주고 말랑도 공감을 느껴 결국 통일성을 준수하기로 했다.

새로 알게 된 것

물론 상황에 따라 다르겠지만, 비즈니스 로직을 처리하는 과정에서 A라는 객체가 a라는 객체의 컨텍스트를 포함하고 있다면 A에서 a 객체를 생성해주는 것도 하나의 방법이다는 것을 알게 되었다.

음.. 근데 지금 생각해보니 원래 알고 있었지만 무의식적으로 진행을 했던 것 같기도 하다. 🧐

암튼 끗 !

profile
newBlog == https://kdkdhoho.github.io

0개의 댓글