๋ค์ด๊ฐ๋ฉฐ
๋๊ท๋ชจ ์์คํ ์์ ํธ๋์ญ์ ์ ๋ฒ์์ ์๋น์ค ๊ฐ์ ๊ฒฐํฉ๋๋ ์ฑ๋ฅ๊ณผ ํ์ฅ์ฑ์ ํฐ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. ํนํ, ๊ธฐ์กด ๋ก์ง์ ์ํฅ์ ์ฃผ์ง ์์ผ๋ฉด์ ๋ถ๊ฐ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ฑฐ๋, ํธ๋์ญ์ ๊ฒฝ๊ณ๋ฅผ ์ต์ ํํ๋ ๊ฒ์ ์๋น์ค์ ์ ์ง๋ณด์์ฑ์ ๋์ด๋ ์ค์ํ ์์ ์ ๋๋ค. ์ด๋ฒ ๊ธ์์๋ ํ์ฌ ์ฝ์ํธ ์์ฝ ์๋น์ค์ ํธ๋์ญ์ ๋ฒ์๋ฅผ ๋ถ์ํ๊ณ , ์๋น์ค๋ค์ ๊ธฐ๋ฅ๋ณ๋ก ๋ถ๋ฆฌํ๋ฉฐ ์ด๋ฒคํธ ๊ธฐ๋ฐ ์ค๊ณ๋ฅผ ๋์ ํ๋ ๊ณผ์ ์ ๋ํด ์ค๋ช ํ๊ฒ ์ต๋๋ค.
ํ์ฌ ์ฝ๋์ ํธ๋์ญ์ ๋ฒ์ ๋ถ์
ํธ๋์ญ์ ๋ฒ์
ํ์ฌ ์ฝ๋์์ @Transactional
์ด ์ ์ฉ๋ ํต์ฌ ๋ก์ง ์ค ์์ฝ ์์ฑ๊ณผ ๊ฒฐ์ ์ฒ๋ฆฌ์ ๋ํ ๋ก์ง์ ๋ํด ํธ๋์ญ์
๋ฒ์๋ฅผ ๋ถ์ํด๋ณด๊ฒ ์ต๋๋ค. ๋ ๋ก์ง์ ์๋๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๋์ํฉ๋๋ค.
์์ฝ ์์ฑ
@Service
class ReservationService(
private val seatFinder: SeatFinder,
private val concertManager: ConcertManager,
private val reservationRepository: ReservationRepository,
) {
@Transactional
fun createPendingReservation(
userId: Long,
scheduleId: Long,
seatId: Long,
): CreateReservationInfo {
seatFinder.getAvailableSeat(scheduleId, seatId).reserve()
occupySeat(scheduleId)
val reservation = concertManager.createPendingReservation(userId, scheduleId, seatId)
return reservation.toCreateReservationInfo(success = true)
}
}
๊ฒฐ์ ์ฒ๋ฆฌ
@Component
class PaymentFacade(
private val waitingQueueService: WaitingQueueService,
private val userService: UserService,
private val reservationService: ReservationService,
private val paymentService: PaymentService,
private val concertService: ConcertService,
) {
@Transactional
fun processPayment(command: PaymentCommand): PaymentInfo {
return runCatching {
val reservationInfo: ReservationInfo = reservationService.confirmReservation(command.reservationId)
val seatInfo: SeatInfo = concertService.verifyAndGetSeatInfo(reservationInfo.seatId)
userService.updateUserBalance(command.userId, seatInfo.price, PointTransactionType.USE)
waitingQueueService.expireToken(command.token)
paymentService.savePayment(command.userId, command.reservationId, seatInfo.price)
}.getOrElse { ex ->
throw CoreException(
errorType = ErrorType.PAYMENT_FAILED,
details = {
"cause" to ex.message
},
)
}
}
}
์ข ๋ ๋ณด๊ธฐ ์ฝ๊ฒ ์๋(pseudo) ์ฝ๋๋ก ๋ค์ ์์ฑํด๋ณด์์ต๋๋ค.
์์ฝ ์์ฑ
// ReservationService.createPendingReservation()
@Transactional
fun ์์ฝ์์ฑ(์ฌ์ฉ์ID, ์ผ์ ID, ์ข์ID) {
val ์ข์ = ์ฌ์ฉ๊ฐ๋ฅํ์ข์๊ฐ์ ธ์ค๊ธฐ(์ผ์ ID, ์ข์ID)
์ข์.์ข์์์ฝ()
์ข์์ ์ ์
๋ฐ์ดํธ(์ผ์ ID) // ์ฌ๊ณ ๊ฐ์
val ์์ฝ = ์์ฝ์์ฑ๋ฐ์ ์ฅ(์ฌ์ฉ์ID, ์ผ์ ID, ์ข์ID)
return ์์ฝ.์์ฝ์ ๋ณด๋ณํ() // ์์ฝ ์ ๋ณด ๋ฐํ
}
๊ฒฐ์ ์ฒ๋ฆฌ
// PaymentFacade.processPayment()
@Transactional
fun ๊ฒฐ์ ์ฒ๋ฆฌ(command): ๊ฒฐ์ ์ ๋ณด {
return runCatching {
val ์์ฝ์ ๋ณด = ์์ฝํ์ธ๋ฐํ์ ์ฒ๋ฆฌ(์์ฝID)
val ์ข์์ ๋ณด = ์ข์์ ๋ณด๊ฒ์ฆ๋ฐ๊ฐ์ ธ์ค๊ธฐ(์์ฝ์ ๋ณด.์ข์ID)
์ฌ์ฉ์์์ก์ฐจ๊ฐ(์ฌ์ฉ์ID, ์ข์์ ๋ณด.๊ฐ๊ฒฉ, USE)
๋๊ธฐ์ดํ ํฐ๋ง๋ฃ์ฒ๋ฆฌ(ํ ํฐ)
๊ฒฐ์ ์ ๋ณด์ ์ฅ(์ฌ์ฉ์ID, ์์ฝID, ์ข์์ ๋ณด.๊ฐ๊ฒฉ)
}.getOrElse { ์์ธ ->
throw ์ต์
์
()
}
}
- ์์ฝ ์์ฑ, ๊ฒฐ์ ์ฒ๋ฆฌ์ ๋ฉ์๋๋ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์ฌ ์์ด, ๋ชจ๋ ์์ ์ด ์์์ ์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค.
- ๊ฒฐ์ ์ฒ๋ฆฌ๋ ์ ํ๋ฆฌ์ผ์ด์ ๊ณ์ธต์์ ํ์ฌ๋๋ก ์์ฑ๋์ด ์ฌ๋ฌ ๋๋ฉ์ธ ์๋น์ค ํธ์ถํ๋ฉด์ ๊ฒฐํฉ๋๊ฐ ๋์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค.
ํธ๋์ญ์ ์ฒ๋ฆฌ์ ๋ฌธ์ ์
1. ํธ๋์ญ์ ๊ฒฝ๊ณ๊ฐ ๋์
๊ฒฐ์ ์ฒ๋ฆฌ์ ๊ฐ์ด ์ ํ๋ฆฌ์ผ์ด์ ๊ณ์ธต์์ ํธ๋์ญ์ ์ ์์ํ๋ ๊ฒฝ์ฐ, ๋ชจ๋ ์๋น์ค ํธ์ถ์ด ํ๋์ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์ฌ ํน์ ์์ ์คํจ ์ ์ ์ฒด ๋กค๋ฐฑ์ด ๋ฐ์ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์์ฝ ํ์ ๊ณผ ์์ก ์ฐจ๊ฐ์ด ์ฑ๊ณตํ์ผ๋ ๊ฒฐ์ ์ ๋ณด ์ ์ฅ์ด ์คํจํ์ ๊ฒฝ์ฐ ํต์ฌ ๋ก์ง๊น์ง ์คํจํ๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค.
2. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์ ์ ์ ์๊ฐ ์ฆ๊ฐ
ํธ๋์ญ์ ์ด ๊ธธ์ด์ง๋ฉด DB ์ปค๋ฅ์ ์ ์ค๋ ์ ์ ํ๊ฒ ๋์ด ํ์ ๋ ์ฐ๋ ๋ ํ์ ์ฌ์ฉํ๊ณ ์๋ ๊ฒฝ์ฐ ๋ค๋ฅธ ์์ ์ ์ํฅ์ ๋ผ์น ์ ์์ต๋๋ค.
3. ๋ฐ๋๋ฝ ๊ฐ๋ฅ์ฑ
ํธ๋์ญ์ ์ด ๊ธธ์ด์ง์๋ก ๋ค๋ฅธ ํธ๋์ญ์ ๊ณผ ์ถฉ๋ํ ๊ฐ๋ฅ์ฑ์ด ๋์์ง๋๋ค.
4. ํ์ฅ์ฑ ํ๊ณ
์๋น์ค ๋ก์ง์ด ๋ณต์กํด์ง์๋ก ๋จ์ผ ํธ๋์ญ์ ๋ด์์ ์ํํ๋ ์์ ์ด ๋ง์์ง๋ฉฐ, ์ด๋ ๋ง์ดํฌ๋ก์๋น์ค๋ก ํ์ฅํ ๋ ๋ฌธ์ ๊ฐ ๋ฉ๋๋ค.
์๋น์ค ๋ถ๋ฆฌ ๋ฐ ๊ฐ์ ๋ฐฉ์
1. ์๋น์ค ๊ธฐ๋ฅ๋ณ ๋ถ๋ฆฌ
ํ์ฌ ๊ฒฐ์ ์ฒ๋ฆฌ ์ฝ๋๋ ์์ฝ, ๊ฒฐ์ , ์ฌ์ฉ์ ์๋น์ค๊ฐ ๋จ์ผ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์ฌ ์์ต๋๋ค. ์ด๋ฅผ ๊ธฐ๋ฅ๋ณ ์๋น์ค๋ก ๋ถ๋ฆฌํ์ฌ ๊ฐ๋ณ ํธ๋์ญ์ ๋ฒ์๋ฅผ ์ขํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์์ฝ ํ์ , ์์ก ์ฐจ๊ฐ, ํ ํฐ ๋ง๋ฃ, ๊ฒฐ์ ์ ๋ณด ์ ์ฅ์ ๊ฐ๊ฐ์ ์๋น์ค๋ก ๋ถ๋ฆฌํ์ฌ ๊ฐ๊ธฐ ๋ค๋ฅธ ํธ๋์ญ์ ์ ๊ฐ์ง๋๋ก ๋ง๋ค ์ ์์ต๋๋ค.
2. ํธ๋์ญ์ ๊ฒฝ๊ณ ์กฐ์
์ค์ํ ํต์ฌ ์์ ์ ๋ํด์๋ง ํธ๋์ญ์ ์ ์ ์งํ๊ณ , ๋ถ๊ฐ์ ์ธ ์์ ์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๊ฒฐ์ ์ฒ๋ฆฌ์์ ์์ฝํ์ , ์์ก์ฐจ๊ฐ ๊ฐ์ ์ฃผ์ ๋ก์ง์ ํธ๋์ญ์ ์ ์ ์งํ๊ณ , ํ ํฐ๋ง๋ฃ๋ ๊ฒฐ์ ์ ๋ณด์ ์ฅ์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
์ธ๋ถ API ํธ์ถ์ด๋ ๋ฉ์์ง ๋ฐํ์ ๊ฒฝ์ฐ์๋ ํธ๋์ญ์ ์ธ๋ถ์์ ์ํํ๋๋ก ์ถ๊ฐํ์ฌ ์ฃผ์ ๋ก์ง์ ์ํฅ์ ์ฃผ์ง ์๋๋ก ํด์ผ ํฉ๋๋ค.
3. ์ด๋ฒคํธ ๊ธฐ๋ฐ ์ค๊ณ ๋์
์๋น์ค ๊ฐ ๊ฒฐํฉ๋๋ฅผ ๋ฎ์ถ๊ธฐ ์ํด ์ด๋ฒคํธ ๊ธฐ๋ฐ ์ํคํ ์ฒ๋ฅผ ๋์ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์์ฝ ์์ฑ์์ ์์ฝ์ด ์๋ฃ๋ ํ ์ด๋ฒคํธ๋ฅผ ๋ฐํํ๊ณ ์ธ๋ถ API๊ฐ ์ด๋ฅผ ๊ตฌ๋ ํ๋๋ก ํ๋ฉด, ํด๋น ๋ก์ง์ ๋ ๋ฆฝ์ ์ผ๋ก ์คํ๋ฉ๋๋ค.
์ด๋ฅผ ํตํด ํ ํธ๋์ญ์ ๋ด์์ ๋ชจ๋ ์์ ์ ์ํํ ํ์๊ฐ ์์ด์ง๊ณ , ํ์ฅ์ฑ๊ณผ ์ ์ฐ์ฑ์ด ๋์์ง๋๋ค.
4. ๋ถ์ฐ ํธ๋์ญ์ ๋ฐ ์ฌ๊ฐ ํจํด ๋์
๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ๋ก ํ์ฅํ ๊ฒฝ์ฐ, ๋จ์ผ ํธ๋์ญ์ ์ด ์๋ ๋ถ์ฐ ํธ๋์ญ์ ์ ๊ด๋ฆฌํด์ผ ํ ์ ์์ต๋๋ค. ์ด๋ ์ฌ๊ฐ ํจํด์ ๋์ ํ์ฌ ํธ๋์ญ์ ์ ๋จ๊ณ๋ณ๋ก ๋๋๊ณ , ๊ฐ ๋จ๊ณ๊ฐ ์คํจํ ๊ฒฝ์ฐ ๋ณด์ ์์ ์ ์ํํ๋๋ก ์ค๊ณํ ์๋ ์์ต๋๋ค.
์์ฝ ์์ฑ ๋ก์ง์ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ถ๊ฐํ๊ธฐ
์์ ๊ฐ์ ๋ฐฉ์์ ํ ๋๋ก ์์ฝ ์์ฑ ๋ก์ง์ ์ด๋ฒคํธ ๋ฐํ ๋ฐ ๊ตฌ๋ ์ฒ๋ฆฌ ๋ก์ง์ ์ถ๊ฐํ์ฌ ์ธ๋ถ API๋ฅผ ํธ์ถํ๋ ์๊ตฌ์ฌํญ์ ์ ์ฉํด๋ณด๊ฒ ์ต๋๋ค.
1. ๊ธฐ์กด ์์ฝ ์๋น์ค์ ์์ฝ ์์ฑ ๋ฉ์๋
์์์ ๋ดค๋ ์์ฝ ์์ฑ์ ์ฝ๋๋ฅผ ๋ค์ ๋ณด๊ฒ ์ต๋๋ค.
@Transactional
fun createPendingReservation(
userId: Long,
scheduleId: Long,
seatId: Long,
): CreateReservationInfo {
seatFinder.getAvailableSeat(scheduleId, seatId).reserve()
occupySeat(scheduleId)
val reservation = concertManager.createPendingReservation(userId, scheduleId, seatId)
return reservation.toCreateReservationInfo(success = true)
}
2. ์์ฝ ์ด๋ฒคํธ POJO ํด๋์ค ์์ฑ
์์ฝ ์์ฑ ํ ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํ๊ธฐ ์ํ Event ํด๋์ค๋ฅผ ์ ์ํ์ต๋๋ค.
์ฐธ๊ณ ๋ก, ApplicationEventPublisher
๋ ์คํ๋ง 4.2๋ถํฐ ApplicationEvent
ํด๋์ค ์์ ์์ด POJO ๊ฐ์ฒด์ ์ด๋ฒคํธ๋ฅผ ๋ฐํํ ์ ์์ต๋๋ค. ์ข ๋ ์์ธํ ๋ด์ฉ์ ์๋ ๋ธ๋ก๊ทธ ์ํฐํด์์ ํ์ธํ ์ ์์ต๋๋ค.
package hhplus.concertreservation.domain.concert.event
data class ReservationCreatedEvent(
val userId: Long,
val scheduleId: Long,
val seatId: Long,
val reservationId: Long,
) {
companion object {
fun from(reservation: Reservation): ReservationCreatedEvent {
return ReservationCreatedEvent(
userId = reservation.userId,
scheduleId = reservation.scheduleId,
seatId = reservation.seatId,
reservationId = reservation.id,
)
}
}
}
3. ์์ฝ ์๋ฃ ํ ์ด๋ฒคํธ ๋ฐํ ์ถ๊ฐ
์์ฝ์ด ์๋ฃ๋๋ฉด ์์ฝ ์ด๋ฒคํธ๋ฅผ ๋ฐํํ๋ ๋ก์ง์ ์ถ๊ฐํฉ๋๋ค.
@Transactional
fun createPendingReservation(
userId: Long,
scheduleId: Long,
seatId: Long,
): CreateReservationInfo {
seatFinder.getAvailableSeat(scheduleId, seatId).reserve()
occupySeat(scheduleId)
val reservation = concertManager.createPendingReservation(userId, scheduleId, seatId)
eventPublisher.publish(ReservationCreatedEvent.from(reservation)) // ์ด๋ฒคํธ ๋ฐํ
return reservation.toCreateReservationInfo(success = true)
}
4. ์ธ๋ถ ๋ฐ์ดํฐ ํ๋ซํผ ํธ์ถ ํด๋์ค ์์ฑ
๊ฐ๋จํ๊ฒ ์์ฝ ์ด๋ฒคํธ๋ฅผ ๋ฐ๊ฒ ๋๋ฉด ๋ก์ง์ ์ํํ๋ ํด๋์ค๋ฅผ ์์ฑํ์ต๋๋ค.
package hhplus.concertreservation.infrastructure.api
@Component
class DataPlatformApiClient {
private val log = LoggerFactory.getLogger(this::class.java)
fun sendReservation() {
log.info("์ธ๋ถ API ํธ์ถ: ์์ฝ ์ ์ก ์ด๋ฒคํธ ์ฒ๋ฆฌ ์๋ฃ")
}
}
5. ์ด๋ฒคํธ Publisher์ Listener ์ธํฐํ์ด์ค ์์ฑ
EventPublisher
์ EventListener
์ ์ธํฐํ์ด์ค๋ฅผ ์์ฑํ์ต๋๋ค. ์ฐจ์ฃผ ๊ณผ์ ์์ Spring์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ Kafka๋ก ์ ํํ๊ธฐ ๋๋ฌธ์ ๊ตฌํ์ฒด๋ฅผ ๋ณ๊ฒฝํ๊ธฐ ์ข์ ๊ตฌ์กฐ๋ก ์ค๊ณํ๊ธฐ ์ํด ์์ฑํ์์ต๋๋ค.
package hhplus.concertreservation.infrastructure.event
interface EventPublisher<T> {
fun publish(event: T)
}
package hhplus.concertreservation.infrastructure.event
interface EventHandler<T> {
fun handle(event: T)
}
6. ์คํ๋ง ์ด๋ฒคํธ ํธ๋ค๋ฌ ๊ตฌํ์ฒด ์์ฑ
์คํ๋ง ์ปจํ
์คํธ์์ ์ ๊ณตํ๋ ApplicationEventPublisher
์ @TransactionalEventListener
๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํ์ฒด๋ฅผ ์์ฑํ์ต๋๋ค.
package hhplus.concertreservation.infrastructure.event.reservation
@Component
class ReservationSpringEventPublisher(
private val applicationEventPublisher: ApplicationEventPublisher
) : EventPublisher<ReservationCreatedEvent> {
override fun publish(event: ReservationCreatedEvent) {
applicationEventPublisher.publishEvent(event)
}
}
package hhplus.concertreservation.infrastructure.event.listener
@Component
class ExternalPlatformEventListener(
private val dataPlatformApiClient: DataPlatformApiClient
): EventListener<ReservationCreatedEvent> {
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
override fun handle(event: ReservationCreatedEvent) {
dataPlatformApiClient.sendReservation()
}
}
์ฐธ๊ณ ) Listener์ @Async์ @Transactional(REQUIRES_NEW), @TransactionalEventListener(AFTER_COMMIT)์ ์ฌ์ฉํ ์ด์
์คํ๋ง 4.2 ๋ฒ์ ๋ถํฐ @EventListener
์ ๋
ธํ
์ด์
์ ์ฌ์ฉํ์ฌ ์ฝ๊ฒ ์ด๋ฒคํธ ๊ตฌ๋
์ฒ๋ฆฌ๋ฅผ ํ ์ ์์ต๋๋ค.
package hhplus.concertreservation.infrastructure.event.listener
@Component
class ExternalPlatformEventListener(
private val dataPlatformApiClient: DataPlatformApiClient
): EventListener<ReservationCreatedEvent> {
@EventListener
override fun handle(event: ReservationCreatedEvent) {
dataPlatformApiClient.sendReservation()
}
}
ํ์ง๋ง, ์ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
- ๋ฉ์ธ ๋ก์ง๊ณผ ๋์ผํ ํธ๋์ญ์ ์ฌ์ฉ: ๋ฆฌ์ค๋ ๋ก์ง์์ ์์ธ๊ฐ ๋ฐ์ํ๋ฉด ๋ฉ์ธ ๋ก์ง๋ ๋กค๋ฐฑ๋ฉ๋๋ค.
- ๋๊ธฐ ์ฒ๋ฆฌ: ๋ฉ์ธ ๋ก์ง๊ณผ ๋๊ธฐ์ ์ผ๋ก ์ํ๋๊ธฐ ๋๋ฌธ์ ๋ฆฌ์ค๋ ๋ก์ง์ ์ํ ์๊ฐ์ด ๊ธธ์ด์ง๋ฉด ๋ฉ์ธ ๋ก์ง์ ์๋ต์ด ์ง์ฐ๋ ์ ์์ต๋๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด @Async
์ @Transactional(propagation = Propagation.REQUIRES_NEW)
๋ฅผ ์ถ๊ฐํ์ฌ ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ๋
๋ฆฝ์ ์ธ ํธ๋์ญ์
์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
package hhplus.concertreservation.infrastructure.event.listener
@Component
class ExternalPlatformEventListener(
private val dataPlatformApiClient: DataPlatformApiClient
): EventListener<ReservationCreatedEvent> {
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
@EventListener
override fun handle(event: ReservationCreatedEvent) {
dataPlatformApiClient.sendReservation()
}
}
์ด๋ ๊ฒ ๋๊ฐ์ ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐ๋ก ์ ์ธํ๊ฒ ๋๋ฉด ์์ ๋ฌธ์ ์ ์ ํด๊ฒฐํ ์ ์์ต๋๋ค. ํ์ง๋ง ์ฌ์ ํ ๋ค๋ฅธ ๋ฌธ์ ๊ฐ ๋จ์์์ต๋๋ค.
๋ฉ์ธ ๋ก์ง์์ ์ด๋ฒคํธ ๋ฐํ ํ ์์ธ๊ฐ ๋ฐ์ํ๊ฒ ๋๋ฉด ๋ฉ์ธ ๋ก์ง์ ์คํจํ์ง๋ง ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ ์ฑ๊ณตํด๋ฒ๋ฆฌ๋ ๊ฒฝ์ฐ๊ฐ ์๊ธธ ์ ์์ต๋๋ค. ์๋๋ ๊ทธ ์์ ์ฝ๋์ ๋๋ค.
@Transactional
fun createPendingReservation(
userId: Long,
scheduleId: Long,
seatId: Long,
): CreateReservationInfo {
seatFinder.getAvailableSeat(scheduleId, seatId).reserve()
occupySeat(scheduleId)
val reservation = concertManager.createPendingReservation(userId, scheduleId, seatId)
eventPublisher.publish(ReservationCreatedEvent.from(reservation)) // ์ด๋ฒคํธ ๋ฐํ
throw CoreException(ErrorType.SYSTEM_FAILURE, "์ด๋ฒคํธ ๋ฐํ ํ ์์ธ ๋ฐ์") // ์์ธ ๋ฐ์
return reservation.toCreateReservationInfo(success = true)
}
@Component
class ExternalPlatformEventListener : EventListener<ReservationCreatedEvent> {
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
@EventListener
override fun handle(event: ReservationCreatedEvent) {
println("์์ฝ ์ธ๋ถ API ํธ์ถ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ค: $event")
Thread.sleep(3_000)
println("์์ฝ ์ธ๋ถ API ํธ์ถ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์๋ฃ.")
}
}
์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ๋ฉ์ธ ๋ก์ง์ ํธ๋์ญ์
์ด ์ฑ๊ณต์ ์ผ๋ก ์ปค๋ฐ๋์์ ๋, ์ด๋ฒคํธ ์ฒ๋ฆฌ ๋ก์ง์ ์ํํ๋๋ก ์ค์ ํด์ผ ํฉ๋๋ค. ์ด๋ฅผ ์ํด @EventListener
์ ํ์ฅ๋ ๊ฐ๋
์ธ @TransactionalEventListener
๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
@TransactionalEventListener
๋ TransactionPhase๋ฅผ ํตํด ์ด๋ฒคํธ๊ฐ ์คํ๋ ์์ ์ ์ง์ ํ ์ ์์ต๋๋ค.
- AFTER_COMMIT: ํธ๋์ญ์ ์ด ์ฑ๊ณต์ ์ผ๋ก ์ปค๋ฐ๋ ํ ์ด๋ฒคํธ๊ฐ ์ฒ๋ฆฌ๋จ.
- BEFORE_COMMIT: ํธ๋์ญ์ ์ปค๋ฐ ์ง์ ์ ์ด๋ฒคํธ๊ฐ ์ฒ๋ฆฌ๋จ.
- AFTER_ROLLBACK: ํธ๋์ญ์ ๋กค๋ฐฑ ํ ์ด๋ฒคํธ๊ฐ ์ฒ๋ฆฌ๋จ.
- AFTER_COMPLETION: ํธ๋์ญ์ ์ด ์๋ฃ๋ ํ(์ปค๋ฐ๋์๋์ง ๋กค๋ฐฑ๋์๋์ง์ ์๊ด์์ด) ์ด๋ฒคํธ๊ฐ ์ฒ๋ฆฌ๋จ.
๋ฐ๋ผ์, ์ต์ข ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ด 3๊ฐ์ ์ ๋ ธํ ์ด์ ์ ์ถ๊ฐํ์ฌ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ๊ตฌํํ ์ ์์์ต๋๋ค.
package hhplus.concertreservation.infrastructure.event.listener
@Component
class ExternalPlatformEventListener(
private val dataPlatformApiClient: DataPlatformApiClient
): EventListener<ReservationCreatedEvent> {
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
override fun handle(event: ReservationCreatedEvent) {
dataPlatformApiClient.sendReservation()
}
}
๋ง์น๋ฉฐ
์ด๋ฒ ๊ธ์ ํตํด ํธ๋์ญ์ ๊ฒฝ๊ณ๋ฅผ ๋ถ์ํ๊ณ , ์๋น์ค ํ์ฅ์ ์ํด ์๋น์ค ๋ถ๋ฆฌ์ ์ด๋ฒคํธ ๊ธฐ๋ฐ ์ค๊ณ๋ฅผ ๋์ ํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ดํด๋ณด์์ต๋๋ค. ํธ๋์ญ์ ๊ฒฝ๊ณ๋ฅผ ์ ์ ํ ์กฐ์ ํ๊ณ ๋น๋๊ธฐ ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ฅผ ๋์ ํจ์ผ๋ก์จ ์๋น์ค์ ์ฑ๋ฅ๊ณผ ํ์ฅ์ฑ์ ๊ฐ์ ํ ์ ์์์ต๋๋ค. ๋ค์ ์ฃผ์๋ Kafka๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฒคํธ ์ฒ๋ฆฌ ๋ฐ ๋ณด์ ํธ๋์ญ์ ์ ๋์ ํ์ฌ ์ค์ํ ๋ก์ง์์์ ์ค๋ฅ์ ๋์ํ ์ ์๋ ๋ฐฉ๋ฒ๋ ์์๋ณด๊ฒ ์ต๋๋ค.
'๐ป ๊ฐ๋ฐ > ๐ Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Redis ๊ธฐ๋ฐ์ ์บ์ฑ ๋ฐ ๋๊ธฐ์ด ๊ด๋ฆฌ๋ฅผ ํตํ ์ฝ์ํธ ์์ฝ ์๋น์ค ์ฑ๋ฅ ๊ฐ์ (2) | 2024.11.07 |
---|---|
๋์์ฑ ์ฒ๋ฆฌ ์ฝ๊ฒ ์ดํดํ๊ธฐ (synchronized, reentrantLock) (0) | 2024.09.29 |
Spring Security ๋ด๋ถ ํ๋ฆ ์ดํดํ๊ธฐ (0) | 2024.02.13 |
์คํ๋ง ๋ถํธ์ OpenAI Whisper API ์ ์ฉํ๊ธฐ (1) | 2023.08.24 |