서론
프로세스간 데이터 전달을 위해서 DTO(Data Transfer Object) 사용한다. NestJS에서도 MVC layer간의 데이터 전달이나 request 요청을 받을 때도 DTO를 사용할 수 있다.
이때 한 Domain에 대해서, 여러 layer별로 dto는 조금씩 사용되는데, 모든 dto를 각각의 객체로 관리하는 건 어려운 일이다.
한 예시로 Register DTO와 Login DTO를 생각해보자.
// register.dto.ts
export class RegisterDto {
email!: string;
password!: string;
username!: string;
}
// login.dto.ts
export class LoginDto {
email!: string;
password!: string;
}
같은 User 도메인 내에 속하지만 로그인과 회원가입을 위해 필요한 dto는 다르다. 하지만 email과 password라는 공통적인 필드가 생기고, 필드와 DTO가 늘어가면 관리하기가 어려워진다.
문제상황
swagger를 쓰면서 문제는 더 커졌다. NestJS에서는 REST API 문서화를 쉽게할 수 있도록 swagger 사용을 공식적으로 지원한다. *github : https://github.com/nestjs/swagger
// login.dto.ts
export class LoginDto {
@ApiProperty({ example: 'example@kakao.com' })
email!: string;
@ApiProperty({ example: 'querty1234' })
password!: string;
}
위와 같이 DTO에 @ApiProperty 데코레이터를 추가해서 swagger docs에서, 내 API를 써볼 유저(다른 개발자)에게 보여줄 example이나 require여부 등을 표현할 수 있다.
문제는 모든 DTO에 각각 Property를 관리하기가 어렵다는 점이다. 그래서 DTO를 정의할 때 Entity 혹은 다른 DTO를 상속받도록하고, root에만 ApiProperty를 정의하여 재사용할 수 있도록 해야한다.
해결방법
NestJS docs에는 Mapped Types라는 이름으로 방법을 소개하고 있다.
* 참고 : Mapped Types - OpenAPI | NestJS - A progressive Node.js framework
// @nestjs/swagger 內
export * from './intersection-type.helper';
export * from './omit-type.helper';
export * from './partial-type.helper';
export * from './pick-type.helper';
Intersection-type, omit-type, partial-type, pick-type을 지원한다.
Partial : 부모의 모든 필드 optional로 상속
Pick : 특정 필드만 상속
Omit : 특정 필드만 빼고 상속
Intersection : 두 부모의 필드를 상속
export class UpdateCatAgeDto extends PickType(CreateCatDto, ['age'] as const) {}
위와 같은 방식으로 원하는 DTO에서 필요한 type만 받아와서 새로운 DTO를 구성할 수 있다.
@nestjs/swagger VS @nestjs/mapped-types
코드를 짜다보니 '대체 왜 이 PickType이라는 함수가 swagger 내장 함수일지' 궁금했다.
생각해보면 일부 type만 pick해오는 단독 라이브러리가 있을법한데 말이다.
아니나 다를까 @nestjs/mapped-types라는 라이브러리도 존재했다.
// #1
import { PickType } from '@nestjs/mapped-types';
// #2
import { PickType } from '@nestjs/swagger';
그럼 대체 두 라이브러리의 차이점은 무엇일까?
본인은 mapped-types를 써서 한번 털려보고서야 깨달음을 얻었다... 미리 docs를 잘봤어야하는데
바로 DTO를 상속받아올 때 ApiProperty 데커레이터까지 같이 상속받아올 수 있냐는 문제이다.
만약 import { PickType } from '@nestjs/mapped-types'; 를 사용한다면 아래와 같이 Property에 대한 description을 불러올 수 없는 문제가 발생해서 swagger를 원활하게 쓸 수 없게 된다.
블로그를 쓰면서 추가로 조사해보니 반대로 @nestjs/swagger 썼을 때, 제한되는 부분이 있을 수도 있었는데, PartialType을 쓸때는 모든 Field에 ApiProperty를 추가해야만 Optional로 불러올 수 있다는 점이었다.
*참고 : https://github.com/nestjs/swagger/issues/1043#issuecomment-727855261
각각 필요한 상황에 맞춰 라이브러리를 쓸 수 있어야겠다.