[ Study ] 실무와 가까워지는 백엔드 개발 스터디.세션_#3
💪 Study 참여/코드리뷰 스터디

[ Study ] 실무와 가까워지는 백엔드 개발 스터디.세션_#3

반응형

 

이번 포스팅에서는 벌써 세션 3번째 시간에 대한 내용을 담아보려고 한다.

지난 세션에서는 MVC 패턴에 대해서 배웠고 더 나아가 한 이벤트에 접근하여 수정하는 API까지 구현해 보았다.

이번 시간에서 중요하다고 생각하는 부분인 Transaction에 대해서 알아보려고 한다.

 

Transaction ?

Firebase의 firestore 공식문서에서를 살펴보면 좋을 것 같아 링크해 두었다.

 

트랜잭션 및 일괄 쓰기  |  Firebase Documentation

Join Firebase at Google I/O online May 11-12, 2022. Register now 의견 보내기 트랜잭션 및 일괄 쓰기 Cloud Firestore는 데이터의 원자적 읽기 및 쓰기를 지원합니다. 원자적 작업 집합에서는 모든 작업이 성공하거

firebase.google.com

우선 들어가기 앞서 내가 배웠던 지식에서 생각해보았다.

DB시간에 배웠던 것 같은데 간단하게 말하자면 작업 집합을 의미한다.

예로는 이러한 예를 들었던 것 같았다.

A라는 사람이 B에게 10000원을 이체한다고 생각해보자.

그러면 이체 단계는 아래와 같을 것이다.

  1. A의 계좌에 10000원이 있는지 조회
  2. 출금이 가능하면 A의 계좌에서 10000원을 출금
  3. B의 계좌에 10000원을 입금

그렇지만 항상 위의 과정이 정상적으로만 수행되진 않는다. 언제든 예외가 존재하기 때문이다.

만약 2의 과정을 수행하던 도중 어떤 알 수 없는 문제가 발생해서 A의 계좌에서는 10000원이 빠져나갔지만

3번 과정을 수행하지 못하고 예외가 발생한다면??? (A는 돈이 빠져나갔지만 B에는 들어오지 못하는 엄청난 문제 발생)

따라서 위의 1~3 과정을 하나로 묶어서 작업 집합을 만들어서 한 번에 처리를 하겠다는 것이 바로 Transaction이다.

묶어서 처리를 한다면 1번 과정이 시작되고 3번 과정까지 정상적으로 끝마쳤을 때 비로소 작업이 끝났다고 판단하여 commit을 처리한다는 것이다.

만약 위의 과정 중 어디에서나 예외 상황이 발생한다면 1번 과정을 진행하기 전 상황으로 DB를 RollBack 시켜준다.

이를 통해 Database의 정합성을 유지할 수 있는 것이다.

 

실제 firebase에서는 어떤 식으로 Transaction을 사용하는지에 대한 예제를 살펴보자.

// Initialize document
const cityRef = db.collection('cities').doc('SF');
await cityRef.set({
  name: 'San Francisco',
  state: 'CA',
  country: 'USA',
  capital: false,
  population: 860000
});

try {
  await db.runTransaction(async (t) => {
    const doc = await t.get(cityRef);

    // Add one person to the city population.
    // Note: this could be done without a transaction
    //       by updating the population using FieldValue.increment()
    const newPopulation = doc.data().population + 1;
    t.update(cityRef, {population: newPopulation});
  });

  console.log('Transaction success!');
} catch (e) {
  console.log('Transaction failure:', e);
}

 

try 구문 이전에는 cities라는 collection의 SF라는 문서의 레퍼런스를 가져오고. set을 통해서 초기화를 시켜주는 구문이다.

이후에는 runTransaction을 통해 실제로 문서에 접근해 조회하고 값을 업데이트하는 위에서 설명한 작업 집합이다.

처음에. get으로 문서를 가져오고 doc.data()의 실제 데이터에 접근해서 population이라는 값을 1 업데이트시키는 작업이다.

이런 식으로 사용할 수 있다고 한다. 

이때, 주의해야 할 점으로는 아래와 같다.

  • 읽기 작업은 쓰기 작업 전에 이루어져야 한다.
  • 트랜잭션에서 읽는 문서에서 동시에 수정이 이뤄지는 경우 트랜잭션 함수가 여러 번 실행될 수 있다.
  • 트랜잭션 함수가 애플리케이션 상태를 직접 수정하면 안 된다.

위의 예시를 토대로 애플리케이션에서 실제로 Transaction 처리를 적용해 보았다.

 

async removeOrder(args: { eventId: string; guestId: string }) {
    const eventDoc = this.EventDoc(args.eventId);
    const orderCollection = this.OrdersCollection(args.eventId);
    const oldDoc = orderCollection.doc(args.guestId);

    await FirebaseAdmin.getInstance().Firestore.runTransaction(async (transaction) => {
      const doc = await transaction.get(eventDoc);
      if (doc.exists === false) {
        throw new Error('not exist event');
      }
      await transaction.delete(oldDoc);
    });
    await this.updateCache({ eventId: args.eventId });
 }

 

위의 함수는 특정 주문서 이벤트 내에서 내가 주문한 내역을 삭제(delete)하는 Transaction이다.

인자로 eventId와 guestId를 받고 문서의 존재 유무를 파악한 뒤, 요청한 user의 주문을 삭제하는 함수이다.

아래의 코드는 요창 리더님에게 리뷰를 받은 내용이다.

 

async removeOrder(args: { eventId: string; guestId: string }) {
    const eventDoc = this.EventDoc(args.eventId);
    const orderCollection = this.OrdersCollection(args.eventId);
    const oldDoc = orderCollection.doc(args.guestId);

    await FirebaseAdmin.getInstance().Firestore.runTransaction(async (transaction) => {
      const doc = await transaction.get(eventDoc);
      if (doc.exists === false) {
        throw new Error('not exist event');
      }
      const docData = doc.data() as IEvent;
      if (docData.closed !== undefined && docData.closed === true) {
        throw new Error('closed event');
      }
      await transaction.delete(oldDoc);
    });
    await this.updateCache({ eventId: args.eventId });
  }
}

 

리뷰받은 내용으로는 주문 이벤트가 존재하는 여부를 파악하는 것 이외에도 접근한 주문의 상태에 따라서

마감 상태이면 접근하지 못하도록 예외처리를 하는 것이 좋다고 말씀하시고 바꾼 코드이다!

IEvent는 Event 즉, 주문에 대한 Interface를 의미하며 그 안에는 closed라는 boolean type의 변수가 있어

True이면 주문을 할 수 없는 상태이고 반대로 False이면 주문이 닫혀있지 않은 상태를 의미한다.

이렇게 하면 마감된 주문에 본인이 주문했던 내역을 삭제하지 못하도록 하여 더 안전한 코드가 되었다!

 

 

그리고 이번 세션에서는 리더님께서 지난 코드 리뷰 요청에 대한 내용들 중 깔끔한 PR이 있다고 하셔서

해당 양식을 정리해보려고 한다.

 

 

스터디를 같이 진행하는 동기분이 정리한 리뷰 PR을 보여주시고 위와 같은 형식이면 좋겠다고 강조를 하셨던 것 같았다.

살펴보니 최근에 정리한 포스팅의 내용과 겹치는 부분이 꽤 보이는 것 같았다.

2022.04.06 - [🌈 프로그래밍/[Study] Node.js Backend] - [ Study ] 코드 리뷰를 하는 이유가 무엇일까?

 

[ Study ] 코드 리뷰를 하는 이유가 무엇일까?

이번 스터디를 진행하면서 처음으로 코드 리뷰라는 것을 경험할 수 있었다. 학부시절에 내가 가지고 있던 마인드는 다음과 같았다. 내가 한게 잘 동작하고 있고, 각자 자기가 맡은 부분에 대해

tasddc.tistory.com

앞으로 리뷰와 관련하여 나도 더 꼼꼼하고 자세하게 기록하려고 노력해야겠다.

 

반응형