이 레포지토리는 원티드 프리온보딩 백엔드 코스 4차 과제를 위해 만들어졌습니다.
- 일정 : 2021년 11월 11일(목) 오후 6시 ~ 11월 13일(토) 오전 10시
팀장 박상수 | 팀원 김성연 | 팀원 최준호 |
---|---|---|
blog: Plus Ultra github: epitone |
blog: sudocorp github: SibaDoge1 |
blog: raejun92.log github: raejun92 |
유저 & 계좌생성 API, DB / Test | 입출금 API, DB / DevOps | 거래내역 API, DB |
프로젝트 회고 | 프로젝트 회고 | 프로젝트 회고 |
팀원 어유선 | 팀원 김현길 | 팀원 이동훈 |
---|---|---|
blog: Makkiato github: Makkiato |
blog: gusrlf14 github: hyunghilkim |
blog: dongjay00.log github: dongjay00 |
거래내역 API, DB | 입출금 API, DB | 유저 & 계좌생성 API, DB / Test |
프로젝트 회고 | 프로젝트 회고 | 프로젝트 회고 |
- READ.ME 작성
- 프로젝트 빌드, 자세한 실행 방법 명시
- 구현 방법과 이유에 대한 간략한 설명
- 완료된 시스템이 배포된 서버의 주소
- Swagger나 Postman을 통한 API 테스트할때 필요한 상세 방법
- 해당 과제를 진행하면서 회고 내용 블로그 포스팅
- Swagger나 Postman을 이용하여 API 테스트 가능하도록 구현
✔️ API 목록
- 거래내역 조회 API
- 입금 API
- 출금 API
[고려 사항 및 상세설명]
**✔️ 주요 고려 사항은 다음과 같습니다.**- 계좌의 잔액을 별도로 관리해야 하며, 계좌의 잔액과 거래내역의 잔액의 무결성의 보장
- DB를 설계 할때 각 칼럼의 타입과 제약
✔️ 구현하지 않아도 되는 부분은 다음과 같습니다.
- 문제와 관련되지 않은 부가적인 정보. 예를 들어 사용자 테이블의 이메일, 주소, 성별 등
- 프론트앤드 관련 부분
✔️ 제약사항은 다음과 같습니다.
- (8퍼센트가 직접 로컬에서 실행하여 테스트를 원하는 경우를 위해) 테스트의 편의성을 위해 mysql, postgresql 대신 sqllite를 사용해 주세요.
✔️ 상세설명
1) 거래내역 조회 API
거래내역 API는 다음을 만족해야 합니다.
- 계좌의 소유주만 요청 할 수 있어야 합니다.
- 거래일시에 대한 필터링이 가능해야 합니다.
- 출금, 입금만 선택해서 필터링을 할 수 있어야 합니다.
- Pagination이 필요 합니다.
- 다음 사항이 응답에 포함되어야 합니다.
- 거래일시
- 거래금액
- 잔액
- 거래종류 (출금/입금)
- 적요
2) 입금 API
입금 API는 다음을 만족해야 합니다.
- 계좌의 소유주만 요청 할 수 있어야 합니다.
3) 출금 API
출금 API는 다음을 만족해야 합니다.
- 계좌의 소유주만 요청 할 수 있어야 합니다.
- 계좌의 잔액내에서만 출금 할 수 있어야 합니다. 잔액을 넘어선 출금 요청에 대해서는 적절한 에러처리가 되어야 합니다.
4) 가산점
다음의 경우 가산점이 있습니다.
- Unit test의 구현
- Functional Test 의 구현 (입금, 조회, 출금에 대한 시나리오 테스트)
- 거래내역이 1억건을 넘어갈 때에 대한 고려
- 이를 고려하여 어떤 설계를 추가하셨는지를 README에 남겨 주세요.
- Node.js, express, Sequelize, sqlLite, 회원가입 로그인 API, 거래내역 조회 API, 입금/출금API, 계좌생성 API를 구현하였습니다.
- 인증, 인가를 위해 JWT와 쿠키를 활용했습니다.
- 코드 컨벤션, 커밋 컨벤션, Git Flow를 지켜가며 작업했습니다.
- Github Project, 마일스톤을 활용해서 백로그, 이슈 관리를 진행했습니다.
- 계층 분리를 통해 코드의 가독성을 높였습니다.
- 리팩토링을 통해 가독성을 높이고, 유지보수를 편하게 하기 위해 노력 했습니다.
- 인증방식은 JWT를 쿠키에 저장하는 방식으로 구현 하였습니다.
- 계좌 소유주만 요청 할 수 있게 구현 하였습니다.
- 계좌의 잔액 내에서만 출금 할 수 있고, 잔액을 넘어선 출금 요청에 대해서는 적절한 에러처리를 하였습니다.
- 계좌의 소유주만 요청 할 수 있게 구현하였고, Pagination기능을 구현 하였습니다.
- 거래 일시에 대한 필터링이 가능하게 구현 하였습니다.
- 출금, 입금만 선택하여 필터링 될수 있게 구현 하였습니다.
- 자바스크립트 자체 내장 Error 클래스를 상속 받아서, 커스텀 에러를 생성해서 관리했습니다.
- 구성원들의 전체 의견을 반영하여 API 명세와 DB모델을 구축 하였습니다.
- 긴밀하게 소통하여, 신속하게 문제를 인식하고, 신속하게 해결 하기 위해 노력 하였습니다.
- 전체 작업을 도메인 단위로 분리하여 구성원 간의 병목현상을 줄였습니다.
- subquery를 이용해 시스템의 자동으로 생성되는 execution plan이 아닌 의도한 대로 최적의 탐색 방법이 나오도록 했습니다. 거래내역의 특성상 온전히 나의 거래내역만 보여주면 되고, cardinality 또한 의미있게 높을 뿐더러, FK index가 되어있어서 첫 where clause는 accountNumber에 할당 했습니다.
- 다음은 입금,출금으로, 50%의 cardinality 기대값을 갖는 transactionType을 선정했습니다. 그리고 이전에 조회한 페이지에 이어서 연속한 페이지를 찾는 경우에는 이전의 탐색정보를 활용하여 높은 page 값에서의 overhead를 줄이고, 마지막으로 요청한 기간에 대해서 조건을 맞췄습니다.
- 가장 간단하게 사용할 수 있는 pagination인 limit - offset의 경우, 결국 offset 만큼의 탐색을 진행해야 하기에 page 값이 높아질수록 overhead가 커지는 문제가 있습니다..
- 그래서 이전 page의 마지막 row의 PK를 비롯한, 해당 사용자의 pagination context를 Cookie로 클라이언트에 전달했습니다.
- 해당 context는 다음 조회 요청에 돌아와서, 새 요청과 비교하여 적용할 수 있는지를 판단합니다. 다만 Cookie에 광범위한 데이터를 기록할수는 없기에, 단순히 연속적으로 1페이지, 2페이지, 3페이지 순서대로 넘어가는 경우에만 효과를 받는 점은 아쉽다고 느끼는 부분입니다.
- 실제로는 execution plan 이외에도, 어떻게 실행을 시작시킬지, 얼마만큼이 정확히 필요할지를 예측하는 것도 최적화에 있어서 중요하다고 생각합니다. 많은 DBMS에서 지원하는 기능인 Stored procedure와 Prepared statements를 사용하면 쿼리를 캐싱하는데 도움이 될거라 생각합니다.
- 다만 이번 과제에서는 시간의 부족으로 이를 적용하지는 못했습니다.
- 아무리 index가 잘 되어있다고 해도, 전체 용량이 커지면 index도 커지고, 이는 INSERT와 DELETE 과정에서 추가적인 overhead가 계속 발생할 것임을 예상했습니다.
- 이번과제에는 적용하지 못했지만 언젠가 1억건이 넘는 거래가 있다고 할때, 테이블을 분할하거나 분산저장시스템을 이용하는 것이 좋다고 생각합니다.
- 레포지토리를 clone 받거나, 압축을 해제한 후 npm install을 통해 환경 셋팅을 진행합니다.
- npm start를 통해 서버를 구동합니다.
- src 폴더에 .env 파일을 설정해서, 환경변수를 설정합니다.
- npm start로 서버를 구동시키고, npm test를 입력하면 단위 테스트가 가능합니다.
- .env설정 노션 링크
-
링크 접속불가 시 .env 파일 설정 방법
PORT= '서버의 포트' JWT_SECERT= '원하는 시크릿코드' JWT_ALGO="HS256" ADMIN_USER="[email protected]" ADMIN_PASSWORD="1234" HOST="http://localhost:4000" IS_SQLLITE=true
-
-
Postman을 활용하여 API 작동 테스트를 진행했습니다.
-
배포된 서버 주소 및 자세한 API 명세는 아래에서 확인 가능합니다.
-
을 클릭하여 웹브라우저 혹은 Postman 클라이언트에 콜렉션이 로드되면
- Variables 탭에서 서버 Host와 Port를 지정합니다. (기본값이 지정되어 있습니다.
- Variables 탭에서 테스트하는 동안 사용할 username과 password 그리고 newProjectName을 지정합니다. (기본값이 지정되어 있습니다.)
- 그후 우측 상단의 Run 버튼을 눌러 RUN ORDER 화면에 진입한 뒤 Run [Collection Name]을 클릭하면, 이상적인 상황에서의 테스트가 진행됩니다.
- 좌측의 Workspace 화면에서 해당 콜렉션과 그 요청에는 여러 이상적이지 않은 상황의 테스트에 대한 예시가 있습니다.
**유의사항** *일부 요청의 경우 JWT를 필요로합니다. JWT는 로그인 과정에서 "Set-Cookie" 헤더를 통해 클라이언트가 스스로 관리하게끔 전달됩니다.
📦src
┣ 📂bin
┃ ┗ 📜www.js
┃
┣ 📂configs
┃ ┣ 📜db.js
┃ ┣ 📜index.js
┃ ┗ 📜secretKey.js
┃
┣ 📂controllers
┃ ┣ 📜accountController.js
┃ ┣ 📜transactionController.js
┃ ┗ 📜userController.js
┃
┣ 📂globals
┃ ┣ 📜index.js
┃ ┣ 📜responseMessage.js
┃ ┣ 📜routes.js
┃ ┗ 📜statusCode.js
┃
┣ 📂libs
┃ ┣ 📜encryption.js
┃ ┗ 📜jwt.js
┃
┣ 📂middlewares
┃ ┗ 📜auth.js
┃
┣ 📂models
┃ ┣ 📜account.js
┃ ┣ 📜index.js
┃ ┣ 📜transaction.js
┃ ┗ 📜user.js
┃
┣ 📂routes
┃ ┣ 📜accountRouter.js
┃ ┣ 📜globalRouter.js
┃ ┣ 📜index.js
┃ ┗ 📜userRouter.js
┃
┣ 📂services
┃ ┣ 📜accountService.js
┃ ┣ 📜transactionService.js
┃ ┣ 📜transactionServicePrepared.js
┃ ┗ 📜userService.js
┣ 📂test
┃ ┣ 📂data
┃ ┃ ┗ 📂dto
┃ ┃ ┣ 📜postAccount.json
┃ ┃ ┣ 📜signup.json
┃ ┃ ┗ 📜token.json
┃ ┗ 📂unit
┃ ┗ 📂controllers
┃ ┣ 📂userController
┃ ┃ ┗ 📜postAccount.test.js
┃ ┣ 📂userController
┃ ┃ ┗ 📜getTransaction.test.js
┃ ┃ ┗ 📜postTransaction.test.js
┃ ┗ 📂userController
┃ ┣ 📜postToken.test.js
┃ ┗ 📜postUser.test.js
┃
┣ 📂utils
┃ ┣ 📂errors
┃ ┃ ┣ 📜commonError.js
┃ ┃ ┣ 📜errors.js
┃ ┃ ┣ 📜tokenError.js
┃ ┃ ┣ 📜transactionError.js
┃ ┃ ┗ 📜userError.js
┃ ┣ 📜index.js
┃ ┣ 📜logger.js
┃ ┗ 📜resFormatter.js
┣ 📜.env
┣ 📜.eslintrc.json
┣ 📜.gitignore
┣ 📜.prettierrc.json
┣ 📜app.js
┣ 📜database.db
┣ 📜package-lock.json
┗ 📜package.json