본문 바로가기

Javascript & Typescript/실무와 가까워지는 Node.js 백엔드 개발

[3주차] Order API 강의 요약

더보기

본 주차에서는 지난 주차와 비슷하지만 주문서 내 메뉴를 추가하고 제거하는 API를 작성하는 방법을 다루고 있다. 이번 주차에서 주어진 코드는 2주차 과제의 정답이라고 할 수 있다.

목차

  1. Model, View, Controller
  2. 트랜잭션 (Transaction)

Model, View, Controller

스터디에서 제공되는 코드는 MVC 구조를 따르고 있다. MVC에서 M은 Model(모델), V는 View(뷰), C는 Controller(컨트롤러)를 의미한다.

모델(Model)은 보통 데이터를 정의하고 다루는 코드들을 의미한다. 다시 얘기하면 Model에 해당하는 코드는 DB와 직접적으로 통신하는 코드들로 이루어져 있다. 위키피디아에 따르면 Model은 Model의 변화를 View나 Controller에게 통보하는 식으로도 작성될 수 있지만, Controller가 Model의 상태를 읽어오는 식으로도 가능하다고 한다.

class EventType {
  private EventsStore;

  constructor() {
    this.EventsStore = FirebaseAdmin.getInstance().Firestore.collection(COLLECTION_NAME);
  }

  EventDoc(eventId: string) {
    return this.EventsStore.doc(eventId);
  }

  OrdersCollection(eventId: string) {
    return this.EventsStore.doc(eventId).collection('orders');
  }

  async find({ eventId }: { eventId: string }): Promise<IEvent & { id: string }> {
  	// 주문서 조회
    // ...
    const eventSnap = await this.EventDoc(eventId).get();
    // ...
  }

  async add(args: { title: string; desc: string; owner: InMemberInfo; lastOrder?: Date }): Promise<IEvent> {
    // 주문서 생성
    // ...
    const result = await this.EventsStore.add(addData);
    // ...
  }

  async update(args: {
    // 주문서 갱신
    // ...
    await eventSnap.update(updateData);
    // ...
  }

  async findOrders({ eventId }: { eventId: string }) {
  	// 주문 메뉴 목록
  }

  async addOrder(args: { eventId: string; order: IEventOrder }) {
  	// 주문 메뉴 추가
    // ...
    await FirebaseAdmin.getInstance().Firestore.runTransaction(async (transaction) => {
    const doc = await transaction.get(eventDoc);
    // ...
    await transaction.set(
    	// ...
    );
    // ...
  }

  async removeOrder(args: { eventId: string; guestId: string }) {
  	// 주문 메뉴 삭제
    // ...
    await this.OrdersCollection(args.eventId).doc(args.guestId).delete();
    // ...
  }
}
export default class EventController {
  static async addEvent(req: Request, res: Response): Promise<any> {
  	// 주문서 생성
    // ...
    const result = await Events.add(reqParams);
    // ...
  }

  static async updateEvent(req: Request, res: Response): Promise<any> {
	// 주문서 갱신
    // ...
    const result = await Events.update(reqParams);
    // ...
  }

  static async findEvent(req: Request, res: Response): Promise<void> {
	// 주문서 받아오기
    // ...
    await Events.find({ eventId: validateReq.data.params.eventId });
    // ...
  }
  
  static async findOrders(req: Request, res: Response): Promise<void> {
  	// 메뉴 찾기
    // ...
    const result = await Events.findOrders({ eventId: validateReq.data.params.eventId });
    // ...
  }

  static async addOrder(req: Request, res: Response): Promise<void> {
  	// 메뉴 추가
    // ...
    const result = await Events.addOrder({
        // ...
    });
    // ...
  }

  static async deleteOrder(req: Request, res: Response): Promise<void> {
  	// 메뉴 삭제
    // ...
    const result = await Events.removeOrder(validateReq.data.params);
    // ...
  }
}

실제로 주어진 코드는 대략 위와 같으며 클라이언트에서 요청이 오면 컨트롤러에서 모델의 함수를 실행시키는 방식으로 작성되어 있다. 즉, 위에서 설명한 MVC 구조대로 Controller에서는 View라고 할 수 있는 클라이언트와 Model을 연결시켜주고 있다.

트랜잭션 (Transaction)

트랜잭션은 일반적으로 DB에서 많이 쓰이는 Transaction이다. FireStore 공식 문서에서 트랜잭션에 대해서 다음과 같이 설명하고 있다.

  • 읽기 작업은 쓰기 작업 전에 이루어져야 합니다.
  • 트랜잭션에서 읽는 문서에서 동시에 수정이 이뤄지는 경우 트랜잭션을 호출하는 함수(트랜잭션 함수)가 여러 번 실행될 수 있습니다.
  • 트랜잭션 함수가 애플리케이션 상태를 직접 수정하면 안 됩니다.
  • 클라이언트가 오프라인 상태면 트랜잭션이 실패합니다.

처음에 트랜잭션은 최적화가 목표라고 생각했는데, 좀 찾아보니 최적화는 별로 상관없는 것 같고 정확한 처리를 위한 논리적인 이유로 사용되는 것으로 보인다. FireStore의 트랜잭션도 함수 코드 전체를 서버에다 보내는 건가 싶었는 데, 조금 찾아본 결과 그런 수준은 아닌 것으로 보였다. 트랜잭션에 대한 좀 더 자세하면서도 간결한 설명은 아래 글에 잘 나와 있다.

 

[DB기초] 트랜잭션이란 무엇인가?

 트랜잭션의 정의 트랜잭션(Transaction)은 데이터베이스의 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 또는 한꺼번에 모두 수행되어야 할 일련의 연산들을 의미한다. 트

coding-factory.tistory.com