백엔드 개발 포트폴리오 | 중고 거래 서비스

내일배움캠프 4기 웹 개발 과정 Node.js 트랙 수료생 최종 프로젝트 '냐옹상회'을 소개합니다.
Aug 16, 2023
백엔드 개발 포트폴리오 | 중고 거래 서비스
냐옹상회 서비스 소개
💡
가격으로만 소통하자 Try your Deal! 국내 중고거래 시장은 점점 커지고 있습니다. 커져가는 만큼 많은 불편한 점들이 생겨나며 그 중 하나는 가격협상을 하며 얻는 스트레스라고 생각을 합니다. 그래서 저희는 가격을 협상하는데 의사소통을 없애보려고 했습니다. 의사소통 없이 오직 가격으로만 결정하자 그것이 저희가 서비스를 기획하게된 계기 입니다.
테스트용 ID : test01@gmail.com 테스트용 : PW : 1234
관리자 : https://5gnunfleamarket.shop/admin/login
id : admin1 pw : admin1

🛠
아키텍처
로컬용
notion image
배포용
notion image

‼️
기술적 의사 결정
onclick 이벤트 시 서버와 통신할 도구 선택
  • onclick 이벤트를 통해서 추가로 서버에 데이터를 요청 할 HTTP 통신 도구 선택 axios, ajax, fetch함수 중에 하나를 선택해서 사용하기로 결정
  • ajax는 JQuery를 사용해야 하는 단점과 fetch는 json으로 data를 변환해야하는 추가 작업이 필요한 단점으로 위의 axios를 사용하기로 결정했습니다.
일반 유저와 Admin의 인증 차이
  • 일반 유저는 관리자 페이지에 들어갈 수 없어야 됩니다.
  • NestJS에서 Guard를 기본적으로 제공합니다. 가드는 Passport-strategy 전략을 실행해주는 역할을 해줍니다.
  • 유저와 관리자가 각각 부여받는 엑세스 토큰에는 다른 정보들이 담깁니다. 유저는 리프레시 토큰 또한 부여받습니다. 따라서 유저와 관리자는 각각의 다른 Passport-strategy 전략을 취해야 합니다.
  • 그 결과 일반 유저와 관리자가 인증을 거치는 Guard가 분리됩니다.
  • 일반 유저는 로그인을 하더라도 관리자가 쓰는 Guard에 막혀 관리자 페이지에 들어갈 권한이 안 주어집니다.
데이터 일관성을 위해 NestJS 에서 트랜잭션 사용
  • 사용자가 제품의 ‘찜하기’를 업데이트 하기 위한 접근 방식을 고민했습니다. IF문을 사용하지않고 트랜잭션을 사용한 이유
  • 사용자가 ‘찜하기’를 누르면 likeRepository 에서 ‘찜하기’를 하였는지 확인합니다likeRepository 에 해당 자료가 없으면 저장 후 productRepository 의 ‘likes’컬럼을 +1 update 해줍니다. ikeRepository 에 해당자료가 없으면 반대작업을 합니다. ikeRepository , productRepository 두개의 데이터 소스가 업데이트 되기 때문에 일관성과 무결성을 보장해야됩니다.
  • 고려된 옵션
    • IF문을 사용하여 업데이트 트랜잭션을 사용하여 업데이트
  • 평가 기준
    • 데이터 일관성 및 무결성 안전성
  • 옵션 평가
    • 트랜잭션 데이터 일관성과 무결성을 보장한다. 오류 발생 시 데이터 변경사항을 롤백하는 오류처리를 해준다.
    • IF문 조건부 논리로 처리하는 비교적 쉬운 방법이다. 트랜잭션 수준의 안정성을 제공하지 않는다.
  • 결론
    • 다중 데이터를 처리하는 ‘찜하기’ 기능에서는 트랜잭션을 사용하는것이 좋은방법이다.
ECS
  • 배포시에 이때까지 해오던 EC2 배포에 불편함을 느꼈습니다.
  • 서버에 관한 세팅도 하나에서 열까지 다 세팅해주어야 하고 심지어 서버도 git clone 혹은 FTP 서버를 통해 전송해주어야 서버를 띄울 수 있었습니다.
  • 저희 팀은 이 플랫폼이 서비스 된다고 가정했을 때, 이 서버가 컨테이너화 되어 트래픽에 따라 자연스럽게 오케스트레이션 되는 기능을 찾게 되었고, Github Actions, ECS Fargate CI/CD를 통해 트래픽에 따라 유연하게 AutoScaling되는 클라우드 서버를 구축하였습니다.
  • 또한 ECS를 사용함으로써 AWS CodeDeploy를 통한 다양한 배포 전략(롤링, 카나리, 블루/그린)에도 대응이 가능하게 되었습니다.
Lambda
  • 저희 팀의 서버에는 거래 완료시 구매자에게 거래가 성립되었다는 문자를 보내는 함수가 존재합니다.
  • 이는 구매자와 판매자간 거래 성립시에만 실행되는 호출되어 코드가 실행되는데 Lambda는 서버리스 컴퓨팅 서비스로, 함수가 호출될 때만 코드를 실행시키기 때문에 특정 시간 혹은 기간에만 동작하는 서비스를 만들 때 유용합니다.
  • 서버리스 컴퓨팅 환경의 개발자는 이 서버에 대한 트래픽, 개수, 사양등을 고려할 필요 없이 온전히 코드를 집중할 수 있게되는데 저희 팀은 먼저 서버리스 컴퓨팅이 어떻게 이루어지는지 체험하고자 먼저 문자 발송 로직을 AWS Lambda Function으로 구현하게 되었습니다.

⚠️
트러블 슈팅
파일을 읽을 때 상대 경로 문제
  1. 문제 설명: 절대 경로("/js") 대신 상대 경로("js")를 사용했기 때문에 “public”폴더의 ”/js/header.js" 파일을 읽는 데 문제가 발생 해당 기능의 컨트롤러의 경로가 자꾸 앞에 붙는 문제가 발생하였습니다 문제 경로 : localhost:3000/api/orders/js/header.js 원하는 경로 : localhost:3000/js/header.js
  1. 재현 단계: 문제를 재현하기 위해 상대 경로("js")와 접두사 경로("api/orders/js/header.js")를 사용하여 "public/js/header.js" 파일에 액세스하려고 했습니다.
  1. 예상 동작: "public”폴더의 “/js/header.js" 파일에 접근할 수 있다고 생각
  1. 실제 행동: 상대 경로를 사용할 때 파일을 찾을 수 없었고 경로도 작동하지 않았습니다.
  1. 문제 해결 단계: - 모든 @Controller() 요청메소드를 어떻게 없앨까? 에 대한 고민 - 잘못된 생각을 인지하고 경로에 대해 검색 - 슬래시("/")로 시작하는 절대 경로를 사용하는 것이 파일을 참조하는 올바른 방법임을 깨달았습니다.
Docker 컨테이너에 올라간 MySQL이 무한 재시작 하는 버그
  1. 문제 설명:
    1. docker compose -up을 통해 컨테이너에 올라간 MySQL이 정상 작동되지않는 문제 발견
  1. 원인 조사:
    1. docker 파일에 있는 MySQL 관련 코드를 검색
  1. 원인 파악:
    1. ./.docker/mysql/conf.d:/etc/mysql/conf.d 코드가 문제를 일으키고 있었다. 저희 docker 파일에는 설정 내용이 없는데 설정 파일을 생성해서 생기는 문제였다.
  1. 해결:
    1. 해당 코드를 지우고 docker compose -up을 다시 하니 해결되었다.
파일을 읽을 때 상대 경로 문제
  1. 문제 설명: 절대 경로("/js") 대신 상대 경로("js")를 사용했기 때문에 “public”폴더의 ”/js/header.js" 파일을 읽는 데 문제가 발생 해당 기능의 컨트롤러의 경로가 자꾸 앞에 붙는 문제가 발생하였습니다 문제 경로 : localhost:3000/api/orders/js/header.js 원하는 경로 : localhost:3000/js/header.js
  1. 재현 단계: 문제를 재현하기 위해 상대 경로("js")와 접두사 경로("api/orders/js/header.js")를 사용하여 "public/js/header.js" 파일에 액세스하려고 했습니다.
  1. 예상 동작: "public”폴더의 “/js/header.js" 파일에 접근할 수 있다고 생각
  1. 실제 행동: 상대 경로를 사용할 때 파일을 찾을 수 없었고 경로도 작동하지 않았습니다.
  1. 문제 해결 단계: - 모든 @Controller() 요청메소드를 어떻게 없앨까? 에 대한 고민 - 잘못된 생각을 인지하고 경로에 대해 검색 - 슬래시("/")로 시작하는 절대 경로를 사용하는 것이 파일을 참조하는 올바른 방법임을 깨달았습니다.
PickType 상위 객체 상속
  1. 문제설명:
    1. 처음에는 PickType을 쓰지 않고 DTO를 만들었었습니다.
      Create,Read,Update,Delete 모두 DTO가 필요할 경우 DTO를 각각 만들어야 하는데 그렇게되면 코드가 길어지고, 타입을 재활용 할 수 없어집니다.
  1. 문제 해결 단계:
    1. 공식문서를 찾아보니 4개의 Mapped Types중 PickType의 존재를 알게되었고 가장 적합하다고 생각한 PickType을 선택하게 되었습니다.
      동일한 엔티티를 타입으로 재활용 하고, PickType으로 지정함으로써 엔티티 변화를 빌드 시점에서 인지할 수 있어 예상치 못한 에러를 방지할 수 있게 되었습니다.
Node.js의 CACHE_MODULE
  1. 문제 설명:
    1. 모듈을 쓸때는 의존성 / 특징들을 고려하여 사용하여야 합니다.
      처음 메일 인증번호 발송을 구현할때 옵션으로 TTL을 주지 않았었습니다.
      로그에도 찍히고 정상 인증은 되는데 값이 Redis에는 담기지 않는 이슈가 있었습니다.
  1. 문제 해결 단계:
    1. 돌아가지 않는다고 수정하면서 여러 설정을 forRoot를 사용하여 전역으로 적용시켰었는데 전역으로 적용된 설정이 Redis를 사용하는 CACHE_MODULE이 아닌 Node.js 자체의 CACHE_MODULE을 사용하게 만들었습니다.
      또한 .set 메서드를 사용할 때 옵션에 TTL을 적용시키지 않으면 자체 CACHE_MODULE을 사용한다는것을 깨달았습니다.
Validate 함수 리팩토링
  1. 문제 설명:
    1. JWT Passport Strategy쪽의 Validate 함수는 처음 구현 당시 TypeORM의 fineOne 메서드를 사용하여 유저를 찾아 검증을 진행하였습니다. 하지만 요청이 잦은 미들웨어에서 굳이 db까지 엑세스를 하는게 좋은 방법일까 라는 의문을 가지게 되었습니다.
  1. 문제 해결 단계:
    1. 결과적으로 JWT Decode를 통해 payload에서 유저 정보를 찾아 리턴하는 방식으로 DB I/O를 줄이게 되었습니다.
Share article
Subscribe to our newsletter

내일배움캠프 블로그