-
Notifications
You must be signed in to change notification settings - Fork 1
[BE/feat] Schedule 1차 개발 구현 #40
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
Conversation
|
반복되는 부분들은 따로 피드백 안 달았습니다! 동일한 이슈들 찾아서 전체에 반영하면 좋을 것 같습니다. |
| @Table(name = "artist_follows") | ||
| public class ArtistFollowTmp extends BaseEntityTmp { | ||
|
|
||
| //나중에 꼭 제거해야함 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO로 작성하면 좋을 것 같습니다.
예시)
// TODO: SecurityContext에서 가져오기
이렇게 써놓으면 intelliJ에서 찾기도 쉽고 따로 표시되서 보기도 좋습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정 완료했습니다!
(해당 파일은 다른 도메인 파일 임시로 가져온 거라 나중에 삭제 예정입니다)
| new ScheduleItem(108L, 2L, "BTS", "지민 생일", ScheduleCategory.BIRTHDAY, Optional.empty(), LocalDateTime.of(year, month, 18, 0, 0), LocalDate.of(year, month, 18)) | ||
| ); | ||
| MonthlySchedulesResponse response = new MonthlySchedulesResponse(dummyList); | ||
| Long userId = 1L; // 임시 userId |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO 사용
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정 완료했습니다!
| import java.time.LocalDateTime; | ||
| import java.util.Optional; | ||
|
|
||
| public record DailyScheduleItem( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dto 명들 보니 request를 item으로 통일 하신 것 같은데 request와 response 구분 쉽도록 request를 끝에 붙여서 통일하면 좋을 것 같습니다. 다른 도메인들도 이렇게 되어있어서 맞추는 게 좋을 것 같아요!
ex) DailyScheduleItemRequest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Item -> Response
Response -> ListReponse
로 변경 및 통일 완료하였습니다!
| ); | ||
|
|
||
| @Query(""" | ||
| SELECT new back.kalender.domain.schedule.dto.response.DailyScheduleItem( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지금은 경로가 다 써있는데 import하고 DailyScheduleItem으로 사용할 수 있을 것 같습니다. 코드가 깔끔해질 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 이 부분은 저도 코드가 너무 지저분하다고 생각해서 알아봤는데, JPQL에서는 import문이 작동되지 않는다고 하더라구요.. (자바 입장에서는 그냥 String 문이라서) 그래서 우선 지금 상태를 유지하려합니다. 혹시 더 좋은 방법이 있다면 추천해주시면 감사하겠습니다!
junseokPP
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
확인했습니다
| log.info("[Schedule] [GetPerArtist] 아티스트별 월별 일정 조회 시작 - userId={}, artistId={}, year={}, month={}", | ||
| userId, artistId, year, month); | ||
|
|
||
| if (month < 1 || month > 12) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
반복되는 메서드는 공통 메서드로 빼면 좋을 듯 합니다.
private void validateMonth(int month) { if (month < 1 || month > 12) { throw new ServiceException(ErrorCode.INVALID_INPUT_VALUE); } }
|
|
||
| log.debug("[Schedule] [GetPerArtist] 팔로우 관계 확인 완료 - userId={}, artistId={}", userId, artistId); | ||
|
|
||
| try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
값검증을 try,catch로 구분하고 있던데 dto에서 @Vaild검증으로 앞단에서 검증하는 것도 좋아보입니다. try-catch는 네트워크나 외부연동일때 사용하는 걸로 알고있어서 말해봅니다.
| Long id = artistId.get(); | ||
|
|
||
| boolean isFollowing = artistFollowRepository.existsByUserIdAndArtistId(userId, id); | ||
| if (!isFollowing) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 if문도 반복사용이던데 공통 메서드로 빼는건 어떤지 물어봅니다.
private void validateFollowing(Long userId, Long artistId) { if (!artistFollowRepository.existsByUserIdAndArtistId(userId, artistId)) { throw new ServiceException(ErrorCode.ARTIST_NOT_FOLLOWED); } }
| private List<Long> getFollowedArtistIds(Long userId) { | ||
| List<ArtistFollowTmp> follows = artistFollowRepository.findAllByUserId(userId); | ||
|
|
||
| if (follows.isEmpty()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stream이 애초에 값이 없으면 빈리스트를 넣는걸로 알고있는데 if문 제거해도 될 듯 합니다.
BackSeungBeom
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
확인했습니다
🔀 Pull Request
Schedule 1차 개발 구현하였습니다.
🏷 PR 타입(Type)
아래에서 이번 PR의 종류를 선택해주세요.
🍗 관련 이슈
📝 개요(Summary)
이번에 구현한 기능 리스트는 다음과 같습니다.
🔧 코드 설명 & 변경 이유(Code Description)
1. DTO Projection을 통한 조회 성능 최적화 (N+1 방지)
JPA의 findAll 후 엔티티를 DTO로 변환하는 방식 대신, JPQL의 SELECT new ... 구문(DTO Projection)을 사용하여 필요한 데이터만 즉시 조회하도록 구현했습니다. Artist 정보를 가져오기 위한 불필요한 추가 쿼리(N+1 문제)를 원천 차단하고, Fetch Join보다 가벼운 방식으로 데이터를 조회하기 위함입니다.
예시:
ScheduleRepository2. 쿼리 성능을 위해 Index를 추가
일정 조회 시 artist_id로 필터링하고 schedule_time으로 범위 검색 및 정렬을 수행하는 패턴이 빈번하여 복합 인덱스를 적용했습니다.
3. getFollowedArtistIds 메서드 추출
getFollowingSchedules, getUpcomingEvents 등에서 팔로우 목록을 가져와서 -> 비어있는지 체크하고 -> ID 리스트로 변환하는 로직이 계속 반복되어 getFollowedArtistIds 메서드로 추출하여 반복을 줄였습니다.
4.
date필드를 다시 삭제schedule_time 필드 활용해서 날짜 데이터 받아오는 것이 혼선을 더 줄일 것이라고 판단하였습니다.
5. D-Day 계산 로직 추가
다가오는 일정 조회 시 DB 부하를 줄이고 비즈니스 로직의 유연성을 확보하기 위해,
D-Day(daysUntilEvent)필드는 DB 함수에 의존하지 않고, Service 계층에서 Java Time API를 사용하여 계산하도록 구현했습니다. Repository에서는 NULL을 반환받고 Service에서 값을 주입합니다.6. 일별 조회 & 다가오는 일정 API의 단일화
초기 설계 시에는 [특정 날짜 조회]와 [다가오는 일정 조회] 기능에 전체 아티스트 조회 API와 특정 아티스트 조회 API로 각각 분리하는 방안을 고려했으나, 최종적으로 하나의 엔드포인트(GET
/schedule/daily,/upcoming)에서 Optional 파라미터(artistId)로 분기 처리하는 방식을 채택했습니다.두 경우 모두 반환되는 데이터 구조(Response DTO)가 동일하며, 비즈니스 로직의 차이는 오직 '조회 대상을 누구로 설정하느냐' 의 차이라서 파라미터로 구분하였습니다.
🧪 테스트 절차(Test Plan)
다가오는 일정 조회 - 성공 (전체 아티스트 & D-Day 계산 검증)
다가오는 일정 조회 - 성공 (특정 아티스트 필터링)
다가오는 일정 조회 - 실패 (팔로우하지 않은 아티스트)
다가오는 일정 조회 - 실패 (Limit 값 오류)
월별 전체 조회 - 성공 (전체 아티스트)
월별 전체 조회 - 실패 (유효하지 않은 월 입력)
월별 개별 조회 - 실패 (팔로우하지 않은 아티스트 요청)
월별 개별 조회 - 실패 (유효하지 않은 월 입력)
여러 아티스트 중 특정 아티스트(BTS)만 조회 시, 정확히 해당 ID로만 필터링하여 요청한다.
하루 상세 조회 - 실패 (날짜 포맷 오류)
하루 상세 조회 - 성공 (전체 아티스트)
하루 상세 조회 - 성공 (특정 아티스트 필터링)
하루 상세 조회 - 실패 (팔로우하지 않은 아티스트 요청)
🔄 API 변경 / 흐름 영향(API & Flow Impact)
[다가오는 일정 조회] API가 추가되었습니다. (노션에 업데이트 완료)
GET /api/v1/schedule/upcoming응답데이터 예시
{ "upcomingEvents": [ { "scheduleId": 205, "artistName": "aespa", "title": "팬사인회", "scheduleCategory": "FAN_SIGN", "scheduleTime": "2025-12-20T14:00:00", "performanceId": null, "link": "https://example.com", "daysUntilEvent": 5, "location": "코엑스" } ] }주요 기능 흐름
- 월별 전체 일정 조회 (GET /schedule/following)
- 월별 개별 아티스트 일정 조회 (GET /schedule/artist/{artistId})
- 일별 상세 일정 조회 (GET /schedule/daily)
- 다가오는 일정 조회 (GET /schedule/upcoming)
👀 리뷰 포인트(Notes for Reviewer)
ServiceImpl,Repo,Entity와DTO의 관계 위주로 봐주시면 감사하겠습니다.