Ticket Purchase Service Layer
In this lesson, we'll build the service layer functionality for purchasing tickets, implementing concurrent access handling and QR code generation to create a robust ticket purchasing system.
Understanding the Purchase Flow
The ticket purchase process involves several key steps:
- Finding the user and ticket type in the database
- Checking if tickets are still available
- Creating a new ticket record
- Generating a QR code for the ticket
Let's implement this in our TicketTypeServiceImpl class:
@Service
@RequiredArgsConstructor
public class TicketTypeServiceImpl implements TicketTypeService {
private final UserRepository userRepository;
private final TicketTypeRepository ticketTypeRepository;
private final TicketRepository ticketRepository;
private final QrCodeService qrCodeService;
@Override
@Transactional
public Ticket purchaseTicket(UUID userId, UUID ticketTypeId) {
// Look up the user
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException(
String.format("User with ID %s was not found", userId)
));
// Get ticket type with pessimistic lock
TicketType ticketType = ticketTypeRepository.findByIdWithLock(ticketTypeId)
.orElseThrow(() -> new TicketTypeNotFoundException(
String.format("Ticket type with ID %s was not found", ticketTypeId)
));
// Check ticket availability
int purchasedTickets = ticketRepository.countByTicketTypeId(ticketType.getId());
Integer totalAvailable = ticketType.getTotalAvailable();
if(purchasedTickets + 1 > totalAvailable) {
throw new TicketsSoldOutException();
}
// Create new ticket
Ticket ticket = new Ticket();
ticket.setStatus(TicketStatusEnum.PURCHASED);
ticket.setTicketType(ticketType);
ticket.setPurchaser(user);
// Save and generate QR code
Ticket savedTicket = ticketRepository.save(ticket);
qrCodeService.generateQrCode(savedTicket);
return ticketRepository.save(savedTicket);
}
}Handling Concurrent Access
To prevent overselling tickets when multiple users try to purchase at the same time, we use a pessimistic lock:
@Repository
public interface TicketTypeRepository extends JpaRepository<TicketType, UUID> {
@Query("SELECT tt FROM TicketType tt WHERE tt.id = :id")
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<TicketType> findByIdWithLock(@Param("id") UUID id);
}This ensures that when one user is purchasing a ticket, other users must wait until the transaction completes before they can access the same ticket type.
Error Handling
We handle several error cases:
- User not found
- Ticket type not found
- Tickets sold out
- QR code generation failure
These are caught and handled by our global exception handler to provide clear error messages to users.
Summary
- Implemented the initial purchase ticket functionality