(Spring) 취향 기반 향수 추천 서비스 - 4. 두번째 기능 개발 (이미 쓰는 향수와 유사한 향수 찾기)

김준석·2023년 3월 19일
1

향수 추천 서비스

목록 보기
5/21

목표

내가 쓰고있는 향수와 비슷한 결의 향수를 추천해주는 기능이다.
플로우는 다음과 같다.
브랜드 입력 -> 내가 쓰는 향수 선택 -> (향,무드,성별)을 토대로 유사한 향수를 제시해준다.)

원래 별 생각 없이 하루동안 구현했었던 기능이다. 하지만 여러 제약사항이 있다는 것을 뒤늦게 깨닿고 과감하게 새로 만들었다. 또한, 첫번째 기능을 리팩토링하면서 두번째 기능 또한 더 효율적으로 코드를 짤 수 있다고 생각이 들어서 다시 만들었다.

기존 로직
SimilarPerfumeRecommend.java

@Service
public class SimilarPerfumeRecommend {
    private final SurveyRepository surveyRepository;
    private final SurveyUtil surveyUtil;
    private final SurveyService surveyService;
    private final PerfumeRepository perfumeRepository;

    public SimilarPerfumeRecommend(SurveyRepository surveyRepository, SurveyUtil surveyUtil, SurveyService surveyService,
                                   PerfumeRepository perfumeRepository) {
        this.surveyService = surveyService;
        this.surveyRepository = surveyRepository;
        this.surveyUtil = surveyUtil;
        this.perfumeRepository = perfumeRepository;
    }

    private List<Survey> extractFirstFeature(PerfumeResponseDto perfumeResponseDto) {
        return surveyRepository.findByGenderAnswerOrGenderAnswer(surveyService.findSurveyById(perfumeResponseDto.getId()).getGenderAnswer(), "젠더리스");
    }

    private List<Survey> extractSecondFeature(PerfumeResponseDto perfumeResponseDto) {
        return surveyRepository.findByScentAnswer(surveyService.findSurveyById(perfumeResponseDto.getId()).getScentAnswer());
    }

    private List<Survey> extractThirdFeature(PerfumeResponseDto perfumeResponseDto) {
        return surveyRepository.findByMoodAnswerContaining(surveyService.findSurveyById(perfumeResponseDto.getId()).getMoodAnswer());
    }

    private List<Survey> filterGenderFeature(PerfumeResponseDto perfumeResponseDto) {
        return surveyUtil.addList(extractFirstFeature(perfumeResponseDto), surveyRepository.findByGenderAnswer("젠더리스"));
    }

    public List<Perfume> showSimilarPerfume(PerfumeResponseDto perfumeResponseDto) {
        List<Survey> firstComparedList = surveyUtil.compareTwoFilteredSurveyData
                (filterGenderFeature(perfumeResponseDto), extractSecondFeature(perfumeResponseDto));
        List<Survey> result = surveyUtil.compareTwoFilteredSurveyData(firstComparedList, extractThirdFeature(perfumeResponseDto));

        return findExceptRequestedPerfume(findPerfumeData(result),perfumeResponseDto);
    }

    public List<Perfume> findPerfumeData(List<Survey> surveyList) {
        List<Perfume> perfumeList = new ArrayList<>();
        for (Survey survey : surveyList) {
            perfumeList.add(perfumeRepository.findById(survey.getId()).orElseThrow(PerfumeNotFoundException::new));
        }
        return perfumeList;
    }

    private List<Perfume> findExceptRequestedPerfume(List<Perfume> perfumeList, PerfumeResponseDto perfumeResponseDto) {
        return perfumeList.stream()
                .filter(x -> x.getId() != perfumeResponseDto.getId())
                .collect(Collectors.toList());
    }

}

해당 코드의 문제점

  • 5개의 하위 메서드를 호출하며 상위에서 또 메서드를 호출하는 방식이다.
  • 설문을 다 찾은다음 그 설문 번호로 향수를 다시한번 찾는다.(findPerfumeData메서드) 결국 쿼리를 두번 수행한다.
  • Db Mood Column에 두가지 이상의 무드가 들어있으면 아무것도 찾기 못한다.

따라서,
1. 첫번째 기능을 만들며 만들어두었던 조건 검색을 활용한다.
2. Mood Column에 두가지 이상의 무드가 들어있으면 쪼개서 찾는다.
를 중점으로 코드를 새로 새웠다.

###Service
SimilarPerfumeService.java

@Service
public class SimilarPerfumeService {

    private static final String BLANK = "\\s";
    private static final int FIRST_MOOD = 0;
    private static final int MOOD_COLUMN_SIZE = 1;
    private final SurveyService surveyService;
    private final SurveyRepository surveyRepository;
    private final SurveyUtil surveyUtil;

    public SimilarPerfumeService(SurveyService surveyService,
                                 SurveyRepository surveyRepository, SurveyUtil surveyUtil) {
        this.surveyService = surveyService;
        this.surveyRepository = surveyRepository;
        this.surveyUtil = surveyUtil;
    }

    private List<Survey> findExceptRequestedPerfume(List<Survey> surveyList, SurveyRequestDto surveyRequestDto) {
        return surveyList.stream()
                .filter(x -> x.getId() != surveyRequestDto.getId())
                .collect(Collectors.toList());
    }

    public List<Perfume> showSimilarPerfume(PerfumeRequestDto perfumeRequestDto) {
        Survey survey = surveyService.findSurveyById(perfumeRequestDto.getId());
        List<Survey> filteredSurveyList = surveyRepository.findByGenderAnswerAndScentAnswer(survey.getGenderAnswer(), survey.getScentAnswer());
        filteredSurveyList = surveyUtil.addList(filteredSurveyList, surveyRepository.findByGenderAnswerAndScentAnswer(SurveyType.GENDERLESS.getValue(), survey.getScentAnswer()));
        filteredSurveyList = surveyService.filterByMood(splitMoodAnswer(survey), filteredSurveyList);
        filteredSurveyList = findExceptRequestedPerfume(filteredSurveyList, SurveyRequestDto.makeDto(survey));

        return surveyService.convertToPerfumeData(filteredSurveyList);
    }

    public SurveyRequestDto splitMoodAnswer(Survey survey) {
        String[] moodAnswerArray = survey.getMoodAnswer().split(BLANK);
        if (moodAnswerArray.length == MOOD_COLUMN_SIZE) {
            return SurveyRequestDto.builder().moodAnswer(survey.getMoodAnswer()).build();
        }
        return SurveyRequestDto.builder().moodAnswer(moodAnswerArray[FIRST_MOOD]).build();
    }
}
  • splitMoodAnswer()에서 길이를 확인한 후 두가지 이상으로 이루어져있으면 쪼갠다. 여러 방법이 있겠지만, 첫번째 Mood가 가장 가까운 무드이기 때문에 첫번째 무드로 사용했다.

  • findExceptRequestedPerfume()은 요청으로 보낸 향수 (내가 갖고 있는 향수)를 제외하고 찾아주는 메서드이다.
    -showSimilarPerfume()에서 해당 향수의 향,무드, 성별을 필터링하여 comvertToPerfumeData()를 호출한다.

  • convertToPerfumeData()는 설문에서 ManyToOne으로 맵핑한 Perfume을 찾아주는 메서드이다.

  • (수정) splitMoodAnswer()은 유사항수 추천 뿐만 아니라 세부정보 조회에서도 사용할 수 있게 구조를 바꾸었다. SimilarPerfumeService 클래스에서 가질 책임이 아니라고 판단하여 Util클래스로 옮겼다.

테스트코드도 다시 다 짤 생각하니까 막막하다..ㅜ

PostMan 테스트 결과

전체 코드 보기

profile
기록하면서 성장하기!

0개의 댓글