[DB][Prisma] transaction 설정하기
DB API를 구현할 때는 한 번의 요청에 대해 여러 개의 함수를 실행하는 경우가 있습니다.
이 때 중요한 것은 모든 함수가 한 번에 수행되어야 하는 것인데,
중간에 오류가 발생해 처리가 중단되면, 처리가 완결되지 않은 불완전한 상태로 남기 때문입니다.
예를 들어 상품 재고를 관리하는 서버를 생각해봅시다.
아래 코드는 주문(order) 요청을 처리하는 엔드포인트로, 크게 두 가지 절차를 진행합니다.
1. order 테이블에 새로운 주문정보에 해당하는 record를 추가
2. 해당 주문에서 요청한 상품들의 수량만큼 product 테이블에서 각 record들의 quantity를 감소
(자세한 구현사항은 생략했습니다)
const prisma = new PrismaClient();
app.post('/orders', async (req, res) => {
// ...
// 1. order 테이블에 새로운 record를 추가합니다.
const order = await prisma.order.create({
// ...
});
// 2. 주문한 수량만큼 product 테이블에서 해당 record들의 quantity를 감소합니다.
const queries = productIds.map((productId) =>
prisma.product.update({
// ...
})
);
await Promise.all(queries);
// ...
res.status(201).send(order);
});
위 코드는 문법상으로는 문제가 되지 않습니다.
하지만, 만약 1. order record 추가가 된 후 2. product record quantity 감소 단계에서 에러가 발생하면 문제가 됩니다.
이 경우 엔드포인트는 오류와 함께 종료될 것이고, 주문은 처리되었지만 재고는 감소되지 않은 채로 끝나기 때문입니다.
따라서 엔드포인트 함수들을 안전하게 실행시키려면 1, 2 두 단계가 한 번에 실행되도록 해서
1, 2 모두 실행되지 않거나 모두 실행되는 경우만 존재하도록 해야 합니다.
Prisma에서는 $transaction 함수를 이용해서 이를 구현할 수 있습니다.
$transaction이 받는 인자는 transaction으로 묶을 함수들의 리스트입니다.
await PrismaClient.$transaction([func1, func2, ...]);
$transaction을 이용하면 위 예시를 아래와 같이 구현할 수 있습니다.
2번 단계는 transaction에서 반환할 필요가 없으므로 destructuring(...)처리를 하고, order만 반환하도록 했습니다.
const prisma = new PrismaClient();
app.post('/orders', async (req, res) => {
// ...
const [ order ] = await prisma.$transaction([
// 1. order 테이블에 새로운 record를 추가합니다.
const order = await prisma.order.create({
// ...
}),
// 2. 주문한 수량만큼 product 테이블에서 해당 record들의 quantity를 감소합니다.
...productIds.map((productId) =>
prisma.product.update({
// ...
})
),
]);
// ...
res.status(201).send(order);
});