Backend/DB

[DB][Prisma] transaction 설정하기

Rayi 2024. 8. 28. 14:58

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);
});
728x90