-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Refactor: 포인트 컨슈머 스레드 명칭을 좀 더 간결하게 변경 * Refactor: 포인트 캐시 업데이트 함수도 결국엔 도메인 비즈니스 로직으로 해당 계층에 맞게 네이밍 변경 * Refactor: 비동기 스레드 리소스 문제로 인한 오류 발생 해결을 위해 Async 코드 삭제 및 예약 요청시 응답시간을 늘리기위해 좌석 상태 변경을 cache write-through 패턴으로 변경 * Refactor: 응답시간 개선을 위해 예약을 위해 콘서트 조회 부분에 cache-aside 패턴 적용 * Feat: DB 작업 병목으로 인한 api 응답속도 개선을 위해 예약 저장 및 좌석 상태 변경 관련 DB 작업은 EventBroker에게 위임 * Test: 변경된 예약 요청에 맞게 테스트코드 수정 * Refactor: 아키텍처 설계의도에 맞게 service 인터페이스 함수 리팩토링 이후 사용하지 않는 함수 삭제 * Refactor: 포인트 결제와 pg 결제 승인(mock) 분리이후 사용하지 않는 함수 삭제 * Refactor: 예약 도메인 리팩토링 과정에서 사용하지 않게된 dto 클래스 삭제 * Test: 리팩토링 이후 테스트코드 정리
- Loading branch information
Showing
23 changed files
with
244 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 0 additions & 44 deletions
44
src/main/kotlin/io/ticketaka/api/concert/application/ConcertSeatService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
src/main/kotlin/io/ticketaka/api/concert/domain/ConcertSeatUpdater.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package io.ticketaka.api.concert.domain | ||
|
||
import java.time.LocalDate | ||
|
||
interface ConcertSeatUpdater { | ||
fun reserve( | ||
concertId: Long, | ||
date: LocalDate, | ||
seatNumbers: List<String>, | ||
): Set<Seat> | ||
|
||
fun confirm( | ||
concertId: Long, | ||
date: String, | ||
seatNumbers: List<String>, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
src/main/kotlin/io/ticketaka/api/concert/infrastructure/InMemoryCacheConcertSeatUpdater.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package io.ticketaka.api.concert.infrastructure | ||
|
||
import io.ticketaka.api.common.exception.NotFoundException | ||
import io.ticketaka.api.concert.domain.ConcertSeatUpdater | ||
import io.ticketaka.api.concert.domain.Seat | ||
import io.ticketaka.api.concert.domain.SeatRepository | ||
import org.springframework.cache.caffeine.CaffeineCacheManager | ||
import org.springframework.stereotype.Component | ||
import java.time.LocalDate | ||
|
||
@Component | ||
class InMemoryCacheConcertSeatUpdater( | ||
private val caffeineCacheManager: CaffeineCacheManager, | ||
private val seatRepository: SeatRepository, | ||
) : ConcertSeatUpdater { | ||
override fun reserve( | ||
concertId: Long, | ||
date: LocalDate, | ||
seatNumbers: List<String>, | ||
): Set<Seat> { | ||
val cache = caffeineCacheManager.getCache("seatNumbers") ?: throw NotFoundException("콘서트별 좌석 캐시가 존재하지 않습니다.") | ||
synchronized(cache) { | ||
var seats = cache.get(concertId) { setOf<Seat>() } as Set<Seat> | ||
if (seats.isEmpty()) { | ||
seats = seatRepository.findByConcertId(concertId) | ||
} | ||
seats.sortedBy { it.number } | ||
.filter { seatNumbers.contains(it.number) } | ||
.forEach { it.reserve() } | ||
cache.put(concertId, seats) | ||
return seats.filter { seatNumbers.contains(it.number) }.toSet() | ||
} | ||
} | ||
|
||
override fun confirm( | ||
concertId: Long, | ||
date: String, | ||
seatNumbers: List<String>, | ||
) { | ||
println("Confirm concert seat") | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/kotlin/io/ticketaka/api/concert/infrastructure/event/DBSeatStatusManger.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package io.ticketaka.api.concert.infrastructure.event | ||
|
||
import io.ticketaka.api.concert.domain.SeatRepository | ||
import org.springframework.stereotype.Component | ||
import org.springframework.transaction.annotation.Transactional | ||
|
||
@Component | ||
class DBSeatStatusManger( | ||
private val seatRepository: SeatRepository, | ||
) { | ||
@Transactional | ||
fun reserve(seatIds: List<Long>) { | ||
val seats = seatRepository.findByIdsOrderByNumberForUpdate(seatIds) | ||
seats.forEach { it.reserve() } | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
...in/kotlin/io/ticketaka/api/concert/infrastructure/event/ReservationCreateEventConsumer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package io.ticketaka.api.concert.infrastructure.event | ||
|
||
import io.ticketaka.api.reservation.domain.reservation.Reservation | ||
import io.ticketaka.api.reservation.domain.reservation.ReservationCreateEvent | ||
import io.ticketaka.api.reservation.domain.reservation.ReservationRepository | ||
import io.ticketaka.api.reservation.domain.reservation.ReservationSeatAllocator | ||
import org.slf4j.LoggerFactory | ||
import org.springframework.stereotype.Component | ||
import org.springframework.util.StopWatch | ||
import java.util.concurrent.LinkedBlockingDeque | ||
import kotlin.concurrent.thread | ||
|
||
@Component | ||
class ReservationCreateEventConsumer( | ||
private val reservationRepository: ReservationRepository, | ||
private val reservationSeatAllocator: ReservationSeatAllocator, | ||
private val seatStatusManger: DBSeatStatusManger, | ||
) { | ||
private val logger = LoggerFactory.getLogger(javaClass) | ||
private val eventQueue = LinkedBlockingDeque<ReservationCreateEvent>() | ||
|
||
init { | ||
startEventConsumer() | ||
} | ||
|
||
fun consume(events: MutableList<ReservationCreateEvent>) { | ||
events.forEach { event -> | ||
seatStatusManger.reserve(event.seatIds) | ||
val reservation = | ||
reservationRepository.save( | ||
Reservation.createPendingReservation( | ||
userId = event.userId, | ||
concertId = event.concertId, | ||
), | ||
) | ||
reservationSeatAllocator.allocate( | ||
reservationId = reservation.id, | ||
seatIds = event.seatIds, | ||
) | ||
} | ||
} | ||
|
||
fun offer(event: ReservationCreateEvent) { | ||
eventQueue.add(event) | ||
} | ||
|
||
private fun startEventConsumer() { | ||
thread( | ||
start = true, | ||
isDaemon = true, | ||
name = "reservationEventConsumer", | ||
) { | ||
while (true) { | ||
val stopWatch = StopWatch() | ||
stopWatch.start() | ||
var processingTime = 1000L | ||
val currentThread = Thread.currentThread() | ||
while (currentThread.state.name == Thread.State.WAITING.name) { | ||
logger.info(currentThread.state.name) | ||
Thread.sleep(processingTime) | ||
} | ||
if (eventQueue.isNotEmpty()) { | ||
val events = mutableListOf<ReservationCreateEvent>() | ||
var quantity = 1000 | ||
while (eventQueue.isNotEmpty().and(quantity > 0)) { | ||
quantity-- | ||
eventQueue.poll()?.let { events.add(it) } | ||
} | ||
consume(events) | ||
stopWatch.stop() | ||
processingTime = stopWatch.totalTimeMillis | ||
logger.debug("reservationEventConsumer consume ${events.size} events, cost ${processingTime}ms") | ||
} else { | ||
Thread.sleep(5000) | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.