-
Notifications
You must be signed in to change notification settings - Fork 0
나는 왜 Swagger Decorator Builder를 만들었을까?
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를 위한 커스텀 데코레이터를 만들면 된다.
커스텀 데코레이터에 파라미터를 넘기지 않는 것이 가장 깔끔하겠지만, 컨트롤러에서 param
과 body
를 넘겨주는 것이 유지보수에 좋을 것 같아 최소한의 파라미터를 허용하고 있다.
🙋♂️ : 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() 적용 결과 |
---|---|
FE분들을 위한 API 문서를 작성하면서, 내 코드의 가독성도 챙기는 커스텀 데코레이터 완성 😎