2022-moragora
2022-moragora copied to clipboard
[BE] 나의 미팅 조회 :: 리펙터링, 쿼리최적화
Close #507
PR 타입
- [x] 리팩토링
반영 브랜치
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번이 나가는 이유
-
Meeting + Participant 조회 (fetch join)
1
번- 각 조회 결과(Meeting)에 대해
- Participant와 지각횟수 조회
1
번 - 다가오는 이벤트 조회
1
번
- Participant와 지각횟수 조회
- 각 조회 결과(Meeting)에 대해
-
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);
}
쿼리 최적화
최적화 전
최적화 후
논의하고 싶은 내용
MeetingAttendances 검증 제외와 이유
현재 Jacoco 유효성 검증에 Branches
가 0.8로 되어있는데
이 Branches는 분기문에 따른 테스트 커버리지를 측정하고 있어요
그러나 테스트를 추가했음에도 불구하고 0.75가 나오는데... 그 이유를 잘 모르겠네요 ㅠ,
(테스트를 추가했지만 branches 커버리지가 올라가진 않았네요!!)
곧 사라질 클래스이기도 하고 (ParticipantAttendances와 함께)...
MeetingAttendances를 검증 대상에서 배제하는 것에 대해 제안합니다. ㅠ
📊 checkmate-sonarqube-588 분석 결과 확인하기 링크
📊 checkmate-sonarqube-588 분석 결과 확인하기 링크
리뷰 반영했어요 :) 천천히 확인 하면 될 것 같아요