원티드 프리온보딩 기업과제 4차 회고록

전준영·2022년 10월 24일
0

프로젝트 회고록

목록 보기
6/6
post-thumbnail

📌 병원 예약 시스템 구축하기

1. 프로젝트 소개

병원에 갈때 대기하지 않고 바로 진료를 볼 수 있도록 병원 예약 시스템을 구축하기 위한 프로젝트 입니다.

1. 프로젝트 진행기간 및 인원

  • 2022.10.15 ~ 2022.10.17
  • BE : 1명

2. 기술스택

  • Back-End : TypeScript, Express, Typeorm, MySQL
  • Common : RESTful API, Git, Github
  • Communication : Slack, Notion, Zep

3. 맡은 기능

  1. 예약 가능 병원리스트
  2. 예약 가능시간 조회
  3. 예약 하기
  4. 예약 목록
  5. 예약 변경

👉 Github link

4. db모델링

👉 DB diagram link

2. 프로젝트를 진행하며 느낀점

마지막 기업과제는 혼자서 프로젝트를 진행하게 되었다.
데이터베이스 모델링 과정부터 혼자서 해보다가 혼자 생각하고 모델링을 하는 것보다 다른 사람과 함께 여러 의견을 공유하며 모델링을 하는 것이 퀄리티가 더 좋을 것 같다는 생각이 들어 평소 같이 공부하던 동기와 함께 모델링을 하기 시작했다.
모델링을 하며 병원 예약 시스템에 현실적으로 접근하며 해보다가 개발 사이즈가 많이 커졌었는데 마감 기간이 3일 이내인 것을 감안하여 많이 축소하고 여러가지 가정상황을 만들면서 개발을 시작했다.

바로 직전과제에서 typescript를 감명깊게 썼기 때문에 항상 만들던 express에 typescript를 적용시켜 보고싶었기 때문에 미숙하지만 express + typescript를 혼자 생각대로 세팅해보고 레이어드패턴으로 개발을 해보았다.

우선 이번 과제를 하며 감명깊었던 부분이 여러개 있었는데 우선 첫번째로는 에러확장 이었다.
nest.js 에는 여러가지 exception을 제공해주었었는데 express는 그런 부분이 없었기 때문에
내가 필요한 에러를 확장해서 사용했다.

1. 에러확장

class UnAuthorizedError extends Error {
  private statusCode: number;
  constructor(message: string) {
    super(message);
    this.name = "UnAuthorized Error";
    Error.captureStackTrace(this);
    this.statusCode = 403;
  }
}

class NotExistError extends Error {
  private statusCode: number;
  constructor(message: string) {
    super(message);
    this.name = "Not Exist Error";
    Error.captureStackTrace(this);
    this.statusCode = 404;
  }
}

class BadRequestError extends Error {
  private statusCode: number;
  constructor(message: string) {
    super(message);
    this.name = "BadRequestError";
    Error.captureStackTrace(this);
    this.statusCode = 400;
  }
}

export { BadRequestError, NotExistError, UnAuthorizedError };

어떤 식으로 에러확장을 해야하는지 찾아보면서 이렇게 확장시켜 예외사항 곳곳에 사용하였다.

두번째로 잘했다고 생각한 것은 중복되어 쓰이는 코드를 따로 관리하여 import하여 사용했다는 점이다.

2. 중복코드 관리

// src/common_function/checkDate.ts

import reservationDao from "../models/reservationDao";
import { BadRequestError } from "../middleware/error_creator";

export const checkTime = async (
  id: number,
  checkDate: string,
  checkTime: string,
  interval: string
) => {
  const [hospitaldata] = await reservationDao.getVerifyTime(id);

  const date = new Date(checkDate);
  const lunchTime = hospitaldata.lunch_time.split("~");
  const lunchStart = Number(lunchTime[0].split(":").join(""));
  const lunchEnd = Number(lunchTime[1].split(":").join(""));
  const time = Number(checkTime.split(":").join(""));
  const verifyInterval = checkTime.split(":")[1];

  if (hospitaldata.is_active === 0) {
    throw new BadRequestError(`hospital is not open`);
  }

  if (date.getDay() === 6) {
    if (hospitaldata.saturday === 0) {
      throw new BadRequestError("unavailable in saturday");
    }
    if (checkTime >= hospitaldata.saturday_close_time) {
      throw new BadRequestError("unavailable time");
    }
  }

  if (date.getDay() === 0) {
    if (hospitaldata.sunday === 0) {
      throw new BadRequestError("unavailable in sunday");
    }
    if (checkTime >= hospitaldata.sunday_close_time) {
      throw new BadRequestError("unavailable time");
    }
  }

  if (checkTime > hospitaldata.close || checkTime < hospitaldata.open) {
    throw new BadRequestError("unavailable time");
  }
  if (time > lunchStart && time < lunchEnd) {
    throw new BadRequestError("hospital lunch time");
  }

  if (interval === "30m") {
    if (verifyInterval !== "00" && verifyInterval !== "30") {
      throw new BadRequestError("check time interval");
    }
  }
  if (interval === "1h") {
    if (verifyInterval !== "00") {
      throw new BadRequestError("check time interval");
    }
  }
};

export const checkDate = (date: string) => {
  let now = new Date().toLocaleString().split(",", 1)[0].split("/");
  let tmp = now[2];
  now.splice(2, 1);
  now.unshift(tmp);
  const newNow = now[0] + "-" + now[1] + "-" + now[2];
  if (newNow > date) {
    throw new BadRequestError("check reservation date");
  }
};

이렇게 긴 로직이 service 단에 두번 중복되어 존재했었기 때문에 코드가 너무 길어져서 가독성이 많이 떨어졌었는데 함수로 만들어 다른 파일로 따로 관리를 해두었기 때문에 함수만 import 해와서 코드가 많이 간결해졌다.

import { checkDate, checkTime } from "../common_function/checkDate";

const reservationService = async (data: ReservationDto, userId: number) => {
  checkDate(data.date);

  const [timeInterval] = await reservationDao.getHospitalIntervalTime(data.hospital_id);
  await checkTime(data.hospital_id, data.date, data.time, timeInterval.time_interval);
 
  	...
    
  );

  return await reservationDao.createReservation(data, userId);
};

이렇게 checkDate, checkTime 이라는 함수를 사용하니 훨씬 간결해지고 가독성도 좋았다.

이외에 추가구현사항에 예약완료 시에 완료 알림을 구현하라는 요청사항이 있었는데 nodemailer라는 라이브러리를 사용해서 이메일 발송 기능도 처음으로 구현 해보았다.

// src/common_function/nodemailer.ts

import nodemailer from "nodemailer";

export const sendGMail = (param: nodemailer.SendMailOptions) => {
  const transporter = nodemailer.createTransport({
    service: "gmail",
    port: 587,
    host: "smtp.gmail.com",
    secure: true,
    requireTLS: true,
    auth: {
      user: process.env.MAILER_EMAIL,
      pass: process.env.MAILER_PASSWORD,
    },
  });

  const mailOptions: nodemailer.SendMailOptions = {
    from: process.env.MAILER_EMAIL,
    to: param.to,
    subject: param.subject,
    text: param.text,
  };

  transporter.sendMail(mailOptions, (err, info) => {
    if (err) {
      console.error(`Failed to send mail = [${err.name}] ${err.message}`);
      return false;
    } else console.log(`Successed to send mail - [${info.messageId}] ${info.response}`);
  });
  return true;
};

이런식으로 common_function 파일에 함수로 구현을 해놓고 필요한 곳에서 import하여 사용했다.


import { sendGMail } from "../common_function/nodeemailer";

const reservationService = async (data: ReservationDto, userId: number) => {
  
  		...

  const [email] = await reservationDao.getUserEmail(userId);
  const [hospital] = await reservationDao.getHospitalName(data.hospital_id);
  sendGMail({
    to: `${email.email}`,
    subject: `${hospital.name}에 예약이 완료되었습니다.`,
    text: `lululab 병원예약 서비스를 이용해주셔서 감사합니다.
    ${hospital.name}${data.date} ${data.time} 에 정상적으로 예약이 완료되었습니다.
    예약된 시간에 병원에 방문하지 않을 시 어플 이용에 제한이 있습니다.`,
  });

  return await reservationDao.createReservation(data, userId);
};

이렇게 예약 병원 이름과 예약된 시간을 데이터베이스에서 정보를 가져와 예약 정보를 사용자에게 이메일로알려주는 기능을 만들어 보았다는 것에 대해 뿌듯했다.

기업과제를 지금 까지 진행하면서 어떻게 해야 더 깔끔한 코드를 작성할 수 있을지에 대해 고민하고 계속 여러가지 기능을 적용시켜보고싶어 공부하게되었었는데 이런게 개발의 매력이 아닐까싶었다.
앞으로도 계속 성장하는 주니어 개발자가 되어야겠다고 다짐하게되는 좋은 프리온보딩 코스였다.👍

profile
Just do it!

0개의 댓글