본문 바로가기
Programming/삽질일지

[8/29 TIS] 나의 리팩토링일지 - 객체가 알아야 할 것은 무엇인가?

by JKROH 2023. 8. 29.
반응형

pr link : https://github.com/codestates-seb/seb44_main_002/pull/456

 

refactor : 불필요하게 전달되던 변수 제거 by jkroh1995 · Pull Request #456 · codestates-seb/seb44_main_002

 

github.com

 

 현재 프로젝트의 계층 구조는 아래 그림과 같이 되어있다.

  • Controller Layer
    • Cocktail Controller
  • Service Layer
    • Cocktail Service
      • Cocktail Query Service, Cockatil Command Service, Cocktail Converter

 이번 리팩토링에서는 Cocktail Service와 Cocktail Converter를 주로 손봤다.

 

 Cocktail Service는 Controller Layer로부터 직접적인 메서드 콜을 받고 비즈니스 로직을 수행하는 진입 포인트의 역할을 한다(이름을 어떻게 지어야 하나 고민이 많은데, 일단은 비즈니스 로직의 시작점이라는 점에서 Service라는 이름을 그대로 사용하는 것이 가독성을 해치지 않는 것 같아 사용하고있다). 이후 Cocktail Service는 실질적인 비즈니스 로직을 담당하는 여러 하위 Service Layer의 객체들의 메서드를 호출해 필요한 비즈니스 로직을 수행한다.

 

 Cocktail Converter는 Dto와 Entity간의 매핑 역할을 담당한다. 그 과정에서 여타 도메인 엔티티들이 섞여서 함께 Response DTO로 변환되거나, 로그인한 사용자와 그렇지 않은 사용자를 구분할 필요가 있어 Mapstruct 라이브러리를 사용하지 않고 직접 구현했다.

 

 앞서 언급한대로, Cocktail Converter에는 로그인한 사용자를 위한 Response DTO와 로그인하지 않은 사용자를 위한 Response DTO를 만드는 두 로직이 있다. 이번에 리팩토링의 대상이 된 로직은 로그인하지 않은 사용자를 위한 Response DTO를 만드는 로직이었다. 기존의 로직은 아래와 같았다.

// Cocktail Service
    if (unsigned(email)) {
        log.info("# cocktailId : {} CocktailService#readCocktail 성공", cocktailId);
        return cocktailConverter.convertEntityToUnsignedResponseDto(cocktail, BOOKMARK_DEFAULT, UNSIGNED_USER_RATE);
    }


// Cocktail Converter
    public CocktailDto.Response convertEntityToUnsignedResponseDto(Cocktail cocktail, boolean unsignedUserBookmark, int unsignedUserRate) {
        User author = cocktail.getUser();
        return convertEntityToResponseDto(cocktail, unsignedUserBookmark, author)
                .recommends(cocktail.getRecommends().stream()
                        .map(recommendedCocktail
                                -> convertEntityToSimpleResponseDto(unsignedUserBookmark, recommendedCocktail))
                        .collect(Collectors.toList()))
                .userRate(unsignedUserRate)
                .build();
    }

 

 Cocktail Service에서 상수 값으로 들고 있는 BOOKMARK_DEFAULT는 boolean false다. 기본적으로 칵테일은 북마크 되어있지 않은 상태이다. 마찬가지로 상수 값으로 가지고 있는 UNSIGNED_USER_RATE 은 double 0이다. 칵테일에 대한 평점은 매기지 않은 값이 기본이다.

 

 리팩토링을 진행하기 위해 여러 객체들을 살펴보던 중, '이 값을 Service가 알고 넘겨주는 것이 맞나?' 라는 생각이 들었다. 로그인하지 않은 사용자를 위한 Response DTO를 만드는 일은 전적으로 Cocktail Converter가 담당한다. Service객체는 그런 Response DTO를 만들라고 명령을 내릴 뿐이다. 그런데, 명령을 내리는 객체에서 '너 그거 만드려면 이게 필요할걸?' 이라고 알려주는 것은 Cocktail Converter라는 객체가 알아야 할 정보까지 지나치게 많이 알고 있는 형태라고 생각됐다. 따라서, CockatilConverter#convertEntityToUnsignedResponse (지금 보니 UnsigndeUserReponseDto로 수정해야겠다.)의 parameter인 boolean unsignedUserBookmark, int unsignedUserRate를 지웠다. 대신, 해당 변수들은 Cocktail Conveter 내에 상수로 저장해 사용하는 형태로 바꿨다. 바뀐 코드는 아래와 같다.

// Cocktail Service
    if (unsigned(email)) {
        log.info("# cocktailId : {} CocktailService#readCocktail 성공", cocktailId);
        return cocktailConverter.convertEntityToUnsignedUserResponseDto(cocktail);
    }

// Cocktail Converter
    public CocktailDto.Response convertEntityToUnsignedUserResponseDto(Cocktail cocktail) {
        User author = cocktail.getUser();
        return convertEntityToResponseDto(cocktail, UNSIGNED_USER_BOOKMARK, author)
                .recommends(cocktail.getRecommends().stream()
                        .map(recommendedCocktail
                                -> convertEntityToSimpleResponseDto(UNSIGNED_USER_BOOKMARK, recommendedCocktail))
                        .collect(Collectors.toList()))
                .userRate(UNSIGNED_USER_RATE)
                .build();
    }

 이렇게 변경하고 객체를 다시 보니, 각 객체의 역할에 맞게 각자가 필요한 변수는 각자가 알고 있는 형태로 바뀌었다고 생각되었다. Cocktail Service는 반드시 필요한 argument인 cocktail만을 Cocktail Converter에 넘겨주기 때문에 Entity -> Response DTO로 변화하는 로직이 변경될 때 Cocktail Service에 까지 변경의 여파가 미칠 일이 사라졌다. 좀 더 변화와 확장에 용이한 코드로 변경된 것이다.

 

 이번 리팩토링은 별 거 아닌 리팩토링처럼 보일지 몰라도, 개인적으로는 객체지향적인 코드에 크게 가까워지게 리팩토링을 진행했다고 생각돼 상당히 만족스러운 리팩토링 과정이었다.

반응형

댓글