[트러블 슈팅] 간결한 Service 로직 만들기

#트러블 슈팅#수업리뷰사이트

기존에 서비스 클래스에서 나오는 로직들이 굉장히 길어졌다. 코드의 양이 200줄이 넘어가기 시작했고, 각 클래스들마다 어떤 기능들을 포함하고 있는지 헷갈리기 시작했다.

나는 기존의 동작들의 변형이 없도록 확인하며 가고 싶었고, Test를 서비스 로직에서 추가하며 작업을 진행할려고하였다. 그리고 테스트를 추가하기 위해서 로직을 살펴보던 도중 이런 고민이 들었다.

service 로직이 하는 일이 너무 많다

    @Transactional
    public Long addReviewPost(ClassReviewRequest request){
      ... 강의 불러오는 로직
      ... 유저 불러오는 로직
      ... 수강후기 객체 빌더 생성
      ... 수강후기 저장
      ... 수강후기 특성 요소 객체 생성 및 업데이트
      ... 평점 로직
      ... 리뷰 개수 카운트 로직
    }

테스트 메소드를 하나 작성하면 호출하는 메소드하나가 거의 뭐 전부 많은 일을 하고있다. 천재 머신이다 그냥 혼자서 일을 다하는 셈이다.

객체지향 사실과 오해 에서 말하는 하나의 메소드가 너무 많은 일을 하면 안된다. 천재지변? 뭐 신 같은 메소드가 존재하면 안된다고 하였다.

그래서 이 메소드가 하는 일들을 그러니까. 로직들을 한번 쪼개서 각자 있어야하는 자리로 옮겨주고 싶었다. 그래서 로직을 살펴보니.. ReviewService에 있는 로직들을 옮길 곳은 첫단인 도메인 클래스라는 것을 판단했다. 그런데 도메인 클래스를 보고나니 그 다음에 고민이 생겼다.

Lecture와 다른 주제의 로직들을 어떻게 할까?

Lecture에 있는 일부 속성값과 기능들

    @ColumnDefault("0")
    @Column(nullable = true)
    private Double importantNormalization;

    @Column(nullable = true)
    @ColumnDefault("0")
    private Double difficultyNormalization;

    @Column(nullable = true)
    @ColumnDefault("0")
    private Double funnyNormalization;


    public Long updateAverageStarLating(Long updateLating){
        this.averageStarLating = updateLating;
        return this.averageStarLating;
    }

    public Long updateTotalStarLating(Long plusLating){
        this.totalStarLating = totalStarLating + plusLating;
        return this.totalStarLating;
    }

    public Long addReviewCount(Lecture lecture){

        this.reviewCount = reviewCount+1;
        return this.reviewCount;
    }

정규화를 진행하는 속성값, 그리고 평균 평점, 총 평점을 업데이트하는 로직 이런 것들은 Lecture 라는 정보에 포함되는 내용이나 기능 로직이 같이 있으니 매우 혼잡했다. 이런 로직들이 존재하면 어울리는 클래스가 필요했다.

그래서 생각했다. 획기적인 방법이라 생각이 들었던 클래스 바로 Embeddable 클래스이다.

Embeddable의 장점

Embeddable을 적용하면 아래와 같은 장점이 있다.

  1. 응집도 향상 : 관련된 필드들이 하나의 객체로 그룹화가 가능하다.
  2. 재사용성 : 동일한 Embeddable을 여러 엔티티에서 사용 가능하다.
  3. 비즈니스 로직 캡슐화 : 관련 로직이 해당 객체 내부에 위치할 수 있다.
  4. 코드 가독성 : 엔티티 클래스가 더 간결해진다.
  5. 테스트 용이성 : 각 Embeddable 객체별로 독립적인 테스트가 가능하다.

현재 작업의 목표는 Service 로직의 간결화이다. 그리고 테스트가 용이하도록, 기능 로직들이 분담되면 그에 해당하는 클래스들을 각자 테스트하면 된다. 모든 면에서 나의 애플리케이션에 도움이 된다.

Embeddable의 단점

  1. 독립적 관리 불가 : 엔티티에 종속적이므로, @Embeddable 객체 자체를 별도로 조회하거나 저장할 수 없음
  2. 복잡한 매핑 어려움 : 관계 매핑은 권장되지 않음(내부에서 다른 엔티티를 참조하는것)
  3. 컬럼 중복문제 : 동일한 Embeddable 을 여러 군데에서 사용할 때, 컬럼명이 충돌할 수 있음
  4. 엔티티와 동일한 생명주기 : 값 타입은 엔티티의 생명주기에 종속되어, 독립적인 변경 감지가 어려움

위 단점 4가지 모두 해당되지 않는 상황이었고, 문제없이 사용가능해보인다. 혹시나 해당되는 내용들이 생긴다면 즉시 테스트 케이스를 추가하며 검증을 진행할 예정이다.

    @Test
    @DisplayName("별점이 추가될때, 평균 별점과 총합 별점을 변경한다.")
    void addRating() {
        Double rating = classReviewRequest.getStarLating();

        System.out.println(classReviewRequest.getStarLating());

        StarRating starRating = StarRating.createRatingBuilder();
        starRating.addRating(rating);

        assertThat(starRating.getTotalRating()).isNotEqualTo(0.0);
        assertThat(starRating.getAverageRating()).isNotEqualTo(0.0);
        assertThat(starRating.getReviewCount()).isEqualTo(1);
        starRating.addRating(3.0);

        assertThat(starRating.getAverageRating()).isEqualTo(3.5);
        assertThat(starRating.getReviewCount()).isEqualTo(2);
    }

위는 StarRating 이라는 클래스를 구성하여, 해당 클래스에 대한 테스트 케이스 중 일부이다. 평점이 올바르게 변경되는지 확인하는 케이스인데, 기존에 Lecture 클래스에서 존재할떄의 메서드명들을 버리고 행위에 집중한 메서드명으로 변경하였다.