1차 프로젝트 회고

HiWoong·2023년 2월 18일
0
post-thumbnail

팀명 : 팔레트

프로젝트명 : 향수 쇼핑몰 구현

프로젝트 기간 : 1/30 ~ 2/10

기술 스택 : React, Node.js, Express.js, MongoDB

소통 : 깃랩, 엘리스 플랫폼, 디스코드, 오픈 카카오톡 채팅방(파트별)


총 팀원은 6명이고, 프론트 3명, 백 3명으로 구성되며 나는 백엔드 파트를 맡았다.
백엔드 파트는 크게 사용자 기능, 상품 기능, 주문 기능으로 나누었고
사용자 기능 관련 파트가 나에게 주어졌다.

솔직히 처음에는 api라는 개념도 어설프게 이해한 상태였고, 누구에게 설명하지는 못하는 정도였다. 그래서 첫 하루, 이틀은 깃허브 오픈 소스나, 엘리스 실습 때 했던 예제 등을 뒤져보면서 공부했다. 아무것도 모른채로 시작해서 나중에 유지 보수도 제대로 안되는 코드를 짜는 것 보다는 천천히 시작하더라도 확실하게 틀을 잡고 가는 것이 좋다고 생각했다.

이 부분에서 코치님이 굉장한 도움을 많이 주셨다. 정말 다행히도 백엔드 강의 때 실제로 강의를 해주신 코치님이셨고 코드를 짜기 전에 어떤 구조를 가지고 해야하는지, 코드 예시와 설명을 충분히 해주셨다.

깃랩에 커밋할 때마다 내가 어떤 것을 수정하고 개발했는지 혼자 정리하며 진행했다.
이런 로그들이 남아있어야 내가 전에 어떤 것을 잘못했고, 혹은 어떤 점이 괜찮았는지 아는데 큰 도움이 된다고 생각했다.

시간 흐름대로 회고를 해보겠다. 첫 주차부터 시작!

사용자 관련 기능 1, 2차

먼저, 1/31 ~ 2/1은 앞서 설명했던 아무것도 모른 채 예제와 오픈 소스 혹은 실습 때 한 자료를 뒤져가며 정말 "기능 구현" 만을 했다. 즉, 유지 보수나 코드컨벤션 같은거는 집어치우고 차를 만들 때 덜컹거리고 위험하지만 일단 굴러가게만 했다고 생각하면 편하다.

다른 프로젝트원들도 분명 그렇겠지만, 첫 주차에 과장을 조금 보탠다면 자는 시간 + 밥먹고 씻는 시간을 제외하고 컴퓨터 앞에 앉아있었다. 물론 그 시간들을 다 개발에 집중했다고 하면 거짓말이지만 실제로 하루에 12시간 이상은 소스를 찾거나 구현하거나, 이해하는데 시간을 쏟았다.

// 초기 회원가입 구현 코드
  async create({ email, name, password, address }) {
    const verifyEmail = await User.findOne({email:email})
    if(!verifyEmail) {
      const check = /\S+@\S+\.\S+/;
      if(!check.test(email)) {
        throw new Error("이메일 형식이 아닙니다.")
      }
      const user = await User.create({email, name, password, address})
      return user;
    }
    else {
      throw new Error("중복된 이메일입니다.")
    }

  }

부끄럽지만 직접 구현했던 소스들도 몇 개 올려보려한다.
언뜻 보기에도 알겠지만, 사용자에게 데이터를 입력받고 DB에 저장하는 코드다.
정규 표현식으로 이메일 형식을 체크하고, 만약 DB에 입력받은 이메일을 검색했을 때 값이 존재한다면 에러를 반환한다.

웃을 수 있지만 이 때, 나는 나름대로 일찍 구현하기도 했고 좀 뿌듯했다.😂
그리고 프로젝트를 진행하며 2주간 총 5회 오피스아워라고 코치님께 궁금한 것을 여쭤보고 이슈 처리를 도와주시는 시간이 있었고, 주말마다 총 2번 코드 리뷰하는 시간이 있었다.

2/1에 코치님께 설명을 듣고 난 후 위 README 파일에서 많은 변경점이 있다는 것을 알 수 있다.
사용자 관련 기능 3차~6차가 모두 구조 변경, 코드 추가 수정의 경우이다.

사용자 관련 기능 3차

실습 때 사용자 id를 구별하는 것을 nanoid라는 패키지를 사용해서 고유 id를 만들어주고 그것으로 구분했었는데, mongodb에 데이터를 저장하면 기본적으로 _id라는 ObjectId를 제공해준다. 코치님께서 패키지를 많이 사용할수록 코드가 무거워지고 좋은 것이 아니다라고 말씀해주셔서 삭제하고, _id를 참조하는 방향으로 변경했다.

또한, 초기에 생각하기에 관리자도 사용자처럼 로그인 후 기능을 사용하는 것이라고 생각해서 userRouter에 넣었었는데, 관리자는 라우터를 아예 따로 관리하는게 코드 오류 관리 측면에서 더 좋다고 판단해서 나누었다.

사용자 관련 기능 4차

위에 있는 초기 회원가입 코드에 이메일 형식 체크하는것이 번거롭고, if문을 가능한 줄이면 줄일수록 코드의 안정성이 올라간다고 생각해서 여쭤보니 Joi패키지를 알려주셔서 미들웨어를 만들고 라우터에 적용했다.

const express = require("express");
const { userController } = require("../controller");
const { userMiddleware } = require("../middleware");

const userRouter = express.Router();

// 회원가입
userRouter.post(
  "/",
  userMiddleware.checkCompleteUserFrom("body"),
  userController.addUser
);

코드에 userMiddleware.checkCompleteUserFrom("body")라는 코드가 바로 Joi 스키마를 이용해서 데이터를 분류해주는 미들웨어다.

const signInSchema = Joi.object({
  email: Joi.string()
    .pattern(
      new RegExp(
        "^[0-9a-zA-Z]([-_]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_]?[0-9a-zA-Z])*[.][a-z]{2,3}$"
      )
    )
    .required(),
  password: Joi.string()
    .pattern(
      new RegExp("^(?=.*?[0-9])(?=.*?[#?!@$ %^&*-])(?=.*?[A-Za-z]).{8,}$")
    )
    .required(),
  name: Joi.string().required(),
  address: Joi.string().required(),
  phoneNumber: Joi.string()
    .pattern(new RegExp("^[0-9]{2,3}-[0-9]{3,4}-[0-9]{4}$"))
    .required(),
});

조금 지저분해 보이지만 회원가입할 때 스키마를 지정한 것이고, 더 좋은 방법이 있을거 같지만 내가 생각한대로 email()같은 코드를 적용했을 때 원하는 분류가 되지 않아서 직접 정규표현식을 작성해서 분류했다.

또한 로그인하고 있을 때의 상태와 관리자가 아닌 사용자가 관리자 기능에 접근했을 때 검증하는 미들웨어 도 구현했다.

사용자 관련 기능 5차

api가 잘못된 요청을 받았거나 오류가 발생했을 때 클라이언트에게 전해주는 httpCode를 세부적으로 수정했다.

회원가입, 로그인 에러 시 사용자가 입력한 데이터 명시 부분은 만약 사용자가 입력한 데이터가
email:test@test.com, password:123456이라고 가정했을 때
[email:test@test.com], [password:123456]: 유효한 데이터셋이 아닙니다.
라고 에러 메시지를 반환하도록 지정했다.

클라이언트 측에서 전화번호가 필요하다고 요청해서 추가했다.

사용자 관련 기능 6차

나는 passport 패키지를 사용해서 로그인을 구현했다. email과 password를 field로 지정하고, bcrypt 패키지로 해쉬화해서 데이터와 db에 저장된 데이터를 비교하는 알고리즘이다.
이 경우, 정보를 수정할 때 필수적으로 password를 입력해야 하는 오류가 발생한다. 무조건 해쉬화된 password를 비교해야 하기 때문이다.
이것을 해결하기 위해서 삼항 연산자를 넣었다.

const hashedPassword = password ? bcrypt.hashSync(password, 10) : password;

이렇게 구성하면 만약 password를 수정하려고 데이터를 입력받지 않아도 코드가 정상적으로 실행된다.

jwt 토큰 관련 내용은 내가 크게 오해하고 있던 사실이 있었다.
나는 사용자가 로그인한다는 정의가 db에서 데이터를 비교해서 사용자에게 권한을 주고 그것까지 관리해야한다고 생각했는데, 코치님께서 로그인 시 발급되는 토큰이 바로 로그인했다는 증거고, 그것이 만료되거나 없을 때 에러 처리 혹은 재발급 처리를 해주면 되는 것이라고 하셨다.

실제로 나는 passport-jwt 패키지를 사용해서 내가 토큰을 확인하고 자동 로그인이 되도록 구현을 했었는데, 이것을 제거하고 쿠키에 jwt토큰을 담아 프론트엔드에 넘겨주고, 토큰의 유효 검증 미들웨어를 통해 아직 쿠키안에 존재하는 토큰이 만료되지 않았다면, 창을 닫았다 켜도 기능을 여전히 수행할 수 있도록 구현했다.

즉, 쿠키와 토큰이 다르다는 개념과 백엔드가 로그인에서 어떤 기능 부분을 수행하는지 정확히 이해한 순간이었다.
README 파일에 적힌 부분은 조금 모호하게 생각할 수 있는데 쿠키가 남아있어도 자동 로그인이 되지 않지만 만약 그 안에 있는 jwt 토큰이 유효하다면 자동 로그인이 되는 것이다.

사용자 관련 기능 7차

쿠키와 토큰이 별개라는 사실을 알았으니 그 안에 담긴 헤더와 속성을 정해주었다.
쿠키가 브라우저에 남아있는 기간을 1달, 사용자가 로그인 했을 때 토큰이 만료되는 시간을 10분으로 설정했다.

또한 나는 세션으로만 user를 식별해서 req.user로 로그인했는지 하지 않았는지를 구별했지만, 로그인 했다는 것은 토큰이 발급되어 있고, 유효하다는 뜻이기 때문에 토큰을 디코딩해서 그 안에 있는 데이터를 검증했다.

연계되는 내용으로 관리자 계정을 구별하는 방법을 admin@admin.com 계정의 _id를 가지고 기능을 실행할 때마다 확인하는 방식이었는데, payload에 role을 추가해서 만약 로그인하는 계정이 admin@admin.com이라면 true 아니라면 false를 주어서 미들웨어에서 토큰을 디코딩하고 role을 확인한다.

사용자 관련 기능 8차

쿠키의 기본 옵션이 여러가지가 있는데 postman으로 구현해봤을 때 httpOnly가 기본적으로 false 값인거같아서 true로 하는 것이 보안이나 이후 측면에서 좋다고 판단해서 바꾸었다.

사용자 관련 기능 9차

2주차에서 가장 많은 변경점이 있다고 생각하는데, 처음 코드를 짤 때 passport 폴더를 따로 만들어서 경로를 지정했었는데, 이것보다는 mongodb의 연결과 passport 연결이 개념적으로 비슷하고 묶어놓았을 때의 측면이 더 좋다고 판단해서 loader라는 폴더 안에 넣어주었다.

코드 컨벤션이 제대로 이루어지지 않아서 prettier라는 확장 프로그램을 사용했고, 위에서 사용자와 관리자 라우터를 나누었다고 언급했었는데, 로그인 / 로그아웃 같은 경우는 사용자의 검증을 하기 때문에 명시적으로 라우터를 다시 나누어주었다.

프론트엔드와의 연결과 보안적인 부분을 위해 cors를 추가해주었지만, 실제로 배포할 때는 nginx를 사용해서 리버스 프록시를 해주어서 크게 신경쓸 부분은 없었다.

사용자 관련 기능 10~12차

이 부분에서 크게 문제되는 부분은 없었고, 불필요한 코드가 있다거나 세부적으로 특정 조건에서만 이상하게 작동하는 코드를 수정했다.

마무리 단계

2/8 이후에는 VM으로 배포를 하기 시작했는데, 처음 해보는 작업이라 신기하기도 하고 어렵기도 했다.

깃랩에서 코드를 clone한 후에 똑같이 실행을 해주면 되는데, ssh 인증을 마친 내 pc와는 달리 VM은 user.email, user.username등을 설정하지 않으면 clone이 되지 않아서 http로 clone한 후에 accessToken을 발급받아서 클론을 진행했다.

테스트를 진행한 후에 nginx 패키지를 글로벌로 받아서, 리버스 프록시와 프론트엔드의 build 파일을 root 경로로 지정해준다.
pm2 패키지를 글로벌로 받아서 백엔드 코드를 돌려주면 배포가 완료된다.

물론 글로 적어서 간단히 끝난걸로 보이지만 백엔드 파일만 배포했을 때는 한 번도 에러가 나지 않다가 프론트 파일을 master로 커밋하고 마무리로 배포를 할 때 알 수 없는 에러가 자꾸 발생해서 발표 전 날 새벽에 코치님께서 에러를 해결해주셨다...(감사합니다 백엔드 코치님 ㅜㅜ)

이렇게 해서 길다면 길고 짧다면 짧은 첫 프로젝트가 마무리 되었는데,
초기에 3명이었던 프론트 파트가 1명은 소통도 제대로 되지 않고, pc 고장으로 프로젝트 참여를 전혀 할 수 없게 되어서 발표 자료를 만드는 쪽으로 변경되었고, 1명은 같이 개발을 진행하다가 프로젝트 명세를 잘못 이해해서 다시 구현을 해야 했었는데, 갑자기 몸이 많이 아프셔서 2주차에는 프로젝트를 거의 진행하지 못했다.

즉, 1명만 계속해서 구현해왔다는 말인데 처음부터 인원이 없었다면 생각하면서 진행할 수 있었을 텐데 갑자기 인원이 빠져버리니 여러가지로 어려움이 많았다. 이 때 프론트 코치님이 나서서 프론트 파트를 물심양면으로 도와주시고 백엔드와의 소통 문제도 해결해주셨다!(그저 빛..)

한 가지 아쉬웠던 점은 디자인은 고사하고 발표 했을 때 프론트엔드 기능 구현이라도 된 파트는 사용자 기능과 상품 기능밖에 없었다. 주문 기능은 백엔드 개발은 완료됐지만, 발표할 때 보여주지 못해서 많이 아쉬웠다. 물론 내 파트가 아니었지만 같이 이슈에 대해 고민하고 얼마나 서로 노력했는지 알기에 더 아쉬운 부분이었다.

https://github.com/HiWoong/Palette_Shop
개인 깃허브에도 업로드해놨으니 관심있다면 그냥 둘러보는 정도로 봐주면 감사하겠다.😊

+ 추가로 마지막 코드 리뷰를 진행했다.
주신 코멘트로는,

1. 함수 명명이나 매개변수로 받는 값 자료형을 통일해라.
2. MVC 패턴과 같은 코드 구조를 뛰어넘지 마라. 뛰어넘으면 유지보수할 시 코드가 꼬이고 어려워진다.
3. 특정 단계(ex. response)에 의존하는 util함수를 만들지 마라. 만약 response가 꼭 필요하다면 사용자의 입력을 받아 처리하는 controller에서 처리해라.

등이 있었다.
말씀해주신 부분을 토대로 잊지않고 다음 프로젝트에서 더 좋은 코드를 작성할 수 있도록 해보려한다!

고생 많이 하신 백엔드, 프론트엔드 코치님과 같이 고민하고 성장해나간 백엔드 팀, 어려운 상황에도 열심히 해주신 팀장님과 프론트엔드 분들도 모두 감사했다고 전하고 싶다. 다음엔 더 좋은 기회로 더 성장한 사람으로 만나서 또 재밌는 프로젝트 하기를!

profile
방갑습니다

0개의 댓글