Skip to content

Commit

Permalink
refactor#7: 스케쥴링 모듈 분리 (#9)
Browse files Browse the repository at this point in the history
* refactor: 스케줄링 서버 분리를 위한 모듈 생성

- core build.gradle JPA, MySQL 추가 및 api-server build.gradle에서 삭제
- settings.gradle schedule-server 모듈 추가
- schedule-server 모듈 생성

* refactor: 공통 사용 엔티티 core 모듈로 이동

- Ticket, Member, Purchase, Festival 엔티티 api 모듈 -> core 모듈로 이동
- Auditing config core 모듈로 이동

* refactor: 스케쥴링 모듈 분리 및 모듈 분리에 따른 api 모듈 리팩터링

- 테스트 및 비즈니스 로직 코드 이동
- JPA Repository 생성 및 테스트용 JPA Repository 생성
- FestivalService 스케쥴링 코드 주석 처리
- FestivalsApplication @EnableScheduling 삭제

* config: Jacoco 설정 변경

* test: 테스트를 위한 임베디드 레디스 설정
  • Loading branch information
HyeonJun0530 authored Dec 30, 2024
1 parent 582b48c commit 4bb4d50
Show file tree
Hide file tree
Showing 49 changed files with 511 additions and 99 deletions.
3 changes: 0 additions & 3 deletions backend/api-server/build.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-mail'

runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

// S3
implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.4.4'
implementation 'io.awspring.cloud:spring-cloud-starter-aws-secrets-manager-config:2.4.4'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

@SpringBootApplication
@EnableConfigurationProperties(CloudConfiguration.class)
@EnableScheduling
@EnableAsync
public class FestivalsApplication {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,6 @@ List<MyFestivalResponse> findFestivalsByAdminAndCursorOrderStartTimeDesc(Long ad
Long beforeId,
Pageable pageable);

@Modifying
@Query("UPDATE Festival f SET f.festivalProgressStatus = 'COMPLETED' WHERE f.festivalProgressStatus != 'COMPLETED' AND f.endTime <= :now")
void bulkUpdateCOMPLETEDFestivals(LocalDateTime now);

@Modifying
@Query("UPDATE Festival f SET f.festivalProgressStatus = 'ONGOING' WHERE f.festivalProgressStatus = 'UPCOMING' AND f.startTime <= :now")
void bulkUpdateONGOINGFestivals(LocalDateTime now);

@Query("SELECT f FROM Festival f WHERE f.festivalProgressStatus != 'COMPLETED' AND f.isDeleted = false")
List<Festival> findFestivalsWithRestartScheduler();


@Query(value = """
SELECT DISTINCT new com.wootecam.festivals.domain.festival.dto.ParticipantResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public class FestivalService {

private final FestivalRepository festivalRepository;
private final MemberRepository memberRepository;
private final FestivalSchedulerService festivalSchedulerService;

/**
* 새로운 축제를 생성합니다.
Expand All @@ -54,8 +53,8 @@ public FestivalIdResponse createFestival(FestivalCreateRequest requestDto, Long

Festival savedFestival = festivalRepository.save(festival);

//save하면서 cronTrigger등록해서 festival startTime endTime에 동적으로 상태를 변경하기 위한 코드
festivalSchedulerService.scheduleStatusUpdate(savedFestival);
// //save하면서 cronTrigger등록해서 festival startTime endTime에 동적으로 상태를 변경하기 위한 코드
// festivalSchedulerService.scheduleStatusUpdate(savedFestival);

return new FestivalIdResponse(savedFestival.getId());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.wootecam.festivals.domain.ticket.dto;

import com.wootecam.festivals.domain.festival.dto.TicketResponse;
import java.util.List;

public record TicketListResponse(Long festivalId, List<TicketResponse> tickets) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package com.wootecam.festivals.domain.ticket.repository;

import com.wootecam.festivals.domain.ticket.dto.TicketResponse;
import com.wootecam.festivals.domain.festival.dto.TicketResponse;
import com.wootecam.festivals.domain.ticket.entity.Ticket;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -15,7 +14,7 @@ public interface TicketRepository extends JpaRepository<Ticket, Long> {
Optional<Ticket> findById(Long id);

@Query("""
SELECT new com.wootecam.festivals.domain.ticket.dto.TicketResponse(
SELECT new com.wootecam.festivals.domain.festival.dto.TicketResponse(
t.id, t.name, t.detail, t.price, t.quantity,
(SELECT count(ts.id) FROM TicketStock ts WHERE ts.ticket.id = t.id AND ts.memberId IS NULL),
t.startSaleTime, t.endSaleTime, t.refundEndTime, t.createdAt, t.updatedAt
Expand All @@ -27,16 +26,4 @@ public interface TicketRepository extends JpaRepository<Ticket, Long> {

@Query("SELECT t FROM Ticket t join fetch t.festival WHERE t.id = :ticketId AND t.festival.id = :festivalId AND t.isDeleted = false")
Optional<Ticket> findByIdAndFestivalId(Long ticketId, Long festivalId);

@Query("""
SELECT new com.wootecam.festivals.domain.ticket.dto.TicketResponse(
t.id, t.name, t.detail, t.price, t.quantity,
(SELECT count(ts.id) FROM TicketStock ts WHERE ts.ticket.id = t.id AND ts.memberId IS NULL),
t.startSaleTime, t.endSaleTime, t.refundEndTime, t.createdAt, t.updatedAt
)
FROM Ticket t
WHERE t.startSaleTime >= :now OR (t.startSaleTime <= :now AND t.endSaleTime >= :now)
AND t.isDeleted = false
""")
List<TicketResponse> findUpcomingAndOngoingSaleTickets(LocalDateTime now);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import com.wootecam.festivals.domain.ticket.dto.TicketCreateRequest;
import com.wootecam.festivals.domain.ticket.dto.TicketIdResponse;
import com.wootecam.festivals.domain.ticket.dto.TicketListResponse;
import com.wootecam.festivals.domain.ticket.dto.TicketResponse;
import com.wootecam.festivals.domain.festival.dto.TicketResponse;
import com.wootecam.festivals.domain.ticket.entity.Ticket;
import com.wootecam.festivals.domain.ticket.entity.TicketStock;
import com.wootecam.festivals.domain.ticket.repository.CurrentTicketWaitRedisRepository;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.wootecam.festivals.domain.festival.dto.FestivalResponse;
import com.wootecam.festivals.domain.festival.dto.KeySetPageResponse;
import com.wootecam.festivals.domain.festival.entity.Festival;
import com.wootecam.festivals.domain.festival.entity.FestivalProgressStatus;
import com.wootecam.festivals.domain.festival.entity.FestivalPublicationStatus;
import com.wootecam.festivals.domain.festival.exception.FestivalErrorCode;
import com.wootecam.festivals.domain.festival.repository.FestivalRepository;
Expand All @@ -30,7 +29,6 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@DisplayName("FestivalService 통합 테스트")
class FestivalServiceTest extends SpringBootTestConfig {
Expand All @@ -44,8 +42,8 @@ class FestivalServiceTest extends SpringBootTestConfig {
@Autowired
private MemberRepository memberRepository;

@Autowired
private ThreadPoolTaskScheduler taskScheduler;
// @Autowired
// private ThreadPoolTaskScheduler taskScheduler;

@Autowired
private CacheManager cacheManager;
Expand All @@ -56,7 +54,7 @@ class FestivalServiceTest extends SpringBootTestConfig {
void setUp() {
clear();

taskScheduler.getScheduledThreadPoolExecutor().getQueue().clear();
// taskScheduler.getScheduledThreadPoolExecutor().getQueue().clear();
admin = memberRepository.save(
Member.builder()
.name("Test Organization")
Expand Down Expand Up @@ -103,40 +101,40 @@ void createValidFestival() {
});
}

@Test
@DisplayName("유효한 정보로 축제를 생성하면 크론 태스크가 추가된다")
void createValidFestivalAddCronTask() {
// Given
LocalDateTime now = LocalDateTime.now();
FestivalCreateRequest requestDto = new FestivalCreateRequest(
"테스트 축제",
"축제 설명",
now.plusDays(1),
now.plusDays(7)
);

// When
FestivalIdResponse responseDto = festivalService.createFestival(requestDto, admin.getId());

// Then
assertThat(responseDto).isNotNull();

Festival savedFestival = festivalRepository.findById(responseDto.festivalId())
.orElseThrow(() -> new AssertionError("저장된 축제를 찾을 수 없습니다."));

// 크론 태스크에 2개의 태스크가 추가되어야 한다.
assertThat(taskScheduler.getScheduledThreadPoolExecutor().getQueue().size()).isEqualTo(2);

/**
* 시작 시간의 크론 태스크가 실행되면 축제 상태가 ONGOING으로 변경된다.
* 종료 시간의 크론 태스크가 실행되면 축제 상태가 COMPLETED로 변경된다.
*/
taskScheduler.getScheduledThreadPoolExecutor().getQueue().forEach(runnable -> {
runnable.run();
assertThat(festivalRepository.findById(savedFestival.getId()).get().getFestivalProgressStatus()).isIn(
FestivalProgressStatus.ONGOING, FestivalProgressStatus.COMPLETED);
});
}
// @Test
// @DisplayName("유효한 정보로 축제를 생성하면 크론 태스크가 추가된다")
// void createValidFestivalAddCronTask() {
// // Given
// LocalDateTime now = LocalDateTime.now();
// FestivalCreateRequest requestDto = new FestivalCreateRequest(
// "테스트 축제",
// "축제 설명",
// now.plusDays(1),
// now.plusDays(7)
// );
//
// // When
// FestivalIdResponse responseDto = festivalService.createFestival(requestDto, admin.getId());
//
// // Then
// assertThat(responseDto).isNotNull();
//
// Festival savedFestival = festivalRepository.findById(responseDto.festivalId())
// .orElseThrow(() -> new AssertionError("저장된 축제를 찾을 수 없습니다."));
//
// // 크론 태스크에 2개의 태스크가 추가되어야 한다.
// assertThat(taskScheduler.getScheduledThreadPoolExecutor().getQueue().size()).isEqualTo(2);
//
// /**
// * 시작 시간의 크론 태스크가 실행되면 축제 상태가 ONGOING으로 변경된다.
// * 종료 시간의 크론 태스크가 실행되면 축제 상태가 COMPLETED로 변경된다.
// */
// taskScheduler.getScheduledThreadPoolExecutor().getQueue().forEach(runnable -> {
// runnable.run();
// assertThat(festivalRepository.findById(savedFestival.getId()).get().getFestivalProgressStatus()).isIn(
// FestivalProgressStatus.ONGOING, FestivalProgressStatus.COMPLETED);
// });
// }

@Test
@DisplayName("존재하지 않는 조직 ID로 축제 생성 시 예외를 던진다")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import com.wootecam.festivals.domain.ticket.dto.TicketCreateRequest;
import com.wootecam.festivals.domain.ticket.dto.TicketIdResponse;
import com.wootecam.festivals.domain.ticket.dto.TicketListResponse;
import com.wootecam.festivals.domain.ticket.dto.TicketResponse;
import com.wootecam.festivals.domain.festival.dto.TicketResponse;
import com.wootecam.festivals.domain.ticket.service.TicketService;
import java.time.LocalDateTime;
import java.util.ArrayList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.wootecam.festivals.domain.festival.entity.Festival;
import com.wootecam.festivals.domain.member.entity.Member;
import com.wootecam.festivals.domain.ticket.entity.Ticket;
import com.wootecam.festivals.domain.ticket.entity.TicketStock;
import java.time.LocalDateTime;

public final class Fixture {
Expand Down
15 changes: 12 additions & 3 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ subprojects {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' // Jackson의 Java 8 날짜/시간 모듈

runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

developmentOnly 'org.springframework.boot:spring-boot-devtools'

testCompileOnly 'org.projectlombok:lombok'
Expand Down Expand Up @@ -168,12 +171,12 @@ subprojects {
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.8
minimum = 0.5
}
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.7
minimum = 0.5
}
}
}
Expand Down Expand Up @@ -232,4 +235,10 @@ project(':queue-server') {
}

project(':e2e') {
}
}

project(':schedule-server') {
dependencies {
implementation project(':core')
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.wootecam.festivals.domain.ticket.dto;
package com.wootecam.festivals.domain.festival.dto;

import java.time.LocalDateTime;

Expand Down
2 changes: 2 additions & 0 deletions backend/schedule-server/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dependencies {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.wootecam.festivals;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
public class ScheduleServerApplication {

public static void main(String[] args) {
SpringApplication.run(ScheduleServerApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.wootecam.festivals.domain.festival.repository;

import com.wootecam.festivals.domain.festival.entity.Festival;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

public interface FestivalRepository extends JpaRepository<Festival, Long> {

@Modifying
@Query("UPDATE Festival f SET f.festivalProgressStatus = 'COMPLETED' WHERE f.festivalProgressStatus != 'COMPLETED' AND f.endTime <= :now")
void bulkUpdateCOMPLETEDFestivals(LocalDateTime now);

@Modifying
@Query("UPDATE Festival f SET f.festivalProgressStatus = 'ONGOING' WHERE f.festivalProgressStatus = 'UPCOMING' AND f.startTime <= :now")
void bulkUpdateONGOINGFestivals(LocalDateTime now);

@Query("SELECT f FROM Festival f WHERE f.festivalProgressStatus != 'COMPLETED' AND f.isDeleted = false")
List<Festival> findFestivalsWithRestartScheduler();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import com.wootecam.festivals.domain.festival.entity.Festival;
import com.wootecam.festivals.domain.festival.entity.FestivalProgressStatus;
import com.wootecam.festivals.domain.festival.exception.FestivalErrorCode;
import com.wootecam.festivals.domain.festival.repository.FestivalRepository;
import com.wootecam.festivals.global.exception.type.ApiException;
import com.wootecam.festivals.domain.festival.repository.FestivalRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.wootecam.festivals.domain.ticket.repository;

import com.wootecam.festivals.domain.festival.dto.TicketResponse;
import com.wootecam.festivals.domain.ticket.entity.Ticket;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface TicketRepository extends JpaRepository<Ticket, Long> {

@Query("""
SELECT new com.wootecam.festivals.domain.festival.dto.TicketResponse(
t.id, t.name, t.detail, t.price, t.quantity,
(SELECT count(ts.id) FROM TicketStock ts WHERE ts.ticket.id = t.id AND ts.memberId IS NULL),
t.startSaleTime, t.endSaleTime, t.refundEndTime, t.createdAt, t.updatedAt
)
FROM Ticket t
WHERE t.startSaleTime >= :now OR (t.startSaleTime <= :now AND t.endSaleTime >= :now)
AND t.isDeleted = false
""")
List<TicketResponse> findUpcomingAndOngoingSaleTickets(LocalDateTime now);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.wootecam.festivals.domain.ticket.service;

import com.wootecam.festivals.domain.ticket.dto.TicketResponse;
import com.wootecam.festivals.domain.festival.dto.TicketResponse;
import com.wootecam.festivals.domain.ticket.repository.CurrentTicketWaitRedisRepository;
import com.wootecam.festivals.domain.ticket.repository.TicketInfoRedisRepository;
import com.wootecam.festivals.domain.ticket.repository.TicketRepository;
Expand Down
Loading

0 comments on commit 4bb4d50

Please sign in to comment.