Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] 나의 미팅 조회 :: 리펙터링, 쿼리최적화 #588

Open
wants to merge 20 commits into
base: dev
Choose a base branch
from

Conversation

progress0407
Copy link
Collaborator

@progress0407 progress0407 commented Nov 13, 2022

Close #507

PR 타입

  • 리팩토링

반영 브랜치

feature/be/find-all-by-me -> dev

요구사항

  • 나의 미팅 조회 /meetings/me
    • 리펙터링
      • 비즈니스 로직을 도메인으로 응집
    • 1+N 쿼리 최적화

변경사항

CompositionRepository 도입

성능 최적화를 고려하면서 엔티티를 조회할 경우, 단순히 조회하여 조립하는 것 뿐인데
흐름이 복잡해서 파악하는 게 힘든 경우가 있었어요.

따라서 Service <-> Repository간에 중간 Repository를 하나 두어 (Facade Pattern) 생성 로직을 응집했어요.

이 Repository의 역할은 아래와 같아요.

  • 복잡한 조회 로직 위임
  • 조회 쿼리 최적화
  • 반환 타입이 DTO가 아닌 Entity
  • Entity(Meeintg)가 가진 Repository와의 DTO 제거
  • 일급 컬렉션 MeetingAttendances 의존 제거

반환타입이 Entity인 이유

아무래도 일반 DTO를 반환하다 보면 미팅별 지각횟수 조회 란 개념이 엔티티가 아닌 각 API별 ResponseDTO로 중복됩니다. 따라서 반환 타입을 엔티티로 잡았습니다.

Entity(Meeintg)가 가진 Repository와의 DTO 제거

중간 Repository Layer가 하나 더 있기 때문에, Meeting의 Repositroy <-> Entity간 DTO(지각횟수)를 의존성을 이곳에서 제거할 수 있습니다.

일급 컬렉션 MeetingAttendances 의존 제거

나의 미팅 조회 API의 호출과정에서 일급 컬렉션을 의존하는 로직을 제거했어요.

쿼리 5번이 나가는 이유

image

  • Meeting + Participant 조회 (fetch join) 1

    • 각 조회 결과(Meeting)에 대해
      • Participant와 지각횟수 조회 1
      • 다가오는 이벤트 조회 1
  • 5번: 1 + 2 * (1+ 1)

리펙터링 전/후 코드 (Service Layer 중심)

리펙터링

    public MyMeetingsResponse findAllByUserId(final Long userId) {
        final List<Participant> participants = participantRepository.findByUserId(userId);

        final List<MyMeetingResponse> myMeetingResponses = participants.stream()
                .map(participant -> generateMyMeetingResponse(participant, serverTimeManager.getDate()))
                .collect(Collectors.toList());

        return new MyMeetingsResponse(myMeetingResponses);
    }
    private MyMeetingResponse generateMyMeetingResponse(final Participant participant, final LocalDate today) {
        final Meeting meeting = participant.getMeeting();
        final boolean isLoginUserMaster = participant.getIsMaster();

        final MeetingAttendances meetingAttendances = getMeetingAttendances(meeting, today);
        final boolean isCoffeeTime = meetingAttendances.isTardyStackFull();
        final int tardyCount = countTardyByParticipant(participant, meetingAttendances);

        final Optional<Event> upcomingEvent = eventRepository
                .findFirstByMeetingIdAndDateGreaterThanEqualOrderByDate(meeting.getId(), today);
        if (upcomingEvent.isEmpty()) {
            return MyMeetingResponse.of(
                    meeting, tardyCount, isLoginUserMaster, isCoffeeTime, false, null
            );
        }
        final Event event = upcomingEvent.get();
        final LocalTime startTime = event.getStartTime();
        final boolean isActive = event.isSameDate(today) && serverTimeManager.isAttendanceOpen(startTime);
        final LocalTime attendanceOpenTime = serverTimeManager.calculateOpenTime(startTime);
        final LocalTime attendanceClosedTime = serverTimeManager.calculateAttendanceCloseTime(startTime);
        return MyMeetingResponse.of(
                meeting, tardyCount, isLoginUserMaster, isCoffeeTime, isActive,
                EventResponse.of(event, attendanceOpenTime, attendanceClosedTime)
        );
    }
    private int countTardyByParticipant(final Participant participant, final MeetingAttendances meetingAttendances) {
        final ParticipantAttendances participantAttendances = meetingAttendances
                .extractAttendancesByParticipant(participant);
        return participantAttendances.countTardy();
    }
    private MeetingAttendances getMeetingAttendances(final Meeting meeting, final LocalDate today) {
        final List<Long> participantIds = meeting.getParticipantIds();
        final List<Attendance> foundAttendances = attendanceRepository
                .findByParticipantIdInAndEventDateLessThanEqual(participantIds, today);
        return new MeetingAttendances(foundAttendances, participantIds.size());
    }
MeetingAttendances // 생략 ...
ParticipantAttendances // 생략 ...

리펙터링

    public MyMeetingsResponse findAllByMe(final Long loginUserId) {
        final LocalDate today = serverTimeManager.getDate();
        final List<Meeting> meetings = compositionRepository.meetingsWithTardyCount(loginUserId);
        final List<MyMeetingResponse> myMeetingsResponse = createMyMeetingsResponse(loginUserId, today, meetings);

        return new MyMeetingsResponse(myMeetingsResponse);
    }
    private List<MyMeetingResponse> createMyMeetingsResponse(final Long loginUserId,
                                                             final LocalDate today,
                                                             final List<Meeting> meetings) {
        final List<MyMeetingResponse> myMeetingsResponse = new ArrayList<>();

        for (final Meeting meeting : meetings) {
            final Event upcomingEvent = findUpComingEvent(meeting, today);
            myMeetingsResponse.add(MyMeetingResponse.of(meeting, upcomingEvent, loginUserId, serverTimeManager));
        }

        return myMeetingsResponse;
    }
    private Event findUpComingEvent(final Meeting meeting, final LocalDate today) {
        return eventRepository
                .findFirstByMeetingIdAndDateGreaterThanEqualOrderByDate(meeting.getId(), today)
                .orElse(null);
    }

쿼리 최적화

최적화 전

image

최적화 후

image

논의하고 싶은 내용

MeetingAttendances 검증 제외와 이유

현재 Jacoco 유효성 검증에 Branches 가 0.8로 되어있는데

이 Branches는 분기문에 따른 테스트 커버리지를 측정하고 있어요

그러나 테스트를 추가했음에도 불구하고 0.75가 나오는데... 그 이유를 잘 모르겠네요 ㅠ,

image

image

image

image

(테스트를 추가했지만 branches 커버리지가 올라가진 않았네요!!)

곧 사라질 클래스이기도 하고 (ParticipantAttendances와 함께)...
MeetingAttendances를 검증 대상에서 배제하는 것에 대해 제안합니다. ㅠ

@progress0407 progress0407 added the 🌾 backend Anything related to back-end label Nov 13, 2022
@progress0407 progress0407 self-assigned this Nov 13, 2022
@github-actions
Copy link

📊 checkmate-sonarqube-588 분석 결과 확인하기 링크

@shindong96 shindong96 added the 🛠 refactor Refactor code label Nov 13, 2022
Copy link
Collaborator

@YJGwon YJGwon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쿼리와 가독성 모두 엄청 개선되었네요!! 👍 몇 가지 코멘트 확인 부탁드릴게요.

@github-actions
Copy link

📊 checkmate-sonarqube-588 분석 결과 확인하기 링크

@progress0407
Copy link
Collaborator Author

리뷰 반영했어요 :) 천천히 확인 하면 될 것 같아요

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🌾 backend Anything related to back-end 🛠 refactor Refactor code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BE] 나의 미팅 조회 :: 리펙터링, 쿼리최적화
3 participants