Skip to content

Commit

Permalink
feat: 티켓 재고 레디스 큐 사용하여 관리하도록 변경 및 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
seokjin8678 committed Jun 11, 2024
1 parent 0fcce86 commit 0bbe180
Show file tree
Hide file tree
Showing 23 changed files with 318 additions and 418 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.festago.ticket.domain.NewTicket;
import com.festago.ticket.dto.event.TicketCreatedEvent;
import com.festago.ticket.dto.event.TicketDeletedEvent;
import com.festago.ticketing.application.command.TicketQuantityUpdateService;
import com.festago.ticketing.application.command.TicketSequenceUpdateService;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
Expand All @@ -12,21 +12,21 @@

@Component
@RequiredArgsConstructor
public class TicketQuantityEventListener {
public class TicketSequenceEventListener {

private final TicketQuantityUpdateService ticketQuantityUpdateService;
private final TicketSequenceUpdateService ticketSequenceUpdateService;

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void ticketCreatedEventHandler(TicketCreatedEvent event) {
NewTicket ticket = event.ticket();
ticketQuantityUpdateService.putOrDeleteTicketQuantity(ticket);
ticketSequenceUpdateService.putOrDeleteTicketSequence(ticket);
}

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void ticketDeletedEventHandler(TicketDeletedEvent event) {
NewTicket ticket = event.ticket();
ticketQuantityUpdateService.putOrDeleteTicketQuantity(ticket);
ticketSequenceUpdateService.putOrDeleteTicketSequence(ticket);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,49 @@
import com.festago.common.exception.NotFoundException;
import com.festago.common.exception.TooManyRequestException;
import com.festago.ticketing.domain.Booker;
import com.festago.ticketing.domain.TicketQuantity;
import com.festago.ticketing.domain.TicketSequence;
import com.festago.ticketing.domain.TicketingRateLimiter;
import com.festago.ticketing.dto.TicketingResult;
import com.festago.ticketing.dto.command.TicketingCommand;
import com.festago.ticketing.repository.TicketQuantityRepository;
import com.festago.ticketing.repository.TicketSequenceRepository;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
//@Transactional 명시적으로 Transactional 사용하지 않음
public class QuantityTicketingService {
public class SequenceTicketingService {

private final TicketQuantityRepository ticketQuantityRepository;
private final TicketingCommandService ticketingCommandService;
private final TicketSequenceRepository ticketSequenceRepository;
private final TicketingRateLimiter ticketingRateLimiter;

public TicketingResult ticketing(TicketingCommand command) {
TicketQuantity ticketQuantity = getTicketQuantity(command.ticketId());
Long ticketId = command.ticketId();
TicketSequence ticketSequence = getTicketSequence(ticketId);
validateFrequentTicketing(command.booker());
int sequence = ticketSequence.reserve();
try {
ticketQuantity.decreaseQuantity();
return ticketingCommandService.reserveTicket(command);
return ticketingCommandService.ticketing(command, sequence);
} catch (Exception e) {
ticketQuantity.increaseQuantity();
ticketSequence.cancel(sequence);
throw e;
}
}

private TicketQuantity getTicketQuantity(Long ticketId) {
TicketQuantity ticketQuantity = ticketQuantityRepository.findByTicketId(ticketId)
.orElseThrow(() -> new NotFoundException(ErrorCode.TICKET_NOT_FOUND));
if (ticketQuantity.isSoldOut()) {
throw new BadRequestException(ErrorCode.TICKET_SOLD_OUT);
}
return ticketQuantity;
}

private void validateFrequentTicketing(Booker booker) {
if (ticketingRateLimiter.isFrequentTicketing(booker, 5, TimeUnit.SECONDS)) {
throw new TooManyRequestException();
}
}

private TicketSequence getTicketSequence(Long ticketId) {
TicketSequence ticketSequence = ticketSequenceRepository.findByTicketId(ticketId)
.orElseThrow(() -> new NotFoundException(ErrorCode.TICKET_NOT_FOUND));
if (ticketSequence.isSoldOut()) {
throw new BadRequestException(ErrorCode.TICKET_SOLD_OUT);
}
return ticketSequence;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.festago.ticketing.application.command;

import com.festago.ticket.domain.NewTicket;
import com.festago.ticketing.repository.TicketSequenceRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class TicketSequenceUpdateService {

private final TicketSequenceRepository ticketSequenceRepository;

public void putOrDeleteTicketSequence(NewTicket ticket) {
if (ticket.isEmptyAmount()) {
ticketSequenceRepository.delete(ticket);
} else {
ticketSequenceRepository.put(ticket);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import com.festago.ticket.repository.NewTicketDao;
import com.festago.ticketing.domain.Booker;
import com.festago.ticketing.domain.ReserveTicket;
import com.festago.ticketing.domain.TicketingSequenceGenerator;
import com.festago.ticketing.domain.validator.TicketingValidator;
import com.festago.ticketing.dto.TicketingResult;
import com.festago.ticketing.dto.command.TicketingCommand;
Expand All @@ -26,21 +25,19 @@ public class TicketingCommandService {

private final NewTicketDao ticketDao;
private final ReserveTicketRepository reserveTicketRepository;
private final TicketingSequenceGenerator sequenceGenerator;
private final List<TicketingValidator> validators;
private final Clock clock;

public TicketingResult reserveTicket(TicketingCommand command) {
public TicketingResult ticketing(TicketingCommand command, int sequence) {
Long ticketId = command.ticketId();
NewTicketType ticketType = command.ticketType();
NewTicket ticket = ticketDao.findByIdWithTicketTypeAndFetch(ticketId, ticketType);
Booker booker = command.booker();
ticket.validateReserve(booker, LocalDateTime.now(clock));
validators.forEach(validator -> validator.validate(ticket, booker));
validate(ticket, booker);
int sequence = sequenceGenerator.generate(ticketId);
ReserveTicket reserveTicket = ticket.reserve(booker, sequence);
reserveTicketRepository.save(ticket.reserve(booker, sequence));
reserveTicketRepository.save(reserveTicket);
return new TicketingResult(reserveTicket.getTicketId());
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.festago.ticketing.domain;

import com.festago.common.exception.BadRequestException;

/**
* 티켓의 재고와 순서를 관리하는 도메인 <br/> 해당 도메인을 구현하는 구현체는 반드시 원자적인 연산을 사용해야 한다. <br/>
*/
public interface TicketSequence {

/**
* 티켓의 매진 여부를 반환한다.
*
* @return 티켓이 매진이면 true, 매진이 아니면 false
*/
boolean isSoldOut();

/**
* 티켓의 재고를 하나 감소시키고 순서를 반환한다. <br/> 해당 메서드의 연산은 atomic 해야 한다. <br/> 티켓의 재고가 비어있을 때 해당 메서드를 호출하면 BadRequestException을
* 던져야 한다. <br/>
*
* @throws BadRequestException 티켓의 재고가 비어있으면
*/
int reserve() throws BadRequestException;

/**
* 티켓의 재고를 하나 증가시키고 인자로 들어온 순서를 다시 보관한다. <br/> 해당 메서드의 연산은 atomic 해야 한다. <br/>
*
* @param sequence 티켓의 순서
*/
void cancel(int sequence);

/**
* 티켓의 남은 재고를 반환한다. <br/>
*
* @return 티켓의 남은 재고
*/
int getQuantity();
}

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit 0bbe180

Please sign in to comment.