-
Notifications
You must be signed in to change notification settings - Fork 8
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] feat: 목 데이터 스케쥴러 구현(#725) #821
Conversation
Test Results154 files 154 suites 31s ⏱️ Results for commit be08c63. ♻️ This comment has been updated with latest results. |
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.
고생하셨습니다 푸우!
해당 PR 머지된다면 개발 환경에서 테스트하기 편하겠네요!
다만 몇 가지 신경 써야할 부분에 대해 고려가 필요할 것 같습니다.
리뷰에 남겼으니 확인 부탁드립니다!
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Profile({"local"}) |
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.
프로파일 잘못 입력된 것 같네요!
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 final SchoolRepository schoolRepository; | ||
private final ArtistRepository artistRepository; | ||
private final FestivalRepository festivalRepository; |
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.
기존 Repository에 findAll
메서드가 생겨야 하는데, 해당 mock 생성 기능 때문에 도메인이 영향을 받고 있네요.
새로운 mock 전용 Repository를 만들어서 사용하게 하는게 어떤가요?
public interface ForMockSchoolRepository extends JpaRepository<School, Long> {
}
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.
해당 리뷰 반영하셨다면 기존 Repository에 있는 findAll
메서드 제거할 수 있을 것 같네요!
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.
제거했습니다!
@Profile({"dev"}) | ||
@Component | ||
@RequiredArgsConstructor | ||
public class CommandLineAppStartupRunner implements CommandLineRunner { | ||
|
||
private final MockDataService mockDataService; | ||
private final MockScheduler mockScheduler; | ||
|
||
@Override | ||
public void run(String... args) { | ||
mockDataService.initialize(); | ||
mockScheduler.run(); | ||
} | ||
} |
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.
CI/CD 작업으로 서버가 다시 켜지는 일이 잦을텐데, 혹시 이에 대해 고려 해보셨나요?
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.
initialize 여부에 따라 초기 scheduler 로직 호출 여부를 다르게 해주었습니다!
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.
서버가 실행되면 mockScheduler.run();
해당 로직 호출될텐데.. 이러면 매 서버 시작마다 축제 데이터가 생길 것 같은데.. 문제가 없을까요?
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.
@RequiredArgsConstructor | ||
public class MockDataService { | ||
|
||
private static final AtomicLong FESTIVAL_SEQUENCE = new AtomicLong(); |
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 static final int COUNT_FIRST_DAY_AS_DURATION_ONE = 1; | ||
private static final int RANDOM_OFFSET = 1; | ||
private static final int MAX_END_DATE_FROM_START_DATE = 2; | ||
private final Random random = new Random(); |
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 final Random random = new Random(); | |
private final Random random = ThreadLocalRandom.current(); |
Random 객체를 생성하는 것 보다, ThreadLocalRandom을 사용하는게 더 좋습니다!
https://www.baeldung.com/java-thread-local-random
public void makeMockFestivals(int availableFestivalDuration) { | ||
List<School> allSchool = schoolRepository.findAll(); | ||
List<Artist> allArtist = artistRepository.findAll(); | ||
for (School school : allSchool) { | ||
makeFestival(availableFestivalDuration, school, allArtist); | ||
} | ||
} |
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.
availableFestivalDuration
파라미터를 받지 않고, 여기서 상수로 관리해도 될 것 같은데, 파라미터로 받으신 이유가 있나요?
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.
또한 Mock으로 세팅된 데이터 말고, 직접 사용자가 추가한 School, Artist에 영향을 받을 수 있을 것 같네요!
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.
availableFestivalDuration 파라미터를 받지 않고, 여기서 상수로 관리해도 될 것 같은데, 파라미터로 받으신 이유가 있나요?
스케쥴러 주기를 바꿀 수 있다 생각해서 파라미터로 주기를 받도록 만들었습니다!
또한 Mock으로 세팅된 데이터 말고, 직접 사용자가 추가한 School, Artist에 영향을 받을 수 있을 것 같네요!
이 부분은 dev에서 직접 데이터를 만드는 것에 편의를 주기 위해 만든 것이기 때문에 별 상관없다 판단했었습니다!
private void makeStages(Long festivalId, Queue<Artist> artists) { | ||
Festival festival = festivalRepository.findById(festivalId) | ||
.orElseThrow(() -> new NotFoundException(ErrorCode.FESTIVAL_NOT_FOUND)); | ||
LocalDate endDate = festival.getEndDate(); | ||
LocalDate dateCursor = festival.getStartDate(); | ||
while (dateCursor.isBefore(endDate) || dateCursor.equals(endDate)) { | ||
makeStage(festival, artists, dateCursor); | ||
dateCursor = dateCursor.plusDays(1); | ||
} | ||
} |
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.
LocalDate.datesUntil()
메서드 사용해보는 것도 좋겠네요!
} | ||
|
||
private void makeStage(Festival festival, Queue<Artist> artists, LocalDate localDate) { | ||
LocalDateTime startTime = localDate.atStartOfDay().plusHours(STAGE_START_HOUR); |
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.
LocalDateTime startTime = localDate.atStartOfDay().plusHours(STAGE_START_HOUR); | |
LocalDateTime startTime = localDate.atTime(STAGE_START_HOUR, 0); |
@Service | ||
@Transactional | ||
@RequiredArgsConstructor | ||
public class MockDataService { |
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.
각 도메인 별로 별도의 서비스를 만들었다면 어땠을까 하는 생각입니다. 😂
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.
Mock 내부에서 서비스를 나누는 방향을 말씀하신 건가요?
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.
MockFestivalDataService
, MockSchoolDataService
와 같이 도메인 별 데이터를 세팅해주는 서비스에 대한 얘기였어요!
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.
아무래도 이 클래스 자체가 코드가 엄청 길어지다보니 읽는데 부담이 있는데
데이터 셋팅 해주는 메소드들을 별도 클래스로 만들어주고 여기선 객체 의존 & 메소드 호출 해주는 식으로 하면 조금 더 읽기 쉬워질 것 같습니다.
너무 따로 따로 만드는게 부담 된다면 School & artsit / Festival & stage 이렇게 묶어도 괜찮지 않을까요?
private List<Long> makeStageArtists(Queue<Artist> artists) { | ||
List<Artist> result = new ArrayList<>(); | ||
for (int i = 0; i < STAGE_ARTIST_COUNT; i++) { | ||
Artist artist = artists.poll(); | ||
if (artist == null) { | ||
throw new IllegalArgumentException("축제를 구성하기 위한 아티스트가 부족합니다"); | ||
} | ||
result.add(artist); | ||
} | ||
return result.stream() | ||
.map(Artist::getId) | ||
.toList(); | ||
} |
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.
공연에 포함되는 Artist는 반드시 3으로 나눠 떨어져야 하나요?
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.
그건 아니고 임의로 하나의 공연당 3개의 아티스트를 넣었습니다.
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.
artists의 size가 STAGE_ARTIST_COUNT
개수보다 적다면 IllegalArgumentException
예외가 발생할 것 같은데..
size가 그 이하이면 예외를 발생시키지 않고, 그 사이즈 그대로 넣으면 어떤가요?
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.
반영했는데 로직이 조금 복잡해졌네요
윤하("https://i.namu.wiki/i/GXgnzRzEe4tq0YmevyZdmLNTPmVK2fzRyZtiCMA-0QSO8vbau9tah4rgP3qNokmT33-El0fx2PauysBruWpTk8B6MLu73gDgVnC3tvFeBnm3Lwo9oRreHM6slID3ilDTyFjpV2eKMO80Jf5u5E920g.webp", | ||
"https://i.namu.wiki/i/IBHythQ09eithIwC-Ix3hr0Sh-AohBVBavl0u-2WW28L6f16JM8F8Klm-0A6gDacgy4t7ATbto7wBc9xt5gwsj0IVG1y9EphSkJun-op4O9OGwvOvX8ES8aTUejOsKPFX5Iat_ubwgGWAYudo5q-Yw.webp"), | ||
뉴진스("https://i.namu.wiki/i/l1co7IV1KFVJ9HBgzxCXkMbgMfuZp_MJhjhqgB7e76DLuokabw6CNlltqr7HGzAMFqt42JfXF94Cflw5XdDuXTS2QkvomS7WYpiiJbuAn5MAjBxOA_zT93dsgyLO-gJXtV0JN-jEQ4tQ-MWtqbHJyA.webp", | ||
"https://i.namu.wiki/i/j5Fs-OjRcQsjfrrFFUVumAWauv-47tj5WPrfyIcCMuBrV5UeStJwaFK17HKcaKxvME2NVpo5PuxVgRpED1huULNxCYBydqsOs-HCLRD-kMztnZdaMJJvi1VefVB1RN0MnwMdxS7xKzxJa11qem0LMg.svg"), | ||
아이유("https://i.namu.wiki/i/k-to3_lfqjjcdnXtWMu3aLtZAArBM1nDpDP6cCWz5iJYm3HjJZ3b7i2H-4-KFSkQ6HOeftXIilMOQXvkdp83hu1FdBv5GE_PyYuacNUSygQ2cnT8vfNHqQVUReYdEYY3ob1BWoyGBE6BQRaHmnGPLw.webp", | ||
"https://i.namu.wiki/i/bJDL9DWmKsrHvvMcLOSKMlVv_E62CX6brjhiddhFuLrGPVYN6-bYcJxUHnE_KP04Ok-8PqezYob8OCepRWFBw6CTDE5Jvde2iJOZEqGgYVt6Gdbub0s9pBIOqzI1DQYZvJbAezbh-8xns_ZugPW68g.webp"), | ||
방탄소년단( | ||
"https://i.namu.wiki/i/EvnNG2DchyHHYmFtyWWzVHhPKkURdc6kdoiRVYisSKHE6BDE8itzfhfYvIMdoX0-6wvum0UgELIowRGR6cuwfNsR1OHrLamq-Rpg0F4XzFMSJHJ_xchPwFBBurNR45kOUYk2ueOKasd-xZ0g9Z14dg.webp", | ||
"https://i.namu.wiki/i/PmFR4AjifhcmdLRXQUPPce9Z7BXVWc6mVX4N22fPUKOzK5ZfjNTfo9e1M7HPa2jiEmG_tuhm43aJMGWLylyQqw.svg"), | ||
푸우("https://i.namu.wiki/i/aj4JXZR4P-ZiY6Gf0EsoPMK3XFHHsSmx0b8ysKnUDpEd0ET1BVQHZIEAGvZGHCNJrn7Nkz7N5zeYzKh3WPSTGdCdOPpj1LnlAceeLWTmMSsiXvl2fyGaEZfRjm7i6DiBTW6_7pDqIRCWfYRQFKUsdg.webp", | ||
"https://i.namu.wiki/i/_EIBF52MTqZAj5wtmY86jsU4fs7aYsdns4guDgLKYWQGoauVdCyZZiFcxc5qI92HxTUiWRRRrK0qk4Ot0qCRGpIc1GUTjUaYz1Y20IKkDuIo9472InXbpNMxcsE8PmP-taYj-7-Ql3_P557yYOk3EA.webp"), | ||
장범준("https://i.namu.wiki/i/6VAPyai_C0lBvsGytiMDu3moDOzS9UH9TDHqxzkjPWFymhQV-vcyH4q884nf1KKH2lVzLqMndBnCOTlUh4ZJE6QeB1oUQEH23d_FwMa3CFsyj8mkn3nG2DQMmJ31TN0cvCrxk6II8-IWq6C-d883zA.webp", | ||
"https://i.namu.wiki/i/WPzIZvkNW_-q9UZmEHLKMtrvAQ2g2Oo7MwbrbzWnWEkRYYAdc2cyougS-n8-BwWsLuo3knt9aaHYEGiyhd7jtg.webp"), | ||
세븐틴("https://i.namu.wiki/i/55JrvWZKaTo_Vik4Wim6-PfXiEmWqwYnAwL5_KmNg9FWiM31U5VV-lmMl76TgtON5hpGP9dIEBhub70rAQvbvOVWt7dQ6GLqvrnpbQSV3Vr1vEKRXjk_RSqCpz5a_7nupUqhB8sBJExEDHf7WGKQDw.webp", | ||
"https://i.namu.wiki/i/l4c_545au41xOK_9XRJyDh1PoNU1k9v-T5NF1UhLHxgCBQJzahV-ra8kP87FLmVFhey_OaWJcrBDJs0RmqbBB3lziiAgbM9lkDUAkENQBv4GweS2MSglXGGno9XQfnzisf5e-Z7185_U4jTqIqTiQA.svg"), | ||
아이들("https://i.namu.wiki/i/LTu_7r5vrTyZgjGb0pV79BoSI_CZr3hwLMnj2s1-ShPb-A07Nc0Gh_rGn8dic1_JwcJlB-pnSunyqmmIP-UhKRw33PlPO5GECFE2u4I5EtKIXN3c5u8_Wln6U22-Ofyjf90PxLLG1BLQziOoQ0d-pg.webp", | ||
"https://i.namu.wiki/i/KwThwv_MdMM3O7mCr-WyXHXCZhfwKtLgAof5i-wIkkgp10izoSGyTKwCgMgBcoAaIP7VocBS-D36nHI6pkiPy3E8ncWJqsqrghC8bwoaM5dOEs32E9QSxk12CZUKCzGg9AM1bJivIuBBzFBpuc5JBg.webp"), | ||
에스파("https://i.namu.wiki/i/KJ5Gpz42djU6ZUsFKnkAnpMS1zRFxUOuqzt9plzbjV_mkFlruZcDULsfEjpVw-2vxjsSKbcGflPlOThHE1DgzST-hnm9jmxPqdPMExPkqH_71ZMF6jhQVfQX6QuNZw3Bz0EZ4C1sO5vpZ-OJNfvTyg.webp", | ||
"https://i.namu.wiki/i/yAfAHme6H-HfWWQCvNAje-KInl_XM-xzRHOUmUxvRxh-HLbzk8KbG6zmD9qQXfUAeCenhHM5whZJ2nhQk0lanzT1LVja3BEQCVk1yPWABxy4NygdaLGyNpiRZTwVFkhD_PnCcESdUQ7-oEtK0YptsQ.webp"), |
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.
추가로 확장자가 .webp
, .svg
와 같이 끝나는데 클라이언트에서 해당 확장자 지원하는지 확인이 필요할 것 같네요!
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 final SchoolCommandService schoolCommandService; | ||
|
||
|
||
public void initialize() { |
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 final SchoolCommandService schoolCommandService; | |
public void initialize() { | |
private final SchoolCommandService schoolCommandService; | |
public void initialize() { |
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.
리뷰 반영 확인했습니다! 고생하셨어요!
기존 기능을 수정하지 않고, 새로운 기능을 추가했으니, 추후 Mock 데이터에 변경이 필요하더라도 편하게 수정할 수 있겠네요!
boolean existsByFestivalId(Long festivalId); | ||
|
||
List<Stage> findAllByFestivalId(Long festivalId); | ||
|
||
} |
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.
해당 수정만 없애주면 완벽할 것 같네요!
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.
artists의 size가 STAGE_ARTIST_COUNT 개수보다 적다면 IllegalArgumentException 예외가 발생할 것 같은데..
size가 그 이하이면 예외를 발생시키지 않고, 그 사이즈 그대로 넣으면 어떤가요?
StageCommandService 에서 Validator 의
public static void notDuplicate(List<?> list, String fieldName) {
// avoid NPE
if (list == null || list.isEmpty()) {
return;
}
if (new HashSet<>(list).size() != list.size()) {
throw new ValidException("%s에 중복된 값이 있습니다.".formatted(fieldName));
}
}
를 호출하기 때문에 하나의 Stage 에서 중복된 artist들을 넣을 수 없습니다!!
만약 같은 artist로 구성하려면 직접 Stage를 만들고 queryInfo도 생성해야 하는데 이 방식은 유지보수가 상당히 복잡해질 것 같네요 😂
그래서 아티스트가 STAGE_ARTIST_COUNT 이하라면 예외 상황이 맞다 판단하였고
같은 축제 내에서는 아티스트가 중복될 수 있도록 변경했습니다.
이때 Stage 내에서는 여전히 unique 한 값으로 구성됩니다.
private final SchoolRepository schoolRepository; | ||
private final ArtistRepository artistRepository; | ||
private final FestivalRepository festivalRepository; |
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 makeRegionSchools(SchoolRegion schoolRegion) { | ||
for (int i = 0; i < 3; i++) { | ||
int schoolNumber = i + 1; | ||
String schoolName = schoolRegion.name() + schoolNumber; | ||
schoolCommandService.createSchool(new SchoolCreateCommand( | ||
schoolName, | ||
schoolName + ".com", | ||
schoolRegion, | ||
null, | ||
null | ||
) | ||
); | ||
} | ||
} |
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 List<Long> makeStageArtists(Queue<Artist> artists) { | ||
List<Artist> result = new ArrayList<>(); | ||
for (int i = 0; i < STAGE_ARTIST_COUNT; i++) { | ||
Artist artist = artists.poll(); | ||
if (artist == null) { | ||
throw new IllegalArgumentException("축제를 구성하기 위한 아티스트가 부족합니다"); | ||
} | ||
result.add(artist); | ||
} | ||
return result.stream() | ||
.map(Artist::getId) | ||
.toList(); | ||
} |
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.
반영했는데 로직이 조금 복잡해졌네요
굿굿 고생하셨습니다! |
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.
푸우 고생하셨습니다. 꼼꼼한 테스트 코드도 좋네요.
저도 글렌이 코멘트 달아주신 것 처럼 기존에 DB에 존재하는 혹은 테스트용으로 클라이언트에서 직접 넣은 학교, 아티스트 등도 데이터 들어가는 거를 고민해봤는데..
푸우가 말씀해주신 것처럼 어차피 데이터 셋팅을 위한거니까 별 상관이 없다고 느껴지네요.
다만 제 생각엔 코멘트 남긴 것처럼 MockDataService에서 insert 해주는 책임을 조금씩 나누어 가지면 좀 더 보기 편하지 않을까 생각합니다!
@Service | ||
@Transactional | ||
@RequiredArgsConstructor | ||
public class MockDataService { |
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.
아무래도 이 클래스 자체가 코드가 엄청 길어지다보니 읽는데 부담이 있는데
데이터 셋팅 해주는 메소드들을 별도 클래스로 만들어주고 여기선 객체 의존 & 메소드 호출 해주는 식으로 하면 조금 더 읽기 쉬워질 것 같습니다.
너무 따로 따로 만드는게 부담 된다면 School & artsit / Festival & stage 이렇게 묶어도 괜찮지 않을까요?
📌 관련 이슈
✨ PR 세부 내용
목 데이터 스케줄러를 구현했습니다.
전체 흐름은
이때 만약 학교가 하나라도 존재한다면 이미 초기화 이후 데이터라 판단하여 초기 학교, 아티스트 데이터를 만들지 않습니다.
그래서 dev db의 학교들을 다 지워야하는데 이 방법 외에 좋은 아이디어 있으시면 공유 부탁드립니다.
생성 되는 Festival은 요청 파라미터 이내의 기간 내에서 최대 3일의 기간을 갖는 축제입니다.
각 축제는 기간만큼 Stage를 가지며 이때 Stage 간의 중복 아티스트를 만들지 않기위해 Queue를 Festival 마다 생성해주었습니다.
생성 과정은 QueryInfo 생성과 같은 부가적인 최적화 로직을 자연스럽게 수용할 수 있도록 CommandService를 통해 생성해 주었습니다.
2와 동일한 과정을 거칩니다.