From 43eadcb8c0697c455462a5fe7e78bd1351b5fad7 Mon Sep 17 00:00:00 2001 From: mirageoasis Date: Sat, 28 Dec 2024 12:14:29 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20redis=20=EC=83=88=EB=A1=9C=EC=9A=B4?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/NewRedisReservationService.java | 60 +++++++++++++++++++ .../config/ReservationServiceContainer.java | 13 +++- .../ticket/service/CacheReservationTest.java | 17 ++++-- 3 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/thirdparty/ticketing/domain/ticket/service/NewRedisReservationService.java diff --git a/src/main/java/com/thirdparty/ticketing/domain/ticket/service/NewRedisReservationService.java b/src/main/java/com/thirdparty/ticketing/domain/ticket/service/NewRedisReservationService.java new file mode 100644 index 00000000..1c18db6e --- /dev/null +++ b/src/main/java/com/thirdparty/ticketing/domain/ticket/service/NewRedisReservationService.java @@ -0,0 +1,60 @@ +package com.thirdparty.ticketing.domain.ticket.service; + +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.StringRedisTemplate; + +import com.thirdparty.ticketing.domain.common.ErrorCode; +import com.thirdparty.ticketing.domain.common.TicketingException; +import com.thirdparty.ticketing.domain.member.Member; +import com.thirdparty.ticketing.domain.member.repository.MemberRepository; +import com.thirdparty.ticketing.domain.seat.Seat; +import com.thirdparty.ticketing.domain.seat.repository.SeatRepository; +import com.thirdparty.ticketing.domain.ticket.dto.request.SeatSelectionRequest; +import com.thirdparty.ticketing.domain.ticket.dto.request.TicketPaymentRequest; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +@RequiredArgsConstructor +public class NewRedisReservationService implements ReservationService{ + + private final MemberRepository memberRepository; + private final SeatRepository seatRepository; + private final StringRedisTemplate redisTemplate; + private final String SEAT_CONST = "seat-selected-number:"; + + @Value("${ticketing.reservation.release-delay-seconds}") + private int reservationReleaseDelay; + + @Override + public void selectSeat(String memberEmail, SeatSelectionRequest seatSelectionRequest) { + Seat seat = + seatRepository.findById(seatSelectionRequest.getSeatId()) + .orElseThrow(() -> new TicketingException(ErrorCode.NOT_FOUND_SEAT)); + + Member member = + memberRepository.findByEmail(memberEmail) + .orElseThrow(() -> new TicketingException(ErrorCode.NOT_FOUND_MEMBER)); + + Boolean result = redisTemplate.opsForValue() + .setIfAbsent(SEAT_CONST + seat.getSeatId(), member.getEmail(), reservationReleaseDelay, TimeUnit.SECONDS); + + if (Boolean.FALSE.equals(result)) { + throw new TicketingException(ErrorCode.NOT_SELECTABLE_SEAT); + } + } + + @Override + public void reservationTicket(String memberEmail, TicketPaymentRequest ticketPaymentRequest) { + // do nothing + } + + @Override + public void releaseSeat(String memberEmail, SeatSelectionRequest seatSelectionRequest) { + redisTemplate.delete(SEAT_CONST + seatSelectionRequest.getSeatId()); + } +} diff --git a/src/main/java/com/thirdparty/ticketing/global/config/ReservationServiceContainer.java b/src/main/java/com/thirdparty/ticketing/global/config/ReservationServiceContainer.java index 8c6f974f..03e232f5 100644 --- a/src/main/java/com/thirdparty/ticketing/global/config/ReservationServiceContainer.java +++ b/src/main/java/com/thirdparty/ticketing/global/config/ReservationServiceContainer.java @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.core.StringRedisTemplate; import com.thirdparty.ticketing.domain.common.EventPublisher; import com.thirdparty.ticketing.domain.common.LettuceRepository; @@ -21,6 +22,17 @@ @Configuration public class ReservationServiceContainer { + + @Bean + @Primary + public ReservationService newRedisReservationService( + MemberRepository memberRepository, + SeatRepository seatRepository, + StringRedisTemplate redisTemplate) { + return new NewRedisReservationService(memberRepository, seatRepository, redisTemplate); + } + + @Bean public ReservationService redissonReservationServiceProxy( RedissonClient redissonClient, @@ -46,7 +58,6 @@ ReservationService optimisticReservationServiceProxy( return new OptimisticReservationServiceProxy(persistenceOptimisticReservationService); } - @Primary @Bean ReservationService pessimisticReservationServiceProxy( @Qualifier("persistencePessimisticReservationService") diff --git a/src/test/java/com/thirdparty/ticketing/domain/ticket/service/CacheReservationTest.java b/src/test/java/com/thirdparty/ticketing/domain/ticket/service/CacheReservationTest.java index 9525f4eb..201fb180 100644 --- a/src/test/java/com/thirdparty/ticketing/domain/ticket/service/CacheReservationTest.java +++ b/src/test/java/com/thirdparty/ticketing/domain/ticket/service/CacheReservationTest.java @@ -14,8 +14,11 @@ import org.junit.jupiter.api.Test; import org.mockito.MockitoAnnotations; import org.redisson.api.RedissonClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.redis.core.StringRedisTemplate; import com.thirdparty.ticketing.domain.common.LettuceRepository; import com.thirdparty.ticketing.domain.common.TicketingException; @@ -36,6 +39,7 @@ public class CacheReservationTest extends BaseIntegrationTest { + private static final Logger log = LoggerFactory.getLogger(CacheReservationTest.class); @Autowired private SeatRepository seatRepository; @Autowired private MemberRepository memberRepository; @@ -46,9 +50,7 @@ public class CacheReservationTest extends BaseIntegrationTest { @Autowired private PerformanceRepository performanceRepository; - @Autowired private LettuceRepository lettuceRepository; - - @Autowired private RedissonClient redissonClient; + @Autowired private StringRedisTemplate redisTemplate; @Autowired @Qualifier("lettuceReservationServiceProxy") @@ -97,6 +99,7 @@ void setUp() { .seatCode("R") .seatStatus(SeatStatus.SELECTABLE) .build()); + redisTemplate.getConnectionFactory().getConnection().serverCommands().flushAll(); } @AfterEach @@ -108,6 +111,11 @@ void breakUp() { memberRepository.deleteAll(); } + @Test + public void testConcurrentSeatSelectionWithNewRedis() throws InterruptedException { + runConcurrentSeatSelectionTest(new NewRedisReservationService(memberRepository, seatRepository, redisTemplate)); + } + @Test public void testConcurrentSeatSelectionWithLettuce() throws InterruptedException { runConcurrentSeatSelectionTest(lettuceCacheTicketService); @@ -142,6 +150,7 @@ private void runConcurrentSeatSelectionTest(ReservationService reservationServic } catch (TicketingException e) { failureSelections.incrementAndGet(); } catch (Exception e) { + log.error("Error occurred", e); } finally { // latch 카운트 감소, 스레드 완료 시 호출 latch.countDown(); @@ -151,8 +160,6 @@ private void runConcurrentSeatSelectionTest(ReservationService reservationServic latch.await(); - Seat reservedSeat = seatRepository.findById(seat.getSeatId()).orElseThrow(); - assertThat(reservedSeat.getMember()).isNotNull(); assertThat(successfulSelections.get()).isEqualTo(1); assertThat(failureSelections.get()).isEqualTo(4); } From d495501d55c5a4228a6f31a9f55c4d4329a3ba0e Mon Sep 17 00:00:00 2001 From: mirageoasis Date: Sat, 28 Dec 2024 12:25:06 +0900 Subject: [PATCH 2/7] =?UTF-8?q?chore:=20spotless=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/build.gradle b/build.gradle index 670f6115..9dd14ec8 100644 --- a/build.gradle +++ b/build.gradle @@ -69,17 +69,6 @@ dependencies { testImplementation 'org.testcontainers:junit-jupiter:1.20.1' } -spotless { - java { - target 'src/**/*.java' // 대상 파일 지정 - googleJavaFormat().aosp() - importOrder('java', 'javax', 'jakarta', 'org', 'com') - removeUnusedImports() - trimTrailingWhitespace() - endWithNewline() - } -} - asciidoctor { inputs.dir snippetsDir configurations 'asciidoctorExt' From beb25f2bb48e59661e5b73d79fc6e0dc639caeae Mon Sep 17 00:00:00 2001 From: mirageoasis Date: Sat, 28 Dec 2024 12:27:28 +0900 Subject: [PATCH 3/7] =?UTF-8?q?chore:=20spotless=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build.gradle b/build.gradle index 9dd14ec8..ec5a35e8 100644 --- a/build.gradle +++ b/build.gradle @@ -83,10 +83,6 @@ tasks.named('test') { useJUnitPlatform() } -tasks.named('spotlessJava') { - dependsOn 'copyYml' -} - tasks.register('copyDocument', Copy) { dependsOn asciidoctor from file("build/docs/asciidoc") From 2d6b157a8b9a7aca4192a26e8e16fa894bf91441 Mon Sep 17 00:00:00 2001 From: mirageoasis Date: Sun, 29 Dec 2024 14:41:41 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=B9=88=EC=A3=BC=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/ticket/service/CacheReservationTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/thirdparty/ticketing/domain/ticket/service/CacheReservationTest.java b/src/test/java/com/thirdparty/ticketing/domain/ticket/service/CacheReservationTest.java index 201fb180..82b2d0f0 100644 --- a/src/test/java/com/thirdparty/ticketing/domain/ticket/service/CacheReservationTest.java +++ b/src/test/java/com/thirdparty/ticketing/domain/ticket/service/CacheReservationTest.java @@ -60,6 +60,10 @@ public class CacheReservationTest extends BaseIntegrationTest { @Qualifier("redissonReservationServiceProxy") private ReservationService redissonReservationServiceProxy; + @Autowired + @Qualifier("newRedisReservationService") + private ReservationService newRedisReservationService; + private List members; private Seat seat; private Zone zone; @@ -113,7 +117,7 @@ void breakUp() { @Test public void testConcurrentSeatSelectionWithNewRedis() throws InterruptedException { - runConcurrentSeatSelectionTest(new NewRedisReservationService(memberRepository, seatRepository, redisTemplate)); + runConcurrentSeatSelectionTest(newRedisReservationService); } @Test From eff82151eca5c5337484f141bc09f59415e2be2c Mon Sep 17 00:00:00 2001 From: mirageoasis Date: Sun, 29 Dec 2024 14:42:01 +0900 Subject: [PATCH 5/7] =?UTF-8?q?chore:=20spotless=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/style.yml | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .github/workflows/style.yml diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml deleted file mode 100644 index 819baca4..00000000 --- a/.github/workflows/style.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Spotless Check -on: [ pull_request ] - -jobs: - spotless: - name: Spotless Check - runs-on: ubuntu-latest - - steps: - - name: Clone repo with submodules - uses: actions/checkout@v3 - with: - fetch-depth: 1 - submodules: true # 서브모듈도 함께 체크아웃 - token: ${{secrets.PRIVATE_TOKEN}} - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'adopt' - - - name: Cache Gradle dependencies - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Run Spotless - run: ./gradlew spotlessCheck From 305728eadee3009b33e7ece53645d0978148a7b5 Mon Sep 17 00:00:00 2001 From: mirageoasis Date: Sun, 29 Dec 2024 14:44:14 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor:=20scheduler=20=EC=A3=BC=EC=9E=85?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=EC=B7=A8=EC=86=8C=20=EC=8B=9C=EA=B0=84=20container?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=B2=98=EB=A6=AC=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/ReservationServiceContainer.java | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/thirdparty/ticketing/global/config/ReservationServiceContainer.java b/src/main/java/com/thirdparty/ticketing/global/config/ReservationServiceContainer.java index 03e232f5..d1f78348 100644 --- a/src/main/java/com/thirdparty/ticketing/global/config/ReservationServiceContainer.java +++ b/src/main/java/com/thirdparty/ticketing/global/config/ReservationServiceContainer.java @@ -1,7 +1,11 @@ package com.thirdparty.ticketing.global.config; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -23,13 +27,18 @@ @Configuration public class ReservationServiceContainer { + @Value("${ticketing.reservation.release-delay-seconds}") + private int reservationReleaseDelay; + + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5); + @Bean @Primary public ReservationService newRedisReservationService( MemberRepository memberRepository, SeatRepository seatRepository, StringRedisTemplate redisTemplate) { - return new NewRedisReservationService(memberRepository, seatRepository, redisTemplate); + return new NewRedisReservationService(memberRepository, seatRepository, redisTemplate, reservationReleaseDelay); } @@ -80,7 +89,10 @@ public ReservationTransactionService cacheReservationTransactionService( paymentProcessor, lockSeatStrategy, eventPublisher, - reservationManager); + reservationManager, + reservationReleaseDelay, + scheduler + ); } @Bean @@ -98,7 +110,10 @@ public ReservationTransactionService persistenceOptimisticReservationService( paymentProcessor, lockSeatStrategy, eventPublisher, - reservationManager); + reservationManager, + reservationReleaseDelay, + scheduler + ); } @Bean @@ -116,6 +131,9 @@ public ReservationTransactionService persistencePessimisticReservationService( paymentProcessor, lockSeatStrategy, eventPublisher, - reservationManager); + reservationManager, + reservationReleaseDelay, + scheduler + ); } } From 69a84913384ecd1a021d635c98444215b989b961 Mon Sep 17 00:00:00 2001 From: mirageoasis Date: Sun, 29 Dec 2024 14:46:03 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20Container=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=98=88=EC=95=BD=20=EC=B7=A8=EC=86=8C=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=A3=BC=EC=9E=85=20=EB=B0=9B=EA=B8=B0=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/ticket/service/NewRedisReservationService.java | 5 ++--- .../ticket/service/ReservationTransactionService.java | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/thirdparty/ticketing/domain/ticket/service/NewRedisReservationService.java b/src/main/java/com/thirdparty/ticketing/domain/ticket/service/NewRedisReservationService.java index 1c18db6e..066b072b 100644 --- a/src/main/java/com/thirdparty/ticketing/domain/ticket/service/NewRedisReservationService.java +++ b/src/main/java/com/thirdparty/ticketing/domain/ticket/service/NewRedisReservationService.java @@ -25,10 +25,9 @@ public class NewRedisReservationService implements ReservationService{ private final MemberRepository memberRepository; private final SeatRepository seatRepository; private final StringRedisTemplate redisTemplate; - private final String SEAT_CONST = "seat-selected-number:"; + private final int reservationReleaseDelay; - @Value("${ticketing.reservation.release-delay-seconds}") - private int reservationReleaseDelay; + private final static String SEAT_CONST = "seat-selected-number:"; @Override public void selectSeat(String memberEmail, SeatSelectionRequest seatSelectionRequest) { diff --git a/src/main/java/com/thirdparty/ticketing/domain/ticket/service/ReservationTransactionService.java b/src/main/java/com/thirdparty/ticketing/domain/ticket/service/ReservationTransactionService.java index 950d2aa4..3710b38f 100644 --- a/src/main/java/com/thirdparty/ticketing/domain/ticket/service/ReservationTransactionService.java +++ b/src/main/java/com/thirdparty/ticketing/domain/ticket/service/ReservationTransactionService.java @@ -35,12 +35,10 @@ public class ReservationTransactionService implements ReservationService { private final PaymentProcessor paymentProcessor; private final LockSeatStrategy lockSeatStrategy; private final EventPublisher eventPublisher; - private final ReservationManager reservationManager; - private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5); + private final int reservationReleaseDelay; - @Value("${ticketing.reservation.release-delay-seconds}") - private int reservationReleaseDelay; + private final ScheduledExecutorService scheduler; @Override @Transactional