1. Swagger vs Spring Rest Docs

[1] Swagger

  1. 장점
    1. UI 가 깔끔하고 문서화를 사용하기 편리하다.
    2. 테스트 해볼 수 있는 기능과 같은 편리한 기능을 제공한다.
  2. 단점
    1. 테스트없이 문서화가 가능해 로직에 대한 신뢰성을 보장하지 않는다.
    2. 운영 코드에 문서화 관련 코드가 침투하여 가독성이 떨어진다.

[2] Spring REST DOCS

  1. 장점
    1. 테스트를 기반한 문서화이므로 로직의 신뢰성을 보장한다.
    2. 비즈니스 코드와 문서화 코드를 분리시켜 주기 때문에 코드 가독성을 향상시킬 수 있다.
  2. 단점
    1. 커스터마이징에 한계가 있고 문서화에서 제공하는 기능이 제한적이다.

 

2. Swagger vs Spring Rest Docs

Spring REST Docs 기반하여 Swagger 의 장점을 활용하고 단점을 보완하여 사용할 수 있는 방법을 제공한다. OAS(OpenApi Specification) 을 해석하여 API 문서를 Swager-UI 로 시각화해야 하는데, 독일 기업인 epages 에서 Spring REST Docs 와 연동에 OAS 파일을 생성하는 오픈 소스를 제공한다.

 

3. 설치 및 적용

[1] restdocs-api-spec library 설정 및 OAS 출력

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.4.0'
    id 'io.spring.dependency-management' version '1.1.6'
    id 'org.asciidoctor.jvm.convert' version '3.3.2'
    id 'com.epages.restdocs-api-spec' version '0.18.2'
}

group = 'com.cooper'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

configurations {
    asciidoctorExtensions
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

ext {
    set('snippetsDir', file("build/generated-snippets"))
}

dependencies {
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
    asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'
    testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.19.2'
    implementation 'org.springdoc:springdoc-openapi-ui:1.8.0'
}

openapi3 {
    server = 'http://localhost:8080' // 설정할 서버 URL
    title = 'Cooper API' // 문서화 제목
    description = 'Cooper API description' // 문서화 설명
    version = '0.1.0'
    format = 'yaml'
}

tasks.named('test') {
    outputs.dir snippetsDir
    useJUnitPlatform()
}

tasks.named('asciidoctor') {
    configurations 'asciidoctorExtensions'
    baseDirFollowsSourceFile()
    inputs.dir snippetsDir
    dependsOn test
}

bootJar {
    from("${asciidoctor.outputDir}") {
        into "BOOT-INF/classes/static/docs"
    }
    from("swagger-ui") {
        into "BOOT-INF/classes/static/swagger"
    }
    from("build/api-spec") {
        into "BOOT-INF/classes/static/swagger"
    }
    dependsOn('openapi3') // openapi3 실행하고 이후에 bootJar 실행
}

 

 

[2] Swagger-UI 정적 파일 설치 및 라우팅 설정

(1) 제거 파일

출처 : https://tech.kakaopay.com/post/openapi-documentation/

 

(2) 생성 파일

<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Swagger UI for Spring REST Docs 작성 가이드</title>
  <link rel="stylesheet" type="text/css" href="/swagger-ui/swagger-ui.css">
  <link rel="icon" type="image/png" href="/swagger-ui/favicon-32x32.png" sizes="32x32" />
  <link rel="icon" type="image/png" href="/swagger-ui/favicon-16x16.png" sizes="16x16" />
  <style>
    html {
      box-sizing: border-box;
      overflow: -moz-scrollbars-vertical;
      overflow-y: scroll;
    }

    *,
    *:before,
    *:after {
      box-sizing: inherit;
    }

    body {
      margin: 0;
      background: #fafafa;
    }
  </style>
</head>

<body>
<div id="swagger-ui"></div>

<script src="/swagger-ui/swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="/swagger-ui/swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script>
  window.onload = function () {
    // Begin Swagger UI call region
    const ui = SwaggerUIBundle({
      url: "/swagger/openapi3.yaml",
      dom_id: '#swagger-ui',
      deepLinking: true,
      presets: [
        SwaggerUIBundle.presets.apis,
        SwaggerUIStandalonePreset
      ],
      plugins: [
        SwaggerUIBundle.plugins.DownloadUrl
      ],
      layout: "StandaloneLayout"
    })
    // End Swagger UI call region

    window.ui = ui
  }
</script>
</body>

</html>

 

(3) 정적 파일 path 설정(Spring Web)

생성/제거한 swagger-ui 관련 파일을 static 디렉토리 하위에 추가한 파일 정적 파일을 라우팅이 가능하도록 설정해야 한다.

ResourceHandlerRegistry 에 기본 정적 파일 경로에 맞게 addResourceHandlers() 를 작성하도록 하자.

static/swagger-ui 하위에 swagger-ui 정적 파일 위치

 

 

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
	@Override
	public void addResourceHandlers(final ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
		registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/static/swagger-ui/");
	}
}

 

[3] 테스트 작성하기

MockMvc 환경에서 Spring REST Docs 를 적용하기 위한 공통 코드를 위한 클래스를 작성한다.

@AutoConfigureMockMvc
@AutoConfigureRestDocs
@ExtendWith(RestDocumentationExtension.class)
@SpringBootTest
public class RestDocsDocumentation {

	@Autowired
	protected MockMvc mockMvc;

	@Autowired
	protected ObjectMapper objectMapper;

	@Autowired
	protected WebApplicationContext context;

	@BeforeEach
	public void setUp(RestDocumentationContextProvider restDocumentation) {
		this.mockMvc = MockMvcBuilders
			.webAppContextSetup(this.context)
			.addFilters(new CharacterEncodingFilter("UTF-8", true))
			.apply(documentationConfiguration(restDocumentation)
				.operationPreprocessors()
				.withRequestDefaults(modifyUris().removePort(), prettyPrint())
				.withResponseDefaults(modifyUris().removePort(), prettyPrint()))
			.alwaysDo(print())
			.build();
	}

}

 

 

 

테스트를 기반한 문서화 위한 코드를 작성하도록 한다. 자세하게 봐야할 부분은 coffeeMenuApiResource() 이다. 해당 코드를 살펴보면 swagger 에서 사용하는 tag, summary, description 를 제공한다. 테스트가 성공하면 asciidoc 를 변환한 내용을 bootJar 시점에 컴파일 클래스 파일이 존재하는 BOOT_INF/classes 하위에 swagger-ui 정적 파일과 asciidoc 파일을 이동시켜 API 문서화에 사용된다.

@Sql(value = {"classpath:sql/coffee-menu.sql"})
public class CoffeeDocumentation extends RestDocsDocumentation {

	@DisplayName("[성공] 커피 메뉴 조회 문서화")
	@Test
	void getCoffeeMenuList() throws Exception {
		// given, when, then
		mockMvc.perform(get("/api/v1/coffee-menu")
				.contentType(MediaType.APPLICATION_JSON))
			.andExpectAll(
				status().is2xxSuccessful(),
				jsonPath("$.result").value(ResultType.SUCCESS.name()),
				jsonPath("$.data").exists(),
				jsonPath("$.error").doesNotExist()
			)
			.andDo(document("coffee-menu-success", coffeeMenuApiResource()));
	}

	private ResourceSnippet coffeeMenuApiResource() {
		return resource(
			ResourceSnippetParameters.builder()
				.tag("coffee api")
				.summary("커피 메뉴 조회 API")
				.description("커피 메뉴 조회 DESCRIPTION")
				.requestHeaders(
					headerWithName(HttpHeaders.CONTENT_TYPE).description("컨텐츠 타입"))
				.responseFields(
					fieldWithPath("result").type(JsonFieldType.STRING).description("응답 결과"),
					fieldWithPath("data[0].id").type(JsonFieldType.NUMBER).description("커피 메뉴 아이디"),
					fieldWithPath("data[0].name").type(JsonFieldType.STRING).description("커피 메뉴 이름"),
					fieldWithPath("data[0].price").type(JsonFieldType.NUMBER).description("커피 메뉴 가격"),
					fieldWithPath("error").type(JsonFieldType.NULL).description("에러 정보"))
				.build());
	}

}

 

[4] 실행 화면 확인하기

swagger 문서화는 build 를 한 bootJar 파일을 실행시킬 때만 동작한다. build task 로 등록한 openapi3 task 가 작업이 완료되면 openapi3.yaml 을 생성하는데 bootJar 파일에 추가되기 때문이다. 그러므로 꼭 java -jar 명령어를 통해서 실행하도록 하자.

java -jar build/libs/cafe-order-0.0.1-SNAPSHOT.jar

 

 

실행된 결과를 이전에 설정한 URL 을 통해서 결과를 확인해보면 정상적으로 출력한 것을 확인할 수 있다.

http://localhost:8080/swagger-ui.html

 

4. Reference