Swagger 도입으로 인한 중복 코드를 추상화하기

이동엽·2023년 5월 9일
5

spring

목록 보기
4/16

개요

우리 팀은 프로젝트의 API 문서를 노션으로 관리하고 있다.
API 설계도 보러가기


이 방식은 여러 단점들을 가지고 있다.

  1. API 스펙들이 바뀌면 수기로 노션에 수정을 해주어야 한다.
    → 이 과정에서 실수를 유발할 수 있다!
  2. 노션에 기록된 API 문서는 공식화된 것이 아니다.
    → 이 API가 존재하긴 한가?

따라서, 신뢰할 수 있고 API를 자동으로 문서화해주는 도구들을 사용해보려 한다.



발단

여러 도구들을 찾아보던 찰나, Spring Rest DocsSwagger 를 알게 되었고 그 둘을 비교하면 아래와 같다.

Spring Rest DocsSwagger
장점제품 코드에 영향이 없다.API를 테스트해볼 수 있는 화면을 제공한다.
테스트가 성공해야 문서가 작성된다.적용하기 쉽다.
단점적용하기 어렵다.제품 코드에 어노테이션이 침범한다.
테스트가 성공해야 문서가 작성된다.제품 코드와 동기화가 안될 수 있다.


우리에게 적합한 방식은 무엇일까?

우리의 목적은 API 문서를 제공하는 것이였다.

물론 테스트를 작성하고 성공해야만 문서가 작성되는 Spring Rest Docs이 신뢰성이 높을 순 있지만,
어차피 Rest Docs도 단점은 존재한다.

따라서 당장 적용해보고 싶은 마음이 컸기에 적용하기 쉬운 Swagger를 적용해보았다.



그 외 결정해야 할 것들

  1. Junit vs Spock
  2. MockMvc(@WebMvcTest) vs RestAssured(@SpringBootTest)
  3. AsciiDoc vs Markdown

우리 팀은.. 당장 적용해보고 싶은 마음이 컸기에 평소에 쓰던 방식을 따라가기로 했다.

→ JUnit & RestAssured & Markdown!



Swagger 적용하기

아래 블로그를 참고했다.

SpringBoot SpringDoc(OpenAPI)을 이용한 Swagger 적용



적용 전의 우리팀의 제품 코드

위 사진에서 주황색 박스 부분이 Swagger 를 도입하며, 코드에 들어간 부분들이다.

컨트롤러 선언부에 @Tag 는 어쩔 수 없다지만, @Operation@ApiResponses의 중복 코드가 매우 많다.



💡 직접 커스텀 애너테이션을 만들어 이를 대체하면 어떨까?

공통된 부분들을 찾아보자.

  1. @Operation
  2. @ApiResponses 와 내부에 Error에 해당하는 @ApiResponse

API 별로 달라야 하는 부분들을 찾아보자.

  1. @Operation의 summary 속성
  2. @ApiResponses 와 내부에 성공 시에 해당하는 @ApiResponse 의 implementation 속성


💡 커스텀 애너테이션 만들기

처음에 원했던 사용방식은 아래와 같다.

@CustomSwaggerAPI(summary = "회원 로그인", implementation = TestExample.class)

//성공 시에 implementation에는 TestExample.class가 들어가고,
//실패 시에 implementation에는 Error.class가 자동으로 들어가길 원했다.

다만 위 방식처럼 한 어노테이션 내에서 implementation에 두 클래스를 각각 경우를 따져 넣는건 불가능하다.

→ 그래서 성공 시에 적용되는 어노테이션과 실패 시에 적용되는 어노테이션을 구분하기로 했다!



성공 시에 사용되는 애너테이션

@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Operation
@ApiResponses(value = {
        @ApiResponse(responseCode = "200")
})
public @interface SwaggerApi {
    String summary() default "";
    Class<?> implementation() default Void.class;
}

이 애너테이션을 잘 들여보면, @Operation 을 내장하도록 하여 summary 속성을 지정하도록 하였다.

또한 implementation 속성에 들어갈 클래스를 받도록 필드를 선언해놓았다.



실패 시에 사용되는 애너테이션

@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@ApiResponses(value = {
        @ApiResponse(
                responseCode = "400",
                content = @Content(schema = @Schema(implementation = Error.class))),
        @ApiResponse(
                responseCode = "401",
                content = @Content(schema = @Schema(implementation = Error.class))),
        @ApiResponse(
                responseCode = "403",
                content = @Content(schema = @Schema(implementation = Error.class)))
})
public @interface SwaggerApiFailWithAuth {
}

각각의 에러 종류에 따라 implentation에 Error.class 가 기본으로 들어가도록 선언해놓았다.



커스텀 애너테이션 사용 결과

이전 코드와 비교했을 때 훨씬 중복 범위를 줄이고, 가독성은 그대로 가져간 것을 확인할 수 있다!



마무리

여러 사람들이 Spring Rest Docs와 Swagger의 장단점을 비교하여 둘 중 하나만 사용하는 것이 아닌,
각각의 장점들만 가져와 같이 사용하는 경우들도 있다고 한다.


시간이 난다면 아래 블로그 등을 참고하여도 좋을 것 같다.

SwaggerUI + Spring REST Docs 함께 사용하기(feat. Rest Assured)

profile
백엔드 개발자로 등 따숩고 배 부르게 되는 그 날까지

0개의 댓글