Skip to content

나는 왜 Swagger Decorator Builder를 만들었을까?

KimYujeong edited this page Dec 22, 2023 · 8 revisions

누구를 위한 Swagger인가

Swagger는 API 문서를 간편하게 작성하도록 돕지만, 너무 많은 데코레이터들은 오히려 코드의 가독성을 해친다. FE분을 위해 작성한 Swagger 데코레이터들이 막상 BE 본인의 코드를 망치는 아이러니한 상황인 것이다. 😂

😑 : API 문서가 필요한 건 FE분들인데 왜 제 코드가 더러워져야 하죠?

그렇다고 Swagger 데코레이터를 제거해버리면 별도의 문서화 작업이 필요하고, FE분들과 사이도 원만하지 못하게(?) 된다. 내가 작성한 비즈니스 로직을 어지럽히지 않으면서 Swagger 데코레이터를 작성할 방법이 없을까 고민하다가 ⭐커스텀 데코레이터⭐를 만들기로 결정했다.


코드 중복을 최소화한 데코레이터

커스텀 데코레이터는 nest 공식 문서를 참고하여 간단하게 만들 수 있다. 👍

하지만 데코레이터마다 applyDecorators()의 파라미터로 필요한 데코레이터들을 넘겨주면 컨트롤러만 깔끔해졌을 뿐, 데코레이터 파일의 코드가 복잡해진다.

즉, 하나의 데코레이터마다 아래와 같은 코드가 필요하다. 🤯

🙋‍♀️ : 일종의 돌려막기 아닌가요?

return applyDecorators(
  ApiOperation(...),
  ApiParam(...),
  ApiBody(...),
  ApiBadRequestResponse(...),
  ApiUnauthorizedResponse(...),
  ApiForbiddenResponse(...),
  ApiInternalServerErrorResponse(...),
);

데코레이터마다 중복되는 코드를 줄일 방법은 없을까? 🤔

코드 중복을 줄이기 위해서는 기본적인 @ApiResponse()들을 ⭐자동으로⭐ 만들어주면 된다. 대부분의 요청은 200, 401, 403, 404, 500 응답을 보내기 때문이다.

(프로젝트 경험이 거의 없다는 점을 고려부탁드립니다. 내 말이 틀렸을 수도,, 내가 무지했을 수도,,)

ex. UPDATE /cats/:id

- 200 : id에 해당하는 고양이 수정 완료
- 401 : 인증받지 않은 사용자
- 403 : 권한이 없는 사용자
- 404 : id에 해당하는 고양이가 존재하지 않음
- 500 : 서버 에러

빌더 패턴을 사용한 이유

정말 많은 디자인 패턴들이 있는데 나는 왜 하필 빌더 패턴을 사용했을까?

앞서 말했듯이 코드 중복을 최소화하기 위해 기본적인 @ApiResponse()들을 자동으로 만들어줬다. 하지만 API별로 자동으로 만들어준 데코레이터가 필요하지 않을 수 있고, 다른 응답 데코레이터가 추가적으로 필요할 수도 있다.

🙋‍♂️ : 해당 메서드는 POST라 200번이 아니라 201번이 필요한데요?
🙋‍♀️ : 이 데코레이터엔 401번 응답이 필요없는데요?

기본 데코레이터의 수정을 허용하기 위해 빌더 패턴을 적용했다.

빌더 패턴

- 객체 생성에 관한 디자인 패턴
- 복잡한 객체를 단계적으로 생성
- 객체 생성의 유연성을 높이고, 코드의 가독성을 향상시킴

빌더 패턴을 적용함으로써 build()가 나오기 전까지 계속해서 데코레이터를 수정할 수 있다. 😎

// status에 해당하는 @ApiResponse() 추가
add(response: SwaggerResponse): SwaggerDecoratorBuilder {
  this.response.set(response.status, this.makeApiResponse(response));
  return this;
}

// status에 해당하는 @ApiResponse() 제거
remove(status: ResponseStatus): SwaggerDecoratorBuilder {
  this.response.delete(status);
  return this;
}

// 완성된 커스텀 데코레이터 반환
build() {
  // 중략
  return applyDecorators(...decorators);
}

데코레이터 파일을 작성해보자

이제 SwaggerDecoratorBuilder로 Swagger를 위한 커스텀 데코레이터를 만들면 된다.

커스텀 데코레이터에 파라미터를 넘기지 않는 것이 가장 깔끔하겠지만, 컨트롤러에서 parambody를 넘겨주는 것이 유지보수에 좋을 것 같아 최소한의 파라미터를 허용하고 있다.

🙋‍♂️ : param이나 body가 변경되면 데코레이터 파일을 찾아가서 수정해야 하나요? 너무 번거로운 것 같아요.

target@ApiOperation()을 작성하는 데 사용되는 문자열로 리소스 대상을 의미한다. 내가 생성/수정/조회/삭제하려는 대상이 고양이인 경우, target은 고양이가 된다. target만 넘겨주면 내부 로직에 의해 자동으로 @ApiOperation()summary를 작성해준다. 👍

// src/cats/cats.decorator.ts

export const UpdateCatDecorator = (
  target: string,
  param: SwaggerParam,
  body: SwaggerBody,
) =>
  new SwaggerDecoratorBuilder(target, 'PATCH')
    .setParam(param)
    .setBody(body)
    .add({ status: 403, description: 'Forbidden - Unauthorized User' }) // 403 응답 오버라이드 가능
    .build(); // 완성된 데코레이터 반환
// src/cats/cats.controller.ts

  @Patch(':id')
  @UpdateCatDecorator(
    'Cat',
    { type: 'uuid', name: 'id', description: 'identifier of cat' },
    { type: UpdateCatDto, description: 'update dto of cat' },
  )
  update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {}

결과물

target을 바탕으로 자동으로 @ApiOperation() 생성 setParam(), setBody(), add() 적용 결과
api operation patch result

FE분들을 위한 API 문서를 작성하면서, 내 코드의 가독성도 챙기는 커스텀 데코레이터 완성 😎