Project M-TOD 요약, 정리 [2019.11 ~ 2019.12]

2019. 12. 12. 18:44카테고리 없음

프로젝트 소개(Notion) 페이지

(발표 ppt, 영상, 서비스발표, 기술발표)

* 기업 협업 프로젝트로, Github repository는 공개 불가 *

 

M-TOD(Mobile-Tickets On Demand) / T3 (Team Token Ticket )

1. About

www.notion.so

프로젝트 소개 (기업 협업 프로젝트)
    콘서트 스마트티켓(전자티켓) 발매 모바일 앱

    프로젝트명 : M-TOD(Mobile-Tickets On Demand)
    포지션 : Back-end
    사용 스택 : TypeScript, Node.js, Koa, MongoDB, Mongoose, JWT, Nodemailer, AWS

    담당 파트 : JWT활용 인증시스템 구현, 메일인증 및 임시비밀번호 발급 구현, DB 및 스키마 설계,
                       API 작성, AWS-EC2 활용 server web application 배포

 

 

이번 프로젝트를 진행하며 특히 중요하게 배우고 경험한 점에 대해 소개하고 싶다.
    프로젝트 - PM역할의 중요성, 협업, 의사소통  

    ⊙ 프로젝트 - Github Work Flow 중 Commit에 관하여



 Node.js 기반 Koa 프레임워크를 이용하여 서버 구축.

   기존 Express를 사용한 경험이 있지만, 기업측에서 Koa 사용 제안.
   새로운 스택을 경험해본다는 생각으로 진행함.

   내가 겪은, 느낄 수 있던 부분에 한해서만, Express와 Koa를 비교해보고자 한다.
   Koa.js 는 공식docs의 소개를 보면 Express 디자인팀이 새로 고안한 차세대 프레임워크로
   더 작고 표현력이 강하며 API 기초가 튼튼하다고 한다.

   애초부터 promise를 도입하여 callback 보다 자유로운 에러 핸들링이 가능하다고 소개한다. 
   또한 Express에 비해 메모리를 덜 차지하고 가볍다고 한다.

   그리고 Koa는 Express와 다르게 프레임워크 자체에 미들웨어를 제외한 순수 core만 담겨있는 것이
   강점이라고 소개하지만, Express 사용경험에 비추어보면, 미들웨어 자체를 딱히 커스텀하여 쓸 일이
   거의 없기 때문에 큰 의미가 있는건가 싶었다.

   역으로 필수 미들웨어들을 import 해와야 했기 때문에 오히려 자주 사용되는 미들웨어들이
   내장되어있는 
Express가 편한점도 있었다.

   promise자체도 Express역시 문제없이 사용가능했기 때문에 특별하게 Koa의 사용이유라고
   보긴 힘들었던 것 같다.

 

 MongoDB 와 ODM인 Mongoose를 활용한 DB 구축

    MongoDB 는 document 기반의 NoSQL문의 형식이기 때문에 MySQL과 다르게 형식에서
    많이 자유로운 편이다.
고정된 스키마를 가지고있지 않다고 표현한다.

    document 는 key-value쌍으로 구성되어 있고, value는 또 다른 document가 들어갈 수 있다.
    동적 스키마를 가지고있기 때문에 같은 Collection(table)안에 다른 형태의 스키마를 가질 수 있다.

    data가 JSON의 형태로 저장되기 때문에 Query문을 사용할 때도 JSON으로 작성해야 한다.

    Mongoose는 ODM(Object Document Mapping)으로 ORM인 Sequelize와 비슷한 맥락이다.
    객체와 문서를 매칭한다는 것으로, 문서를 DB에서 조회할 때 자바스크립트 객체로
    바꿔주는 역할을 한다.

    Mongoose는 NoSQL의 특징인 talbe과 join이 없는 동적 스키마 대신 다시 정적(강제) 스키마를
    사용하는데 이는 MongoDB를 사용하다보면 스키마와 join이 필요한 경우가 생기는데 이런경우
    상당히 편리하기 때문이라고 한다.

    실제 Mongoose를 사용하면 DB에 document를 넣을때 value와 type, field를 작성하여 사용한다.

    또한 현재 Node.js에서는 Promise가 공식 비동기 API처럼 자주 사용되고 있기 때문에, Callback 기반인
    MongoDB에서 Mongoose를 사용하여 Promise를 다룰 수 있게 해준다.

    마지막으로 실제 MongoDB만 사용해본적은 없었지만, MongoDB만 사용하면 쿼리문 작성시
    JSON형식으로 작성하기 때문에 사용하기가 불편하다고 하는데 이를 Mongoose에서 제공하는
    쿼리빌더로 쉽게 쿼리문 작성이 가능하게 해준다고 한다.

 

    이번 프로젝트를 NoSQL인 MongoDB로 진행하며 느낀점을 살펴보면
    첫째로는 table의 field 추가와 삭제가 쉽고 간편했던 점과
    둘째로는 티켓 data의 형식이 객체속 객체속 배열 형태와 같이 depth가 있었기 때문에 
    document의 value 값을 자유롭고 SQL에서보다 다양한 형식의 값으로 지정해줄 수 있었던 것 같다.
   

ex)
db.Users.find({"name" : "Kim"})


Server Side 에서의 TypeScript 활용

    TypeScript 사용의 가장 큰 이유는 정적 타입을 지원하기 때문이라고 한다.

    아예 파일실행 전 컴파일 단계에서 에러를 잡아주고, type들이 명확하게 선언되어 있어
    차후에 존재할 혼선을 방지할 수 있다는 점이다. 

    결과적으로 코드 가독성을 높일 수 있고, 예측이 가능하도록 하여 쉽게 디버깅을 할 수 있게 해준다.
    아직 크게 경험해보진 못했지만 이는 엄청난 장점이라고 한다.

    실제 서버 구축시 TypeScript를 도입하며 경험했던 점은, 컴파일러(tsc)를 통해 .ts파일을 .js파일로
    Transpilling을 해주는 과정, 변수등의 중복 선언 방지 혹은 ","를 빠뜨린 경우 등 문법적인 에러를
    실행전 잡아주는 것이었다.

    이번 프로젝트 중 서버 구축과정에서 딱히 type을 지정해 주어야 할 경우는 없었는데, 스키마 작성같은
    경우에도 Mongoose를 사용하며 type을 명시해 주었기 때문이었다.

    하지만 실질적으로 파일실행 전 오류를 포착할 수 있게 해주는 점은 정말 좋은점이라고 한다.

 

 AWS-EC2 사용과 빠른 배포(Deploy)의 장점

    아마존 서비스를 이용하여 서버 배포를 진행해 보았다. 굳이 아마존 서비스를 선택한 이유는 딱히 없었고
    가장 유명한 서비스 였기에 사용해 보았다. 

    S3버킷은 배포는 해보지 않았고 배포 전 설정까지만 경험해 보았고, RDS는 사용해보지 않았다. 
    주로 사용한 것은 EC2였고 EC2에 서버를 배포하고 DB도 설치하여 사용하였다.

    실제 유저사용 경험은 없었지만, 프로젝트 내부 테스트를 진행하면서 서버의 빠른배포에 대한 장점을
    명확하게 느껴볼 수 있었다. 

    프로덕트 개발에 있어서 정말 중요한 점은 사용자 관점에서의 개발이다.
    결국 제품은 개발자가 아닌 유저가 사용하는 것이기 때문이다. 

    배포 후 개발과정의 API문서에 따른 fetch 요청의 내부 테스트 과정에서 비록 큰 문제는 아니었지만,
    보안상 중요한 이슈였고, 서버 구축 당시에는 미쳐 생각하지 못했던 문제점을 발견할 수 있었고 덕분에
    문제를 미리 해결할 수 있었다. 

    메일 인증부분에서의 오류였는데 수정 전과 후를 비교해 보면,

수정 전
//메일인증 확인 및 차단.
let checkEmail = await User.findOne({
  email_verified: true
});

수정 후
//메일인증 확인 및 차단.
let checkEmail = await User.findOne({
  email: ctx.request.body.email,
  password: ctx.request.body.password,
  email_verified: true
});

    수정 전에는 해당 유저의 정보가 아닌, 메일인증이 된 앞서 등록된 유저의 정보가 잘못 불려왔었다.

    조건을 추가하여 해당 유저만을 선별할 수 있도록 수정하였던 문제였지만, 이런 유저 사용경험과,
    피드백이 없었다면 미쳐 생각하지 못하여 중요한 순간에 문제가 발생할 수 있었던 오류였다.

    이런 사건을 바탕으로 User Experience와 그 피드백이 얼마나 중요한 가치인지 배울 수 있었다.

 

 JWT(Json Web Token) 적용, Session과 token의 차이.

    ⊙ Session 방식

        Session 방식은 사용자가 로그인을 하면, DB에 저장된 값과 비교하여 사용자를 확인한 후에
        고유 ID값을 부여하고 세션 저장소에 저장한 후 이와 연동되는 세션ID를 부여한다. 

        유저는 서버에서 세션ID를 받아 쿠키에 저장하고, 이후 요청시마다 쿠키를 헤더에 실어 보내고
         서버는 이를 비교, 인증하여 응답을 준다.

        이 때 서버는 세션에 로그인이 되었다는 정보를 저장해 두고 있어야 하는데 이런 과정을
        서버와 클라이언트 사이의 연결이 활성화 된 상태, Stateful 이라고 표현한다.

    

    Token 방식

        Token 방식은 반대로 Stateless 라고 표현한다. Session과 달리 서버에서 상태를
        유지할 필요가 없다는 의미이다.

        상태정보를 저장하지 않는다면 서버는 클라이언트의 요청만으로 작업을 처리하게 되는데
        이런 경우 서버와 클라이언트 사이에 연결고리가 없기 때문에 서버의 확장성이 높아진다. 

        이는 각 유저마다 지속적으로 연결고리가 필요가 없기 때문에 해당 요청을 다른 서버에서
        받아 서비스가 가능하게 되는 것이다.

        토큰 방식은 사용자가 로그인을 하면, DB에 저장된 값과 비교하여 사용자를 확인한 후에
        토큰의 Payload에 식별 가능한 정보를 입력하고, 유효기간을 설정하여 암호화를 거쳐 토큰을 
        발급하게 되고, 이번 프로젝트에서는 발급된 토큰을 쿠키에 담아 전송해 주었다.

        쿠키에 발급하게 되면, 쿠키는 자동적으로 요청시 헤더에 포함되어 전송되기 때문에 따로
        저장 및 처리가 필요가 없다는 장점이 있다. 

        이렇게 발급된 토큰을 사용자에게 보내주고, 요청시 다시 서버에서 암호화 된 토큰을 받으면,

        이를 복호화 하여 유효기간을 체크하고, Payload를 decode하여 일치하는 데이터를 
        가져올 수 있게 된다.

 

    Session과 비교한 Token의 장점
        토큰은 발급 후 검증의 과정만 거치지만, 세션은 별도의 저장소 관리가 필요하다.
        Stateless 한 서버의 큰 장점으로 서버의 확장과 유지, 보수에 유리하다.

        위에서 말한 확장성이란, 토큰 기반의 다른 서버, 인증시스템 접근이 가능하다는 것인데,
        구글 등의 다른 소셜 로그인 기능을 위해서 토큰의 사용이 필요하다.
        

응답 데이터 형식과 에러 핸들링

    서버에서 받은 요청에 대한 응답을 status 설정없이 body에 값만 넣어서 보내줄 경우, 프론트단에서
    해당 요청에 대하여 각 상황별로 분기하여 사용하기 힘들다는 피드백을 받았다.

    이에 성공, 실패, 에러 별로 status를 설정해주어 클라이언트에서 응답을 받을 때 status로 분기하여
    각 상황에 맞게 사용할 수 있도록 개선하였다.

 

    당연한 것이지만 그동안 모호하게 알고 있던 사항 중에 전송시 data의 형식에 관한 것이 있었다.

    서버에서 받은 요청에 대한 응답(주로 실패의 경우 응답)을 보통 text로 많이 주었는데,
    이 경우 클라이언트 Fetch 요청의 응답을 받는 .then() 부분에서 res.json()으로 받을 경우 
    응답을 제대로 볼 수 없었다.

    이런 경우는 서버 응답 형식과 클라이언트의 Fetch 요청의 응답 받는 부분을 일치시켜 주어야 했다.

    서버에서 text로 응답을 보낸다면 클라이언트에서는 res.text()의 형식으로 받아야 하고,
    배열, 객체등의 json 형식으로 보낼 경우에는 res.json()의 형태로 받아야 한다.

    정말 기본적인 것이고, 당연한 것이었지만 이런 경험을 통해 그동안 모호하게 알고 있던부분을
    체크하고 넘어갈 수 있었다.