Springdoc OpenAPI 알아보기

spaghetti·2025년 4월 24일

Springdoc OpenAPI란?

Springdoc openAPI이란 스프링부트 프레임워크에서 제공하는 API를 간편하게 문서화하고 테스트할 수 있는 도구다. 찾다보면 단어들이 헷갈려서 정리하자면 다음과 같다.

  • OAS(OpenAPI Specification) : RESTfult API의 구조를 정의하고 기능을 설명하는 명세다. 원래는 Swagger Specification을 기반으로 발전했다고 한다.

  • Swagger : 스웨거는 위의 OpenAPI 명세를 기반으로 명세를 작성하거나, 시각화하고 코드를 생성하고 테스트하는 등 전반적으로 개발을 지원하는 도구다. Swagger에는 Swagger UI(문서형태를 시각화), Swagger Codgen(명세를 기반으로 코드를 자동으로 생성) 등의 도구들이 있다.

  • SpringDoc OpenAPI : 스프링 부트 어플리케이션에서 OpenAPI 3 명세를 생성하고 Swagger UI를 통합해서 지원하는 라이브러리다.
    스프링부트 어플리케이션 라이브러리기 때문에 Spring webflux, mvc, actuator등 다양한 기능들을 지원해서 편리하게 사용할 수 있다.

💡Springboot 3으로 변경되면서 많은 부분이 바뀌었는데, springdoc도 3버전(java 17, jakarta EE9 를 기반)에 맞춰서 개발한 2.x.x버전이 최신 버전인거 같고, springboot 2는 1.x.x버전을 많이 사용하는거 같다.(springboot2에서는 가장 최신버전이 1.8.0이다) 그리고 둘 다 OpenAPI 3을 지원하고 있다. OpenAPI 2의 경우 Swagger 2버전과 함께 사용되는데 이는 springfox라는 라이브러리랑 자주 썼다고 한다. 그러니 스프링부트의 3버전으로 업그레이드를 고려한다면 이제는 springfox는 사용을 권장하지 않는다고한다.

Springdoc OpenAPI 실전

나는 현재 프로젝트에서 springboot 2버전을 사용하고 있기때문에 해당 버전에 맞춰서 사용해보겠다.

1) gradle 설정하기

implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.8.0'
  • 찾다보면 라이브러리가 꽤 여러개 있는것을 볼 수 있는데 해당 라이브러리는 1.8.0 버전까지만 있는듯하고 springboot3 지원의 경우는 springdoc-openapi-starter-webmvc-ui로 적용해주어야한다.

💡MSA 아키텍처에서의 적용
MSA는 보통 여러 어플리케이션으로 나누어져있고 앞에 gateway를 두게된다. 그래서 gateway에서 swagger ui를 적용, yml에서 각 어플리케이션들 url들을 설정하고 나머지 어플리케이션에서는 gateway가 설정을 가져올 수 있도록 yml에서 설정을 해줄 수 있다.
gateway는 보통 webflux 를 사용하기 때문에, springdoc또한 webflux 라이브러리를 적용해주어야한다. 위의 라이브러리는 spring mvc가 들어가있기 때문에 이는 각 어플리케이션에서 설정해주면 된다.

  • (선택) msa 아키텍처에서 gateway 어플리케이션의 gradle 설정
   // https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-webflux-ui
implementation group: 'org.springdoc', name: 'springdoc-openapi-webflux-ui', version: '1.8.0'

2) yml 설정

springdoc:
  api-docs:
    path: /v3/api-docs
  swagger-ui:
    path: /swagger-ui.html
  • api-docs path를 지정해줄 수 있다. api-docs는 json 형태로 애플리케이션의 api 정보나 객체 정보등을 응답받을 수 있다. 이러한 api 데이터를 swagger에서는 swagger.html에 적용한다. MSA에서는 주로 gateway에서 해당 api를 요청하여 정보를 받아서 통합하여 보여준다.
  • swagger-ui path는 api 명세를 시각적으로 보여주는 html url이다. 이를 통해서 api 요청 테스트를 해볼 수도 있다.

yml에서 설정할 수 있는 부분들은 엄청 많으니 해당 부분은 공식문서를 통해 설정하면 된다. 보통은 url을 커스텀하는 정도의 설정만 해주는 것 같다.

  • (선택) MSA Gateway yml에서의 설정
springdoc:
  swagger-ui:
    path: /swagger-ui.html
    config-url: /v3/api-docs/swagger-config
    urls:
      - name : app-service
        url : /app/v3/api-docs
    try-it-out-enabled: true
    display-request-duration: true

중요한것은 urls다. 여기서 각 애플리케이션마다 api-docs 를 요청해야 하기때문에 애플리케이션들의 url에 맞춰서 설정해주어야한다. 나의 경우는 /{application-name}/** 과 같이 prefix로 gateway에서 보내주고 있기때문에 이와 같이 설정하였다.

3) Config 작성 (feat. security 설정)

swagger 명세 html를 구성할때 보다 많은 설정들을 해줄 수 있는데, 이를 위한 config 설정이 필요하다.

@OpenAPIDefinition(
    info = @Info(title = "Auth API", version = "v1"),
    security = {
		@SecurityRequirement(name = "JWT_ACCESS_HEADER")
    }
)
  @Bean
  public OpenAPI customOpenAPItoJwt() {
    return new OpenAPI()
        .addServersItem(new Server().url("/auth"))
        .components(new Components()
            .addSecuritySchemes("JWT_ACCESS_HEADER",
              new SecurityScheme()
                  .type(Type.HTTP)
                  .in(In.HEADER)
                  .name("Authorization")
                  .scheme("Bearer")
                  .bearerFormat("JWT"))
        )
        .addSecurityItem(new io.swagger.v3.oas.models.security.SecurityRequirement()
        .addList("JWT_ACCESS_HEADER"))
        ;
  }
}

내가 설정한 부분은 MSA에 맞춰서 각 애플리케이션에 swagger config가 설정되어있다.

  • OpenAPIDefinition - info에서 전체 제목이나 버전을 설정할 수 있다. security부분은 애플리케이션에서 로그인을 하고 인증이 된 상태로 요청을 보내야할때 필요한 부분으로, 밑에서 설명하는 addSecuritySchemes하고도 연관되어 있다.

  • addServersItem - 해당 부분은 MSA 를 설정할 때 필요했던 부분으로 없으면 gateway에서 직접 해당 서비스의 ip로 요청하게 되어서 cors에러가 난다. gateway에서 설정한 라우터의 url대로 설정해주자.

  • addSecuritySchemes - 해당 부분을 통해서 swagger에도 jwt를 직접 넣어서 api 요청을 보낼 수 있다. 인증이 필요할때 오른쪽에 Authorize 버튼을 통해 header값을 세팅할 수 있다.

    해당 부분을 세팅하면 아래 사진과 같이 각 api마다 자물쇠 아이콘을 선택해주면 내가 세팅한 헤더값을 같이 넣어서 보내준다.

    통합테스트를 할때 security 부분까지 진행해야 된다면 swagger를 이용해서 편리하게 진행할 수 있다.

4) Api 명세 작성

각 Controller에서도 어노테이션을 활용해 부가정보나 응답 값 등을 설정해줄 수 있다. 기본적인 방법은 아래와 같다.

  
  @Operation(summary = "2. 목록 조회")
  @GetMapping("/test")
  public String getTestList() {
    return "test List";
  }

controller 메서드에 @Operatin어노테이션을 붙여주기만 하면된다. 현재는 간단해보이지만, 여러 설정들을 넣게 되면 해당 어노테이션이 엄청 길어진다. (더러워)



  @Operation(summary = "1. id 상세 조회",
      description = "id 값을 통한 상세 조회",
      parameters = {
        @Parameter(name = "id", description = "id 값", required = true)
      }
  )
  @ApiResponses({
      @ApiResponse(
          responseCode = "200",
          description = "성공적으로 처리되었습니다.",
          content = @Content(
              mediaType = "text/plain", // Content-Type을 text/plain으로 설정
              schema = @Schema(type = "string", example = "success detail ${id}.")
          )
      ),
      @ApiResponse(
          responseCode = "400",
          description = "잘못된 요청입니다."
      ),
      @ApiResponse(
          responseCode = "500",
          description = "서버에 오류가 발생했습니다."
      )
  })
  @GetMapping("/test/{id}")
  public String getTestDetail(@PathVariable(name = "id")Long id) {
    return "success detail" + id;
  }

controller에서 이렇게 많은 어노테이션에 대한 내용이 들어간다면 가독성도 떨어지고, Controller가 Swagger에 의존하게 되는 문제가 생긴다. 이를 해결하는 방법으로는 interface로 swagger 어노테이션을 분리하는 방법이 있다.

5) API Swagger 코드를 분리하기

간단하게 Controller의 인터페이스를 선언해주면 된다.


//interface 

public interface ApiSpecification {

  @Operation(summary = "1. id 상세 조회",
      description = "id 값을 통한 상세 조회",
      parameters = {
        @Parameter(name = "id", description = "id 값", required = true)
      }
  )
  @ApiResponses({
      @ApiResponse(
          responseCode = "200",
          description = "성공적으로 처리되었습니다.",
          content = @Content(
              mediaType = "text/plain", // Content-Type을 text/plain으로 설정
              schema = @Schema(type = "string", example = "success detail ${id}.")
          )
      ),
      @ApiResponse(
          responseCode = "400",
          description = "잘못된 요청입니다."
      ),
      @ApiResponse(
          responseCode = "500",
          description = "서버에 오류가 발생했습니다."
      )
  })
  String getTestDetail(@PathVariable(name = "id")Long id)

}


//Controller

public class TestApiController implements TestApiSpecification {

  @Override
  @GetMapping("/test/{id}")
  public String getTestDetail(@PathVariable(name = "id")Long id) {
    ...
  }

이렇게 인터페이스를 활용하면 controller에서 어노테이션이 너무 비대해지는 문제를 깔끔하게 해결할 수 있다.

생각해볼 문제

  • 현재는 응답값을 텍스트로 간단하게 보여주고 있지만, 만약 해당 값들을 복잡하게 보여줘야 할 경우 텍스트로 쓰기에는 한계가 있다고 생각한다. 이를 가독성있게 그리고 간단하게 만드는 방법이 중요할 것 같다.

  • 본문에서는 언급이 되진 않았지만, DTO같은 것들도 Swagger에서는 보여줄 수 있는데 DTO도 여러 설명이 필요하면 어노테이션을 활용해서 작성해줄 수 있다. 근데 이것 또한 Controller처럼 복잡해질텐데, 똑같이 인터페이스로 활용하는게 적절한가하는 의문이든다.
    대충 훑어봤지만 다들 그냥 DTO는 웹에서 주고받는 통신 껍데기의 객체라서 그런지 그냥 객체에 그대로 어노테이션을 쓰는거같긴하지만, 더 알아봐야할 듯 싶다.

profile
개발 그렇게 하는거 아닌데의 그렇게를 맡고있습니다

0개의 댓글