transaction 활용하기

이종훈·2025년 3월 29일
1

개발 일지

목록 보기
5/21
post-thumbnail

문제 상황


모바일 청첩장 프로젝트에서 청첩장 등록 API 실행 시 갤러리의 이미지 개수는 최대 9개까지 등록할 수 있도록 유효성 검사 로직을 구현했습니다.
하지만 이때 10개 이상의 이미지를 등록하면 콘솔에 에러 메세지는 정상적으로 출력되지만, invitations, calendars, maps의 데이터 값은 정상적으로 db에 등록이 되는 문제가 발생하였습니다.


문제 원인

에러가 발생하여 코드가 중단되어도 데이터 값은 정상적으로 db에 등록되는 이유는 다음과 같습니다.
우선 코드의 순서 상 invitations -> calendars -> maps -> galleries -> accounts -> contacts -> notices 순서대로 sequelize 메서드를 통해 db에 값이 등록됩니다. 이때 galleries에서 유효성 검사에 통과하지 못해 에러가 출력되면 galleries의 뒷 순서 데이터(accounts, contacts, notices)의 처리는 중단되지만, 그 전에 이미 실행된 데이터(invitations, calendars, maps)의 처리는 정상적으로 이루어지게 되는 것입니다.


해결 과정

transaction이란?

이 문제를 해결하기 위해선 에러 처리가 난 후 그 전에 이루어진 데이터 처리를 무효화시켜주는 로직이 필요합니다. 이때 transaction을 활용할 수 있습니다.
transaction이란 하나의 작업 단위를 묶어서 처리하는 개념으로서, 데이터베이스에서 여러 작업이 있을 때 모두 성공하거나 모두 실패하도록 보장해주는 역할을 해줍니다.
이 transaction을 현재 문제 상황에 활용한다면 오류 발생 시 모든 db 변경 사항을 되돌림으로서 데이터 처리 과정을 모두 실패하도록 처리할 수 있습니다.

transaction 활용을 통한 문제 해결

  1. 우선 각 repository의 create 함수에 transaction 타입을 추가합니다.
export const createInvitation = async (invitationData: InvitationData, transaction: Transaction) => {
  try {
    return await db.Invitation.create(invitationData, {transaction});  // db.Invitation으로 접근
  } catch (err) {
    throw new Error(`청첩장 등록 에러: ${(err as Error).message}`);
  }
};
  1. 그 후 service에서 마찬가지로 transaction 타입을 선언해준 후 각 create 메서드에 transaction 옵션을 추가해줍니다. 그리고 models에서 선언했던 db.sequelize 메서드를 가져와서 transaction 변수를 생성합니다.
export const createInvitation = async ( userId: number, invitationData: Omit<InvitationData, 'userId'>, 
  calendars: CalendarData[], maps: MapData[], galleries: GalleryData[], accounts: AccountData[], contacts: ContactData[], notices: NoticeData[] ): Promise<{ id: number }> => {
    const transaction = await db.sequelize.transaction();
  
    try {
    const newInvitation = await invitationRepository.createInvitation({ ...invitationData, userId }, transaction);
    
    if (calendars && calendars.length > 0) { // 캘린더 정보가 들어오면 저장
      const calendarsWithInvitationId = calendars.map((calendar) => ({
        ...calendar,
        invitationId: newInvitation.id, // 생성된 초대장의 id
      }));
      await invitationRepository.createCalendar(calendarsWithInvitationId, transaction);
    }
  1. service의 모든 함수가 정상적으로 실행되었다면 transaction.commit을 통해 db에 모든 데이터 값을 등록하고, 만약 에러가 발생했다면 transaction.rollback을 통해 기존에 등록했던 데이터들을 모두 무효화시킵니다.
await transaction.commit();

    return { id: newInvitation.id };

  } catch (err) {

    if(transaction) {
      await transaction.rollback();
      console.error('청첩장 등록 에러:', (err as Error).message);
    }

    if (err instanceof ClientError) { // ClientError의 경우 따로 처리
      throw err;

    }

위 과정을 통해 에러 발생 시 db에 데이터 값이 등록되는 문제를 해결할 수 있습니다.

profile
종훈리의 개발일지

0개의 댓글