Prisma를 위한 Transaction Interceptor 구현
트랜잭션은 데이터 베이스에서 중요한 요소입니다.
트랜잭션을 쉽게 이야기하자면 한꺼번에 수행되어야 할 연산들
이라고 볼 수 있습니다.
해당 연선들 중 하나라도 실패하면 작업을 복구하고 전부 성공해야 작업을 데이터베이스에 적용합니다.
트랜잭션이 필요한 예시로 가장 많이 언급되는 것은 송금 서비스가 있습니다.
- A의 잔고를 불러옵니다.
- 읽은 A의 잔고에서 10000원을 인출합니다.
- B의 잔고를 읽어옵니다.
- 읽은 B의 잔고에 10000원을 입금합니다.
- 이를 데이터베이스에 기록합니다.
송금 서비스에서 인출과 입금 중 하나라도 실패하면 에러를 반환해야 합니다.
prisma에서도 transaction을 위한 여러 가지 방법을 제공합니다.
HINT
자세한 내용은 공식문서를 참고해주세요.
Nested writes
기본적으로 prisma는 중첩 쓰기 사용시 transaction을 제공합니다.
post의 update는 author의 생성과 함께 처리되어야 합니다.
The $transaction
API : Sequential operations
PrismaClient에 $transaction
을 통해 prima 작업에 대한 Promise[]
를 제공하면 작업들에 대해 transaction을 시행합니다.
HINT
prisma row query에서 사용하는 문법은 Tagged Template Function입니다.
The $transaction
API :Interactive transactions
또 다른 $transaction
사용법은 runner를 매개변수로 받는 비동기 콜백 함수를 제공하는 것입니다.
WARNING
트랜잭션을 장시간 열어두면 데이터베이스 성능이 저하되고 교착 상태가 발생할 수 있습니다. 트랜잭션 내부에서 느린 쿼리나 네트워크 요청을 실행하지 않도록 하세요.
Prisma Transaction Interceptor
Interceptor를 구현하기 위해서는 Interactive transactions
과 함께 prisma에서 제공하는 client-extensions
을 사용해야합니다.
INFO
구현한 모든 구현체는 저장소에서 확인할 수 있습니다.
client-extensions
을 전부 설명하지는 않고 Promise와 Proxy를 통해 외부에서 내부의 callback에 접근할 수 있다는 아이디어만 가지고 설명하겠습니다.
기존 Prisma Package는 Repository와 Service를 제공합니다.
이때 PrismaService는 PrismaClient를 확장하는 데 Transaction을 사용하기 위해서는 $begin
메서드를 확장해야 합니다.
이를 위해서 withTransaction()
유틸을 통해 PrismaService를 확장합니다.
withTransaction()
유틸은 인자로 받은 PrismaClient를 확장하면서 $begin
메서드를 재공하는 class를 반환합니다.
이후 transaction을 사용하고자 하는 Controller 메서드에 TransactionInterceptor
를 선언하고 runner
를 제공받습니다.
이후 PrismaRepository
에 해당 쿼리를 실행할 runner를 제공합니다.
PrismaTransectionalInterceptor
내부를 살펴보면 PrismaService를 주입받아 $begin()
을 통해 transaction을 실행합니다.
이후 컨트롤러에 응답 결과에 따라 rollback
과 commit
을 선택합니다.
- 만약 해당 컨트롤러에서 error가 throw 된다면 트랜잭션을 롤백 합니다.
- 해당 컨트롤러가 정상적으로 처리된다면 트랜잭션을 commit 합니다.
Prisma Transaction Interceptor
를 통해서 layer에 목적에 맞는 역할과 책임을 유지하면서 트랜잭션을 수행할 수 있게 되었습니다.
하지만 Prisma Transaction Interceptor
는 트랜잭션 시 각 layer들을 통과하는 runner를 제공해야 한다는 단점이 있습니다.
따라서 다음번에는 nest-cls나 Async Local Storage를 사용해 Transaction propagation을 구현해 보도록 하겠습니다.