Swagger with WebFlux - ryoobib/backend_webflux GitHub Wiki

WebFlux ํ”„๋กœ์ ํŠธ์— Swagger ์ ์šฉํ•˜๊ธฐ

์‚ฌ์šฉ ์ด์œ 

Swagger๋ฅผ ์ˆœ์ˆ˜ํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์‹ถ์–ด์„œ ์ ์šฉํ–ˆ๋‹ค. ์ด์ „ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” Rest Docs๋ฅผ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ, ๋Œ€ํ‘œ์ ์ธ ํ…Œ์ŠคํŠธ ๋ฌธ์„œํ™” ๋„๊ตฌ์ธ ๋‘ ๊ฐœ๋ฅผ ๋ชจ๋‘ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ๋น„๊ตํ•ด๋ณด๊ณ  ์‹ถ์—ˆ๋‹ค.

1. ์˜์กด์„ฑ ์ถ”๊ฐ€ํ•˜๊ธฐ


plugins {
    id 'org.springframework.boot' version '2.7.1'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id "org.springdoc.openapi-gradle-plugin" version "1.3.4" // ์ถ”๊ฐ€
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'


    implementation 'org.springdoc:springdoc-openapi-webflux-ui:1.6.9' // ์ถ”๊ฐ€

    compileOnly 'org.projectlombok:lombok'

    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'

    annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
}

tasks.named('test') {
    useJUnitPlatform()
}

2. application.yml ํŒŒ์ผ ์ˆ˜์ •ํ•˜๊ธฐ

server:
  servlet:
    context-path: /
  compression:
    enabled: true
    mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
    min-response-size: 1024
  http2:
    enabled: true
com:
  example:
    backend_webflux:
      springdoc:
        api:
          router:
            separete:
              enabled: false
            common:
              enabled: true

Router-Handler ๋ฐฉ์‹์œผ๋กœ endpoint ์ธ์‹ํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ •์ด๋‹ค.

3. Router์— Swagger ์–ด๋…ธํ…Œ์ด์…˜ ์ž‘์„ฑํ•˜๊ธฐ


@Configuration
public class UserRouter {

  @Bean
  @GetAllUserApiInfo
  public RouterFunction<ServerResponse> getAllUserRouter(UserHandler userHandler) {
    return RouterFunctions
        .route(GET("/api/user")
            , userHandler::getAllUser)
        ;
  }

  @Bean
  @GetUserByIdApiInfo
  public RouterFunction<ServerResponse> getUserRouter(UserHandler userHandler) {
    return RouterFunctions
        .route(GET("/api/user/{id}")
            , userHandler::getUser);
  }

  @Bean
  @CreateUserApiInfo
  public RouterFunction<ServerResponse> createUserRouter(UserHandler userHandler) {
    return RouterFunctions
        .route(POST("api/user")
            , userHandler::createUser);
  }

  @Bean
  @ModifyUserByIdApiInfo
  public RouterFunction<ServerResponse> modifyUserRouter(UserHandler userHandler) {
    return RouterFunctions
        .route(PUT("api/user/{id}")
            , userHandler::modifyUser);
  }

  @Bean
  @DeleteUserByIdApiInfo
  public RouterFunction<ServerResponse> deleteUserRouter(UserHandler userHandler) {
    return RouterFunctions
        .route(DELETE("api/user/{id}")
            , userHandler::deleteUser);
  }

}

๊ธฐ์กด์˜ Chaining ๋ฐฉ์‹์—์„œ Separate ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ•˜์˜€๋‹ค. ๊ฐœ๋ณ„ Router ๋งˆ๋‹ค Swagger API Info๋ฅผ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค.

4. Swagger API Info ์–ด๋…ธํ…Œ์ด์…˜ ์ž‘์„ฑํ•˜๊ธฐ

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@RouterOperations({
    @RouterOperation(
        method = RequestMethod.GET,
        operation =
        @Operation(
            description = "Get all users ",
            operationId = "getAllUser",
            tags = "users",
            responses = {
                @ApiResponse(
                    responseCode = "200",
                    description = "Get all users endpoint",
                    content = {
                        @Content(
                            mediaType = MediaType.APPLICATION_JSON_VALUE,
                            array = @ArraySchema(schema = @Schema(implementation = User.class)))
                    }),
                @ApiResponse(
                    responseCode = "400",
                    description = "Not found response",
                    content = {
                        @Content(
                            mediaType = MediaType.APPLICATION_JSON_VALUE,
                            schema = @Schema(implementation = ExceptionResponse.class))
                    })
            }))
})
public @interface GetAllUserApiInfo {

}

Swagger-ui์—์„œ ๋ณด๊ณ  ์‹ถ์€ ์ •๋ณด๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค.

image

์•„๋ž˜ ๋ธ”๋กœ๊ทธ๋ฅผ ์ฐธ๊ณ ํ•˜์˜€๋‹ค.

https://medium.com/dandelion-tutorials/documenting-functional-rest-endpoints-with-springdoc-openapi-21657c0ebc8a

Swagger ์ ์šฉ ํ›„ ๋А๋‚€ ์ 

Swagger๋ฅผ ์ด์šฉํ•˜๋ฉด Router ํด๋ž˜์Šค์— ์ง์ ‘์ ์ธ ์ฝ”๋“œ ์ž‘์„ฑ์œผ๋กœ ๊ฐ€๋…์„ฑ์„ ํ•ด์นœ๋‹ค๋Š” ์ ์„ ์—ฌ๋Ÿฌ ๋ธ”๋กœ๊ทธ๋ฅผ ํ†ตํ•ด ๋ณธ ์ ์ด ์žˆ๋‹ค. ์ด๋Š” ์–ด๋…ธํ…Œ์ด์…˜์„ ์ด์šฉํ•ด ๋”ฐ๋กœ ๋ถ„๋ฆฌ๋กœ ๊ฐ€๋…์„ฑ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์—์„œ ํฌ๊ฒŒ ๊ณต๊ฐํ•˜์ง€ ๋ชปํ•˜์˜€๋‹ค. Rest Docs์™€ ๋‹ค๋ฅด๊ฒŒ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค๋Š” ์ ์—์„œ ์ผ๊ฐ์ด ์ค„์—ˆ๋‹ค๊ณ  ๋А๋‚„ ์ˆ˜๋Š” ์žˆ์œผ๋‚˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•˜๋ฉด์„œ ์ž‘์„ฑํ•˜๋Š” ์ฝ”๋“œ์˜ ์ˆ˜๋„ ๋งŒ๋งŒ์น˜ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํฌ๊ฒŒ ๋‘ ๋„๊ตฌ ๋ชจ๋‘ ์ฝ”๋“œ ์ž‘์„ฑ์˜ ์–‘์œผ๋กœ ๋น„๊ตํ•˜๋Š” ๊ฒƒ์€ ๋ฐ”๋žŒ์งํ•˜์ง€ ์•Š์„ ๊ฑฐ ๊ฐ™๋‹ค.

ํ˜ผ์ž ๊ฐœ๋ฐœ์„ ํ•˜๋‹ค ๋ณด๋‹ˆ, ๋‹ค๋ฅธ ์‚ฌ๋žŒ๊ณผ ๋ฌธ์„œ ๊ณต์œ ๋ฅผ ํ•  ์ผ์ด ์—†์–ด์„œ swagger์˜ ์žฅ์ ์„ ์ตœ๋Œ€๋กœ ๋А๊ปด ๋ณด์ง€๋Š” ๋ชปํ–ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ ์šฉ์ด ์‰ฝ๊ณ , ์„ค์ •์ด ๋งŽ์ง€ ์•Š์€ ์ ์—์„œ๋Š” ๋งค๋ ฅ์ ์ด์—ˆ๋‹ค.