본문 바로가기

[Project] 프로젝트 삽질기12 (feat 직렬화)

들어가며

사이드 프로젝트에서 푸시 알림을 활용한 서비스를 개발하고 있습니다. 그 과정에서 생각하고 배웠던 점들을 하나씩 작성하고자 합니다. 프로젝트를 진행하면서, 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로 설정하면, 위와 같은 결과를 얻을 수 있습니다.  

 

 

 

 

 


 

 

 

 

 

 

 

 

 

마치며

앞으로도 팀의 발전을 돕는 개발자가 되기 위해 노력하려 합니다. 팀에 필요한 부분이 무엇일지 고민하면서, 팀에 도움이 된다면, 열심히 공부해서 실무에 적용할 수 있는 개발자가 되기 위해 노력하고 싶습니다. 팀의 성장에 기여할 수 있는 개발자가 되겠습니다. 

 

 

 

 

 


 

 

 

 

 

 

출처

 

Serialization - nestjs

시리얼 라이저는 실제 응답을 보내기 전에 데이터 조작에 대한 깨끗한 추상화 계층을 제공합니다. 예를 들어, 사용자 비밀번호와 같은 민감한 데이터는 항상 최종 응답에서 제외해야 합니다. 또

jakekwak.gitbook.io

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com

 

NestJS에서 응답/요청 객체 직렬화 (Serialization) 하기

저 같은 경우에 최대한 Dto를 불변으로 만들기 위해 setter나 public 필드는 배제하는데요. 어쩔수 없이 public 필드 (혹은 public setter)를 써야하는 경우 (TypeORM의 Entity 등)를 제외하고는 무조건이다 싶

jojoldu.tistory.com

 

TypeScript 환경에서 class-transformer 적극적으로 사용하기

요즘 같이 분산 환경이 적극적으로 도입 되고 있는 시기에는 꼭 프론트엔드가 아니더라도 백엔드 환경에서도 외부의 HTTP API를 호출하는 일은 당연한 일입니다. 그래서 HTTP API (저는 Rest API라는

jojoldu.tistory.com

 

[TS] class-validator의 활용과 검증 옵션

class-validator는 joi의 Typescript 버전으로, 데코레이터를 이용해서 편리하게 오브젝트의 프로퍼티를 검증할 수 있는 라이브러리이다. class-transformer와 결합해서 웹 서버에서 들어오는 JSON body 요청을.

seungtaek-overflow.tistory.com