공연 예매하기 기능 구현을 위해 nest.js에서의 첫 트랜젝션에 시도하게 되었다. express 때 트랜젝션에 대해 학습하긴 했지만, 팀플에서도 그렇고 항상 내가 맡은 부분은 트랜젝션이 필요하지 않은 부분이다보니 혼자의 힘으로는 첫 시도라고 할 수 있다. 막막하지만 처음이다 보니 한 발짝 더 나아가는 것 같아 설레는 마음도 들었다.
기본적인 예매 관련 세팅
- create-reservation.dto.ts
export class CreateReservationDto {
@IsInt()
@IsNotEmpty({message:'공연 스케쥴 Id를 입력해주세요.'})
performanceScheduleId: number;
@IsInt()
@IsPositive()
@IsNotEmpty({message: '수량을 입력해주세요.'})
quantity: number;
}
- reservation.controller.ts
//공연 예매
@Post('performances/:performanceId/reservations')
async reservatePerformance(@UserInfo() user: User, @Body() createReservationDto: CreateReservationDto){
const ticket = await this.reservationService.reservePerformance(user, createReservationDto);
return {
status: 201,
message: '공연 예매에 성공했습니다.',
data : ticket
}
}
예매 관련도 비즈니스 로직을 수행하는 service 파일을 제외하고는 기본적인 패턴으로 만들어주었다.
controller를 통해 userInfo 데코레이터에서 user정보를, body를 통해 받아온 createReservationDto를 service로 넘겨준 정도?
예매 가능 조건 걸기 (transaction 이전)
//공연 예매
async reservePerformance(user: User, createReservationDto: CreateReservationDto) {
const { performanceScheduleId, quantity } = createReservationDto;
const schedule = await this.performanceScheduleRepository.findOne({
where: {performanceScheduleId},
relations: ['performance']
});
if(!schedule) {
throw new NotFoundException('공연 스케쥴을 찾을 수 없습니다.');
}
if(schedule.remainingSeats < quantity) {
throw new ConflictException('남은 좌석 수가 부족합니다.')
}
const totalCost = schedule.performance.price * quantity;
if( user.points < totalCost) {
throw new BadRequestException('포인트가 부족합니다.')
}
본격적으로 예매가 이뤄지는 로직 이전에 공연 데이터 불러오기와 함께 예매가 이뤄지면 안되는 오류를 설정해주었다. 그리고 findOne 함수를 사용해 performanceScheduleRepository에서 동일한 performanceScheduleId를 가진 공연을 찾아주었다.
추후 오류 예외 처리와 공연 정보 반환을 위해 performance 데이터도 관계지어 가져와줬다.
- 오류1. 일정이 없을 경우, NotFoundException 오류
- 오류2. 좌석이 부족할 경우, 해당 schedule의 remainingSeats 데이터의 수를 입력한 quantity와 비교하여 더 작을 경우, ConflictException 오류
- 오류3. 관계지어 가져온 performance의 가격과 선택한 quantity의 곱이 유저가 가진 포인트보다 클 경우, BadRequestException 오류.
본격 티켓 예매 (transactionalEntityManager 발견!)
Nest.js도 어려운데 트랜젝션을 할 생각에 막막해하다 TypeORM의 기능을 찾아냈다..
transactionalEntityManager
: 데이터베이스 트랜잭션을 관리하고 실행하는 데 사용
transactionalEntityManager를 사용하면, 트랜잭션 내에서 여러 데이터베이스 작업을 묶어 처리할 수 있다.
const result = await this.reservationRepository.manager.transaction(async transactionalEntityManager => {
user.points -= totalCost;
schedule.remainingSeats -= quantity;
await transactionalEntityManager.save(user);
await transactionalEntityManager.save(schedule);
const ticket = this.reservationRepository.create({
user: user,
performance: schedule.performance,
performanceSchedule: schedule,
quantity: quantity,
paidPoints: totalCost
})
await transactionalEntityManager.save(ticket)
return {
title: schedule.performance.title,
location: schedule.performance.location,
price: schedule.performance.price,
quantity,
totalCost,
performanceDate: schedule.performanceDate,
performanceTime: schedule.performanceTime
}
})
return result
}
- 우선 this.reservationRepository.manager.transaction 메서드를 사용하여 트랜잭션을 시작한다.
- 사용자의 포인트를 예매 총 비용만큼 차감하고, 스케쥴의 남은 좌석 수를 예매된 수량만큼 감소시킨다.
- 수정된 user와 schedule 객체를 데이터베이스에 저장한다. transactionalEntityManager.save 메서드를 사용하여 트랜잭션 내에서 수정된 user와 schedule 객체를 데이터베이스에 저장한다.
- this.reservationRepository.create 메서드를 사용하여 새 티켓 객체를 생성 후 데이터베이스에 저장한다.
- 예매 정보를 반환한다.
- 모든 작업이 성공적으로 완료되면 트랜잭션이 커밋된다. 만약 중간에 예외가 발생하면 트랜잭션이 롤백되어 모든 작업이 취소된다.
이렇게 콜백함수에 this.reservationRepository.manager.transaction 메서드를 사용하여 트랜잭션을 시작한 후, 데이터베이스에서 트랜잭션이 필요한 부분은 transactionalEntityManager를 통해 작업해주면 된다. 생각보다 훨씬 간편하다.
어려울 것이라고 생각했던 트랜젝션을 생각보다 쉽게 마무리할 수 있었다. 이 과정들이 Nest.js와 TypeORM의 매력을 알아가는 과정이라는 생각이 들었다. 앞으로 더 많은 것들을 구현해나가며 아직 모르는 좋은 기능들을 빨리 접해보고 싶다.
'개발 기초 다지기' 카테고리의 다른 글
칸반보드 프로젝트 LexoRank 트러블 슈팅 (0) | 2024.07.11 |
---|---|
NestJS 서버 포트 충돌 문제 해결(기존 포트 프로세스 죽이기) (0) | 2024.07.10 |
예매 가능여부 반환(메모리 내 계산 vs 데이터베이스 필드) (0) | 2024.07.08 |
티켓예매 사이트 공연 날짜/시간 body에서 배열형식으로 받기 (0) | 2024.07.08 |
카테고리별로 공연 목록을 조회 (쿼리 vs URL 경로) (0) | 2024.07.04 |
댓글