온보딩의 마무리 그리고 새로운 시작(NestJS & Prisma)
목차
- 주제 선정
- 요구 사항
- 참고한 문서
- DB Schema
- Project Structure
- API
- 추가로 공유할 만한 내용
- 마무리
주제 선정
사내 노션을 확인하던 중, 도서 관리 문서를 발견했고,
해당 문서를 서비스화 해서 DB 로 도서 대여 히스토리 등을 관리하면 좋겠다는 생각이 들었습니다.
요구사항
사내 노션의 도서 관리 문서에 존재하는 피쳐들을 그대로 이관할 수 있도록 구현하는 것을 최우선 순위로 하였습니다.
- 로그인 기능
- 도서 등록(구매 요청)
- 도서 상태
- 도서명
- 도서 분야
- 대여자
- 신청자
- 도서 상태값 변경
등의 피쳐들이 존재합니다.
참고한 문서
NestJS + Prisma 를 이용하여 Back-end Server 를 구현하였고,
앞으로 주로 사용하게 될, NestJS 와 Prisma 에 대한 정확한 내용을 파악하고자, 공식 문서를 참고하여 진행하였습니다.
NestJS Official Doc(https://docs.nestjs.com/)
PrismaOfficial Doc(https://www.prisma.io/docs)
DB Schema
온보딩을 진행하면서, 정해진 요구사항이나 기능이 없었기 때문에, 스스로 정한 주제를 토대로, 필요하다고 생각되는 피쳐들을 반영하기 위한 DB Schema 를 작성했습니다.
시니어분들의 피드백을 고려하여 수정사항을 반영하는 등의 작업이 있었습니다.
현재는 도서의 대여 관련 히스토리만을 기록하는BookRentalLog 테이블과, 도서의 구매 관련 히스토리만을 기록하는 BookOrderLog 테이블로 분리되어 있지만, DB Schema 설계 초기에는 BookLog 라는 테이블 하나로 설계를 했었습니다.
한 시니어 분께서는 도서 대여 히스토리만을 관리하는 중간테이블을 추가할 것을 권장하는 피드백을 해주셨고,
또다른 시니어 분께서는 도서관련 로그를 남길 때, 주문관련 로그와 대여관련 로그테이블을 분리할 것을 권장하셨습니다.
두분의 피드백을 모두 종합하여 고려해본 결과, 도서의 대여관련 로그 테이블과 주문관련 로그 테이블을 분리하여 관리하는 것이 이 서비스의 목적에 더 부합하는 것이라는 것에 동의하였고, 이렇게 진행한다면 도서 대여 히스토리만을 관리하는 중간테이블도 추가할 필요 없이 도서 대여 히스토리만을 관리하는 로그테이블로 해당 테이블이 대체될 것이라고 판단하였습니다.
따라서 로그테이블 분리를 진행했습니다.
추가로, 다수의 시니어분께서 is_deleted 필드를 제거하기를 권장하셨는데,
해당 방식은 현재 팀에서 사용하고 있지 않은 방식이므로, 저도 그 이유에 동의하여 피드백을 DB Schema에 반영하였습니다.
또한, FK 관계를 더 확실하게 명시 해주기 위하여, @relation("xxx") 이런형태의 컨벤션을 고려하였는데,
실제 프로덕트에 코드를 작성할 때는, 팀내에서는 저렇게 명시해주지 않아도 된다고 전달받았습니다.
팀의 컨벤션을 잘 파악하여 제가 작성하는 코드가 기존 코드에도 잘 녹아들 수 있도록 노력하겠습니다.
테이블들의 관계를 명시한 다이어그램입니다.
Project Structure
프로젝트의 구조는, root 인 app.module 이 최상단에 존재하며, 그 하위에 모듈 패키지들이 존재하며, 모듈 패키지 하위에는 dto 패키지 및, module, controller, service 가 존재하는 구조이고, 최종적으로는 app.module 이 하위 모듈들을 참조하는 구조입니다.
API
API 엔드포인트는 다음과 같습니다.
세부적으로 살펴보겠습니다.
- POST /auth/login
올바른 email 과 password 를 입력하여 POST 요청을 보내면,
로그인 성공응답으로 accessToken 을 발급하여 줍니다.
이후, 인증이 필요한 기능에 대한 요청시, Frontend Layer 에서 해당 accessToken 을 Authorization Header 에 함께 넣어서 요청해야합니다.
추가로, 해당 기능 요청시, 서버측에서는 회원 로그인에 대한 로그를 기록하여 관리하도록 구현했습니다. 회원 관련 피처에 대한 로그는 UserLog 테이블에 기록됩니다.
실제로 고객 CS 를 처리하는 등의 프로세스가 추가된다면, 백오피스 등에서 해당 로그를 확인할 수 있도록 한다면 더 효율적인 업무처리가 가능해질 것입니다.
- POST /user/signup
올바른 email 형식, 그리고 password, name 데이터와 함께 회원가입 요청을 보내면, 이메일이 중복되지 않는 경우, 회원가입에 성공하게됩니다. 성공응답으로 반환하도록 설정한 DTO 가 반환되며, 회원가입 성공시DB Table 에 실제 생성된 회원 정보의 고유 id 값이 함께 반환됩니다.
- GET /user/all
해당 경로로 유효한 accessToken 과 함께 요청하게되면, 회원들의 정보가 반환됩니다.
- GET /user/id
해당 경로로 유효한 accessToken 과 회원의 고유 id 값을 param으로 하여 요청하면, id 값에 해당하는 단일 회원의 정보가 응답됩니다.
- PATCH /user/id
비밀번호 변경을 원하는 회원이 유효한 accessToken 을 Authorization Header로, 그리고 해당 회원의 id 값을 param 으로 하고, 현재 비밀번호와, 변경을 원하는 비밀번호1과 2를 PATCH 요청으로 보내게 되면, 현재 비밀번호가 맞는지 검증을 진행한 후, 통과시, 변경을 원하는 비밀번호의 1과 2의 일치검사를 하여 검증에 통과하면 비밀번호 변경 성공응답을 반환합니다.
- GET /book/search?query=keyword
유효한 accessToken 과 함께 query param 으로 keyword 와 함께 요청하면,
서버에서는 미리 .env 에 설정해둔 NAVER_CLIENT_ID,NAVER_CLIENT_SECRET 를 header 에 추가하여 NaverOpenAPI 로 도서정보를 요청하게 되고, 해당 응답을 DTO 변환하여 클라이언트에 반환합니다.
https://developers.naver.com/docs/serviceapi/search/book/book.md
현재는 네이버 도서 API 에서 응답하는 이미지 URL 을 그대로 저장해두었는데,
이를 이용하여 Frontend 에서 이미지를 노출할 시, 만약 네이버 API 서버에 이슈가 발생하면, 함께 문제가 생기는 것이 당연하기에, 이를 분리하여 실제 이미지를 저장하여 서비스를 완전히 분리하는 것이 더 좋은 방법이라고 생각됩니다.
- POST /book/register
유효한 accessToken 과 함께 등록(구매)를 원하는 도서정보와 book_category 를 Body 로 포함하여 서버로 요청하면, 도서가 등록되게 됩니다. 이때, BookOrdeLog 테이블에도 로그가 남게되며, book_category 의 경우, DB Schema 에 작성했듯, Enum 이기 때문에, 특정값 이외의 값은 Frontend Layer 에서 제한하고, 정해놓은 값들 중 하나로 입력되도록 해야합니다.
- GET book/all
유효한 accessToken 과 함께 해당 경로로 요청을 보내면, 현재 DB에 등록된 도서의 정보들이 반환됩니다.
- GET book/id
유효한 accessToken 과 함께 도서의 고유 id 값을 param 으로 하여 요청하게 되면, 해당 단일 도서의 정보가 반환됩니다.
이때, 반환되는 정보중, borrower_id 는 해당 도서의 현재 대여자의 id 이며, 대여자가 없다면 null 이 반환됩니다.
request_id 는 해당 도서를 최초로 구매 요청한 회원의 id 이며, 해당 값은 변경 될 수 없습니다.
- PATCH book/id
book_status 또한 Enum 으로 정의 되어 있으며, UNRENTABLE, RENTABLE 의 의미하는 바는 각각, 대여불가능(대여중), 대여가능 입니다. 만약, RENTABLE -> UNRENTABLE 로 값이 변경될 시, borrow_id 필드에 현재 상태 변경을 요청한 회원이 id 와 name 이 함께 저장되게 됩니다.
추가로 공유할 만한 내용
DB Schema 를 작성할 때, 거의 모든 Table 에 중복되는 필드가 존재했습니다.
created_date DateTime @default(now()) // 데이터 생성일
modified_date DateTime @updatedAt // 데이터 수정일
해당 필드를 Java 의 JPA 를 사용할 때 @MappedSuperclass 어노테이션을 사용하여 BaseEntity 를 작성한뒤 해당 엔티티를 상속하여 공통 필드 작성의 중복을 제거하는 방법처럼 비슷한 방법이 Prisma 에도 있을지 서치해보았으나,
Prisma 의 경우 상속개념을 지원하지 않다는 것을 확인하고 공통 필드를 중복하여 작성하였습니다. 이 부분이 조금 아쉽게 느껴집니다.
그리고 인증관련 authModule 을 작성할 때,
제가 스스로 판단하기에는 인증이 필요하지 않은 로직보다 인증이 필수적인 로직이 대부분이라고 판단했습니다.
따라서, 인증데코레이터인 @UseGuards(JwtAuthGuard) 를 인증이 필요한 각 메서드에 작성하는 방식이 아닌,
Controller 단에 @UseGuards(JwtAuthGuard) 를 작성한 후, 인증이 필요하지 않은 특정 메서드에서만 인증을 생략하기 위해 @Public() 데코레이터를 작성하여 적용했습니다.
로직에 @Public 데코레이터가 존재하면, 인증을 생략하도록 설정하였습니다.
하지만, 이 방법은 그리 좋은 방법이 아니라는 피드백을 받았습니다.
만약 특정 기능에 대한 인증 정책이 바뀌거나, 인증 방식이 변경되면, 결국 수정이 필요하여 의미가 없어진다고 하셨고,
추후에는 현재 팀에서 사용중이 방식대로 메서드별로 @UseGuards(JwtAuthGuard) 를 적용하는 방식으로 코드를 작성해야겠습니다.
추가로 테스트 코드 작성이 미흡한 것이 아쉽습니다.
user.service 에 대하여 테스트 코드를 작성해보았는데, 아직 해당 프레임워크에 익숙치 않음에 스스로 만족할만한 테스트코드를 작성하지 못한 것이 아쉽습니다.
NestJS 의 좋은 테스트코드 예시들을 많이 참고하여 더 나은 테스트코드를 적극적으로 작성할 수 있도록 하겠습니다.
마무리
온보딩 기간동안, 개발 환경 세팅 이슈, DB Schema 설계, 인증관련 이슈 등에 대해 피드백을 해주신 모든 분들께 감사의 인사를 전합니다. 그리고 특히 그룹을 이끌어 주고 계신 CEO, CTO 분과 제가 잘 적응할 수 있도록 많은 배려를 해주시는 제 팀의 시니어분들과 팀원분들께 더욱더 감사의 인사를 전합니다.
부족한 부분은 꾸준히 공부하여 채워나갈 수 있도록 노력하겠습니다. 그리고 업무에 빠르게 적응하여 그룹과 팀에 기여하도록 하겠습니다.
마지막으로,
뛰어난 축구선수는 주변 선수들을 뛰어난 선수처럼 보이게 합니다. 예를 들면, 뛰어난 축구 선수는 최대한 많은 상황을 예측하여 어떤 패스라도 잡을 수 있고, 다시 공을 패스할 때는 다른 사람이 쉽게 받을 수 있도록 패스합니다.
이는 비단 축구선수에게만 해당하는 것이 아니라고 생각합니다.
10배 뛰어난 엔지니어는, 남들보다 10배 뛰어난 사람이 아니라, 주변 사람을 10배 뛰어나게 만드는 사람이라는 생각을 가지고, 더 나은 동료가 되도록 하겠습니다.
감사합니다.