들어가며
사이드 프로젝트에서 푸시 알림을 활용한 서비스를 개발하고 있습니다. 그 과정에서 생각하고 배웠던 점들을 하나씩 작성하고자 합니다. 프로젝트를 진행하면서, DB에서 조회한 컬럼값이 담긴 객체를 외부에 그대로 전달한다면, DB의 구조를 외부에서도 쉽게 알 수 있고, 민감한 정보가 담겨서 전달될 수 있기 때문에 보안상 대단히 좋지 못하다는 것을 알 수 있었습니다. 또한, 만약 DB에서 조회한 값이 담긴 Entity 객체를 외부에서 그대로 사용할 경우, Entity가 변경될 경우 Entity 객체를 활용하는 모든 코드의 변경이 일어날 수 있음은 더 큰 문제입니다. 그래서 이 문제를 해결하기 위해, Entity 객체를 Dto 객체로 변경하고, 최종적으로 응답을 할 때 Dto 객체를 JSON 객체로 직렬화하는 것이 필요하다는 것을 알았습니다. 이번 시간에는 직렬화에 조금 더 자세히 알아보겠습니다. 이 글은 NestJS에서 응답/요청 객체 직렬화(Serialization) 하기를 참고한 내용입니다. 링크에서 더 자세하게 내용을 살펴볼 수 있습니다.
Serialization이란?
직렬화는 실제 응답을 보내기 전에 데이터 조작에 대한 깨끗한 추상화 계층을 제공합니다. 예를 들어, 사용자 비밀번호와 같은 민감한 데이터는 항상 최종 응답에서 제외해야 합니다. 또한 특정 속성에는 추가 변환이 필요할 수 있습니다. 예를 들어 전체 데이터베이스 엔티티를 보내지 않으려는 경우, 엔티티에서 id와 name만 고르고 싶다면 나머지 프로퍼티들은 자동으로 제거해야 합니다. 하지만 모든 엔티티를 수동으로 매핑하면 많은 혼란이 생길 수 있습니다. 이런 문제를 해결하기 위해 직렬화를 사용할 수 있습니다. 그렇다면 응답/요청 객체를 어떻게 직렬화하는지 알아보겠습니다.
응답 객체 직렬화하기
원하는 응답을 전달하기 위해 직렬화를 해보겠습니다.
1. class-transformer 의존성 등록
JSON 직렬화를 위해서는 직렬화 라이브러리가 필요합니다. NestJS에서는 공식적으로 class-transformer 패키지를 사용하여 선언적이고 확장 가능한 객체 변환 방법을 제공합니다. 데코레이터 기반의 직렬화/역직렬화 라이브러리로, 본인의 NestJs package.json에 class-transformer와 reflect-metadata가 포함되어있는지 확인하고, 포함이 안되어있다면 두 패키지를 설치해서 사용해야 합니다.
npm install reflect-metadata class-transformer
- reflect-metadata는 리플렉션을 지원하는 라이브러리이며, 코드의 메타 정보(메서드/필드/변수 등)에 접근하기 위한 기술입니다.
- 대부분의 데코레이터들은 리플렉션 개념을 통해 속성을 추가하거나 제거하는 등의 행동을 할 수 있습니다.
2. 글로벌 인터셉터 등록
필요한 패키지들(class-transformer, reflect-metadata)이 모두 설치됐다면, 이제 모든 HTTP 요청에서 직렬화가 가능하도록 직렬화 인터셉터를 글로벌로 추가합니다.
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
기본적으로 ClassSerializerInterceptor 클래스에서 HTTP 응답값을 중간에서 가로채 class-transformer의 classToPlain() 함수를 호출하여 JSON 직렬화를 해서 반환합니다. 그럼 직렬화를 직접 구성해보도록 하겠습니다.
3. 테스트용 객체 생성
컨트롤러에서 다음과 같이 입력합니다.
@Get('/show')
show(): UserShowDto {
return new UserShowDto(User.signup('KilDong', 'Hong'))
}
여기서 UserShowDto 클래스는 아래와 같이 작성합니다.
export class UserShowDto {
// (1)
@Exclude() private readonly _id: number;
@Exclude() private readonly _firstName: string;
@Exclude() private readonly _lastName: string;
constructor(user: User) {
this._id = user.id;
this._firstName = user.firstName;
this._lastName = user.lastName;
}
@ApiProperty()
@Expose() // (2)
get id(): number {
return this._id;
}
@ApiProperty()
@Expose()
get firstName(): string {
return this._firstName;
}
@ApiProperty()
@Expose()
get lastName(): string {
return this._lastName;
}
}
(1) Exclude()
- 내부 멤버 변수인 _id 등을 JSON 직렬화 대상에서 제외합니다.
- class-transformer의 경우 private 변수라도 직렬화를 시킬 수 있기 때문에 노출을 원하지 않는 곳들은 모두 Exclude() 처리하는 것이 좋습니다
(2) @Expose()
- class-transformer 의 데코레이터로, 직렬화 대상 필드를 지정합니다.
- 여기서는 모든 멤버변수는 @Exclude() 로 직렬화 대상에서 제외하고, 온전히 노출에만 사용할 수 있는 getter 메소드에만 @Expose() 를 선언합니다.
이렇게 설정하고, 어떤 응답이 오는지에 대해 살펴보겠습니다.
4. 직렬화 테스트
위와 같이 설정한 후 엔드포인트를 호출하면 다음과 같은 응답이 나타납니다.
{
"id": 1,
"firstName": "Hong",
"lastName": "Kildong"
}
지금까지 응답 객체를 변경하는 방식을 알아봤다면, 요청 객체에 대한 역직렬화는 어떻게 할 수 있는지를 알아보겠습니다.
요청 객체 역직렬화하기
요청 객체의 경우엔 JSON에서 클래스 인스턴스로 변환하는 역직렬화가 필요합니다. 역직렬화를 살펴보기 전에, 왜 역직렬화가 필요한 것일까요? 이에 대해 알아보겠습니다. 만약 JSON으로 받은 요청 객체를 추가 가공해야한다면, 추가 가공 로직을 클래스 인스턴스에서 바로 활용할 수 있다는 점에서, 객체의 역할과 책임을 더욱 명확하게 만들어줄 수 있습니다. 그럼 먼저 역직렬화를 하는 방법에 대해 알아보겠습니다.
이 부분은 class-transformer 외에 class-validator가 추가로 필요합니다. 만약 class-validator가 없다면 설치해야 합니다.
npm i class-validator
1. 글로벌 파이프로 요청 객체 역직렬화 등록
class-validator는 class-transformer와 비슷하게 데코레이터 기반의 클래스 벨리데이션 라이브러리입니다. NestJS에서는 class-validator의 데코레이터를 기반으로 DTO들의 검증을 권합니다. 이를 위해서는 ValidationPipe를 사용하면 되는데, ValidationPipe에서는 추가 옵션으로 요청 객체를 역직렬화할 수 있습니다.
export function setNestApp<T extends INestApplication>(app: T): void {
...
app.useGlobalPipes(new ValidationPipe({ transform: true }));
...
}
ValidationPipe는 class-validator의 데코레이터 기반으로 벨리데이션을 지원하는 파이프입니다. 해당 파이프에 추가 옵션으로 transform 옵션을 true로 넣게 되면, 벨리데이션이 끝난 요청 객체를 실제 클래스의 인스턴스로 변환되어 Controller에서 받을 수 있게 됩니다.
이를 통해 Controller에서 클래스의 인스턴스로 변환된 요청 body를 사용할 수 있습니다. 만약 transform 요청을 안 적었다면 다음과 같이 DTO를 출력할 수 있습니다.
DTO를 출력하면 리터럴 객체가 출력되는데, plainToClass를 활용해서 리터럴 객체를 인스턴스화 시킬 수 있습니다.
하지만 ValidationPipe에서 transform을 true로 설정하면, 위와 같은 결과를 얻을 수 있습니다.
마치며
앞으로도 팀의 발전을 돕는 개발자가 되기 위해 노력하려 합니다. 팀에 필요한 부분이 무엇일지 고민하면서, 팀에 도움이 된다면, 열심히 공부해서 실무에 적용할 수 있는 개발자가 되기 위해 노력하고 싶습니다. 팀의 성장에 기여할 수 있는 개발자가 되겠습니다.
출처
'Project > 서버 개발' 카테고리의 다른 글
[Project] 프로젝트 삽질기14 (feat 큐 모니터링) (0) | 2022.03.30 |
---|---|
[Project] 프로젝트 삽질기13 (feat class-transformer) (0) | 2022.03.29 |
[Project] 프로젝트 삽질기11 (feat pgAdmin4) (0) | 2022.03.28 |
[Project] 프로젝트 삽질기10 (feat bull 공식문서 정리) (0) | 2022.03.16 |
[Project] 프로젝트 삽질기9 (feat Queue, bull) (1) | 2022.03.15 |