프로젝트 경과

타입스크립트 프로젝트

문제점들

  1. 타입스크립트를 처음 활용하여 사용하는것이다 보니, 생각보다 애매한점들이 많았다.
  2. 그리고 타입명시에 대해 생각치도 못한 것들에 대해 생각하게 되었다.
  3. 데이터베이스 정보 활용?

첫번째 문제

어디까지 타입 명시를 해야하는거지??

구현을 하다보니 타입스크립트가 타입추론을 하기때문에 자바스크립트 처럼 작성을 하여도 컴파일이 가능하다는 사실을 알았다. 따라서 익숙하게 구현을 하던중, 이렇게 해서는 기존 자바스크립트와 크게 차이점을 못느끼겠다는 생각이 들어 하나하나 타입명시를 하기 시작했는데 타입명시를 하기 시작하니까 해줄게 너무 많다는 생각이 들었다.

그래서 현업에서 권장하는 타입명시 기준치가 궁금해졌고, 나도 이를 따르기로 결심한다

타입스크립트를 사용한 깃허브 프로젝트들을 참고해본 바로는 항상 타입명시를 해주는 곳들이 있다

  1. interface, type: 여긴 당연히 해줘야하고, 안하면 에러난다
  2. 함수의 매개인자
  3. 복잡한 타입, 인터페이스

이렇게 세군데에서는 반드시 타입명시를 해줘야 한다. 특히 함수의 매개인자나, 사용자가 만든 타입, 인터페이스를 객체로 만드는 경우에는 타입명시를 해주는 듯하다

그럼 언제 타입명시를 안해도 되냐?? 비교적 간단한 기본 자료형을 사용할 때에는 따로 엄격하게 타입명시를 안한다. 그리고, 함수의 반환형 또한 특이한 케이스가 아닌 이상 따로 타입 명시를 안하는 것을 많이 봤다.

두번째 문제

데이터베이스에 쿼리를 보낼때 자바스크립트에서는 아무 생각없이 그냥 const raw = db.query(...) 처럼 사용했는데, 타입스크립트에서 작성하려고 하니 타입을 명시를 해줘야만 할것 같은 기분이 들었다.

기존에는 생각하지도 않던 부분들인데, 인터넷 검색을 해보니 pg 모듈에서는 query 결과 객체 타입을 지정할 수 있는 QueryResult 라는 인터페이스를 지원한다는 것을 알 수 있었다. const raw: QueryResult = db.query(...)

신경쓰지도 않던 부분들을 하나씩 알아가는 기분이 좋았고, 굳이 적지 않아도 타입추론을 통해 컴파일은 잘 되지만, 나중에 코드를 읽는 과정에서 이 변수는 QueryResult 라는 것을 단번에 볼 수 있기 때문에 개발자의 의도에 따라 타입을 명시하면 될듯 싶다

세번째 문제

구현을 하는 중에 서버 클래스에서 데이터베이스를 활용해야하는 상황까지 도달했다. 이때 데이터베이스 클래스를 따로 나눠서 구현을 해야하나, 서버 클래스 내부에 그냥 데이터베이스와 통신하게 구현을 해야하나 고민하다가, 데이터베이스 클래스를 따로 만들어서 구현하기로 했다

여기까지는 좋고 괜찮은데, 데이터베이스에서 받아온 정보들을 가공하는 과정에서 데이터 가공을 데이터베이스 클래스에 맡기니까, 정작 필요한 정보들을 다시 서버 클래스에서 데이터베이스 클래스에 요청을 보내야하는 상황들이 있었다.

예를 들어 DB에서 받은 정보가 {물건: "사과", 갯수: "1", 도착지: "길동아파트", 소요시간: "60"} 라고 하면, 데이터베이스 클래스가 가공하여 전달한 정보는 "[오전 10:00] 길동아파트로 사과 1개 60분 소요" 이다

서버 클래스에서는 이러한 문자열 정보를 받기 때문에 아무런 정보가공을 하지 못하고, 가공된 정보를 다시 받기 위해 다시 데이터베이스 클래스에 함수를 추가하는 수 밖에 없었다

요약하자면, 클래스에서 맡는 역할이 애매해지다 보니 이도저도 아닌 상황들이 생기는 것을 느꼈고, 한가지 역할에 충실하게끔 설계하는것이 중요하다는 것을 느꼈다.

구현 과정

이전 설계를 참고하면서 구현하려고 노력했고, 설계를 하고 해당 간선에 맞게 구현을 하려고 노력하다보니 생각보다 수월하게 구현을 하였다

먼저 DB와 통신하고, 비동기적으로 입력을 받게 하는것은 가장 시간이 많이 들어가는 작업일것 같아 뒤로 미뤄두고 먼저 설계상의 점선들을 처리해줬다

PushServer에서 모든 출력을 관리하므로, Producer와 Rider가 보낸 이벤트를 수신하게 클래스를 만들고, 구현했다

PushServer

export class PushServer {
  emitter: EventEmitter;
  constructor(ev: EventEmitter) {
    this.emitter = ev;
    this.emitter.on('StartOrder', (order: Order) => {
      this.sendStartOrderMessage(order);
    });
 
    this.emitter.on('StartDelivery', (order: Order) => {
      this.sendStartDeliverMessage(order);
    });
 
    this.emitter.on('DoneOrder', (order: Order) => {
      this.sendOrderMessage(order);
    });
 
    this.emitter.on('DoneDelivery', (order: Order) => {
      this.sendDeliverMessage(order);
    });
  }
 
  getTime() {
    const date = new Date();
    return date.toLocaleTimeString();
  }
  ...
}

점선들의 방향 역순으로 먼저 이벤트들을 수신받고 발행하게끔 먼저 구현을 하였다. 서로 연결되어 이벤트를 수신, 발신만 할 수 있으면 서버 클래스 구현만 어느정도 마치면, 전체적인 코드 테스트를 해볼 수 있을것이다

Rider

import { EventEmitter } from 'events';
import { Order } from './interface';
 
export class Rider {
  emitter: EventEmitter;
  constructor(ev: EventEmitter) {
    this.emitter = ev;
 
    this.emitter.on('GetRiderOrder', (order: Order) => {
      this.deliver(order);
    });
  }
 
  deliver(order: Order) {
    const { ridingTime } = order;
    this.emitter.emit('StartDelivery', order);
    const timeID = setTimeout(() => {
      const date = new Date();
      const time = date.toLocaleTimeString();
      this.emitter.emit('DoneDelivery', order);
      this.emitter.emit('end');
    }, ridingTime * 500);
  }
}
 

Producer

import { EventEmitter } from 'events';
import { Order } from './interface';
 
export class Producer {
  emitter: EventEmitter;
  constructor(ev: EventEmitter) {
    this.emitter = ev;
 
    this.emitter.on('Order', (order: Order) => {
      this.cook(order);
    });
  }
 
  cook(order: Order) {
    const { orderTime } = order;
    this.emitter.emit('StartOrder', order);
    const timeID = setTimeout(() => {
      const date = new Date();
      const time = date.toLocaleTimeString();
      this.emitter.emit('DoneOrder', order);
      this.emitter.emit('GetRiderOrder', order);
    }, orderTime * 500);
  }
}

Rider와 Producer 모두 SetTimeout 을 통해 일정 시간을 기다린 후 완료 이벤트를 발행하게끔 구현하였고, 보면 알겠지만 최대한 단순하게 구현하였다.

Server

export class Server {
  emitter: EventEmitter;
  db: DataBase;
  constructor(ev: EventEmitter) {
    this.emitter = ev;
    this.db = new DataBase();
  }
 
  order(order: Order) {
    this.emitter.emit('Order', order);
  }
 
  async getRegion(): Promise<string> {
    const message = await this.db.getRegion();
    return message;
  }
  async getShop(placeId: number): Promise<string> {
    const message = await this.db.getShop(placeId);
    return message;
  }
  ...
}

App

export class App {
  consumer: Consumer;
  server: Server;
  emitter: EventEmitter;
  orderList?: Order;
  rl: readline.Interface;
  constructor(ev: EventEmitter, sv: Server, dest: string) {
    this.consumer = new Consumer(dest);
    this.server = sv;
    this.emitter = ev;
    this.rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });
 
    this.rl.on('close', () => {
      console.log('Readline interface closed.');
      process.exit(0); // 프로세스를 종료합니다.
    });
    this.emitter.on('PrintMessage', (message: string) => {
      this.printMessage(message);
    });
 
    this.emitter.on('end', () => {
      this.rl.close();
    });
  }
  printMessage(message: string) {
    console.log(message);
  }
  question(rl: readline.Interface, ask: string): Promise<number> {
    return new Promise((resolve) => {
      rl.question(ask, (input) => resolve(Number(input)));
    });
  }
  async getOrderList() {...}
  ...
  sendOrderList() {
    if (this.orderList) this.server.order(this.orderList);
  }
}

아직 잘 모르겠는건, rl에 대한 처리를 어떻게 해줘야할 지 잘 모르겠다. 코드를 실행하는 index.ts파일에서 입력을 받아도 좋겠지만, 나는 뭔가 App.ts 파일에서 입력을 처리하게 구현하고 싶었다.

일단은 App 내의 멤버 변수로써 createInterface메소드를 호출하여 입출력을 관리하게 구현하였다.

Main

const main = async () => {
  const ev = new EventEmitter();
  const sv = new Server(ev);
  const psv = new PushServer(ev);
  const pr = new Producer(ev);
  const rd = new Rider(ev);
  const app = new App(ev, sv, '삼부아파트');
  const orderList: Order | undefined = await app.getOrderList();
  const time = getTime();
  console.log(`${time} 주문완료`);
  console.log(orderList);
  app.sendOrderList();
};

EventEmitter에서 아직 잘 이해가 가지 않는 부분이 있는데, 결국 메인 코드에서 직접적으로 사용하지 않아도 이벤트를 수신하기 위해서는 클래스 인스턴스를 만들어야한다 따라서 PushServer, Rider, Producer 은 메인 함수에서 사용되지 않지만, 객체를 만들어야 제대로 작동한다

그리고 EventEmitter가 비동기적으로 작동하는것을 보여주기위해 메인함수에서도 console.log로 일부 출력을 하게끔 구현하였다.

결과

````ad-white
title:입력
![[Pasted image 20240817182523.png]]
````
 
````ad-white
title:출력
![[Pasted image 20240817182623.png]]
````

일단 시간을 0.5초 기준으로 실행했다 (사진기준, 조리시간 2.5초, 배달시간 5초)

마치며

이전에 비슷한 경험을 했을 때는 하루의 절반 이상을 사용해가면서 EventEmitter, 비동기, 리팩토링, 다시 설계 등 시간을 사용했는데 이번에는 설계대로 구현을 한 것 같아 시간도 적게 쓰고, 좀 더 전보다 깔끔해진것 같아 기분이 좋다

아직 리팩토링, 추가 구현사항 과 같은것들은 진행하지 않았는데 시간이 좀 남으면 더 진행해보려한다 일단은 타입스크립트에 좀 더 익숙해지기 위해 한 아주 작은 프로젝트였기 때문에 이정도로 만족하려고 한다

만약 추가적으로 구현을 더 한다면 아래와 같은 사항들을 신경 쓸것같다

  1. 가계에서 조리사람 배정, 사람당 가능한 조리 갯수 배정 → 한명이 2개의 음식을 한번에 할 수 있으면 주문들어온 음식이 3개일때 시간이 더 소요된다
  2. 배달기사 배정 → 기사가 총 5명있고, 주문이 10개가 밀려있으면, 그만큼 기사가 일을 마칠 동안 대기를 해야한다
  3. 추가적인 음식주문 가능 → 지금 현재는 음식하나만 주문 할수 있는데, 여러 주문을 할수 있게 구현한다