OAS - Swagger & Restdocs
프런트와 소통을 위해 API Spec을 제공해줘야 했습니다. 이 과정에서 제공을 위해 고려했던 과정을 공유하고자 합니다.
순서는 가장 많이 사용하는 swagger와 spring rest docs에 대해서 설명하고 그 후 제가 선택했던 방법 순으로 이야기할 예정입니다.
OAS란?
openAPI Specification의 약자로 Rest API에 대한 문서화를 위한 규격입니다.
OAS와 관련하여 자세한 내용은 OpenAPI Specification v3.1.0에서 보면 될 거 같습니다.
Swagger는 이런 OAS 스펙을 구현한 프레임워크라고 생각하면 될 거 같습니다.
사실 OAS는 v3이전에는 swagger1, swagger2로 불렸었는데 SmartBear Software라는 회사가 개발하여 가지고 있었습니다. 그러다 Linux Foundation에 기부가 되면서 새로운 버전으로 OpenAPI Specification를 개발하면서 OAS3.0으로 변경이 되었습니다. 그러면서 이 OAS스펙에 맞게 새롭게 개발이 진행되고 있습니다.
Swagger
swagger는 크게 3개의 툴로 이루어져 있습니다. `Swagger Codegen`, `Swagger Editor`, `Swagger UI`로 되어 있습니다.
- `Swagger Codegen`: OAS 명세(JSON/YAML)을 기반으로 Stub 코드를 만들어 내는 역할을 한다.
- `Swagger Editor`: OAS 명세를 작성하는 하나의 툴
- `Swagger UI`: OAS 명세를 통해 HTML API 문서로 렌더링 한다.
- Ex) Swagger UI
spring프레임워크에 특화된 라이브러리로 `springfox`와 `springdoc`가 있습니다. 이러한 라이브러리들은 Annotation을 통해 자동으로 API 명세를 만들어 Swager UI로 보여주는 역할을 합니다.
`springfox`의 경우 개발이 중단되기도 하였고 swagger2.0 스펙을 지원하다 보니, 최근에 개발된 `springdoc`가 oas3.0 스펙을 지원하는 것으로 비교해 봤을 때 부족한 부분이 있습니다. 따라서 지속적으로 개발이 진행되고 있고 oas3.0스펙을 지원하는 springdoc를 사용하여 예시를 보여줄 예정입니다.
springdoc의 경우 여러 라이브러리를 함께 지원합니다.
- OAS 3.0
- Spring boot v3 (Java17 & Jakarta EE 9)
- JSR-303, @NotNull, @Min, @Max, and @Size
- Swagger-ui
- OAuth2
Springdoc 적용하기
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui'
설정 파일 추가
springdoc:
default-consumes-media-type: application/json;charset=UTF-8
default-produces-media-type: application/json;charset=UTF-8
swagger-ui:
path: /api/docs
disable-swagger-default-url: true
display-request-duration: true
operations-sorter: alpha
springdoc를 사용하여 API 문서를 생성할 때 기본적으로 적용이 되는 설정입니다.
요청과 응답에 대한 media type과 swagger-ui로 렌더링 할 때의 위치와 API 요청에 대한 실행시간, API 보이는 순서를 설정했습니다. 이 외에도 여러 설정을 적용할 수가 있습니다.
springdoc-openapi properties에서 참고하여 추가적으로 작성하면 될 거 같습니다.
빈등록
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
// .components(new Components().addSecuritySchemes(
// "bererToken",
// new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("JWT")))
.info(new Info().title("Swagger with springdoc")
.description("springdoc을 통한 OAS구성")
.version("v0.0.1")
.license(new License().name("Apache 2.0").url("http://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("문서")
.url("https://springshop.wiki.github.org/docs"));
}
@Bean
public GroupedOpenApi mainOpenApi() {
String packagesToscan[] = {"com.example.oauth.controller"};
return GroupedOpenApi.builder().group("main").packagesToScan(packagesToscan)
.build();
}
@Bean
public GroupedOpenApi adminOpenApi() {
String packagesToscan[] = {"com.example.oauth.admin.controller"};
return GroupedOpenApi.builder().group("admin").packagesToScan(packagesToscan)
.build();
}
@Bean
public GroupedOpenApi memberOpenApi() {
String packagesToscan[] = {"com.example.oauth.member.controller"};
return GroupedOpenApi.builder().group("users").packagesToScan(packagesToscan)
.build();
}
@Bean
public GroupedOpenApi shortOpenApi() {
String packagesToscan[] = {"com.example.oauth.shorted.controller"};
return GroupedOpenApi.builder().group("short").packagesToScan(packagesToscan)
.build();
}
}
먼저 OpenAPI문서를 생성하기 위해 위와 같이 bean을 등록해야 했습니다.
메인 header와 같은 느낌이라 생각하시면 될 거 같습니다. 따라서 헤더에 들어갈 여러 내용을 넣을 수 있습니다.
group openApi는 우측 상단의 group을 나누어 선택할 수 있게 하고, 그룹에 맞게 controller를 지정하면 그 내부에서 사용할 수 있습니다.
API문서 추가
@SecurityScheme(
name="bearerAuth",
type= SecuritySchemeType.HTTP,
scheme = "Bearer",
bearerFormat = "JWT"
)
@Tag(name = "Admin", description = "관리자 API")
@RestController
@RequestMapping("/admin")
public class AdminController {
@Operation(summary = "테스트1", description = "관리자 테스트 1",
security = @SecurityRequirement(name="bearerAuth"))
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "관리자 테스트1 성공",
content = @Content(schema = @Schema(implementation = AdminResponse.class))),
@ApiResponse(responseCode = "404", description = "Not Found",
content = @Content),
@ApiResponse(responseCode = "401", description = "Authentication Failure",
content = @Content(schema = @Schema(hidden = true)))
})
@GetMapping
public void adminTest1() {
}
@PostMapping
public void adminTest2() {
}
}
- SecurityScheme: Bean등록에서 components의 BearerToken과 같다
- 다만 위에는 전박적인 적용을 해주는 반면, 위 코드는 하나의 클래스에 대해 적용
또한 @ResponseBody나 @RequestBody를 통해 자동으로 API문서를 만들 때 추가해 줄 수 있다.
@GetMapping(value = "/oauth/google", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void authorizationCode(@RequestParam("code") String code,
@RequestParam("scope") String scope,
@RequestParam("authuser") String user,
@RequestParam("prompt") String prompt,
HttpServletResponse response) throws IOException, URISyntaxException {
}
내가 느낀 Swagger의 장점과 단점
swagger를 많이 사용해 보지 않아도 딱 느낄 수 있었던 장점과 단점이 있었습니다.
장점
- 알록달록한 UI: 아무런 설정 없이 바로 완성된 UI를 제공해 주는 점이 좋았고, 눈에 확 들어왔습니다.
- swagger-ui: 전항에서 ui의 디자인에 대해서 말씀드렸는데, 그 외에도 테스트 동작까지 같이 수행할 수 있다는 점도 하나의 장점이라 볼 수 있었습니다.
- controller에서 명세를 간략하게 미리 볼 수 있었습니다.
단점
- 보기 힘든 코드: 많은 어노테이션이 있고 그에 대한 여러 설명이나, 샘플까지 넣을 수 있다 보니 코드가 눈에 안 들어왔습니다.
- springdoc을 위한 Bean객체, 즉 API문서화를 위한 Bean등록이 추가되어야 했습니다. 어떻게 보면 사소하지만 약간은 필요 없는 코드가 추가된 느낌을 받았습니다.
- 지속적으로
내가 관리해야 하는 코드가 늘어난 느낌이고, 코드에 집중이 안되고 분산되는 느낌도 받았습니다. controller에서 데이터가 어떤 형태로 들어오는지 더 명확하게 알 수 있고, 설명까지 넣다 보니 무슨 역할을 하는지 나중에 보더라도 쉽게 알 수 있었습니다. 다만 코드를 보려면 많은 어노테이션들이 걸리적거려 코드에 집중을 저하시켰던 부분이 있었습니다. 또한 처음에는 '어떤 데이터가 오고 가는지 한 번에 알 수 있어서 좋다'라고 생각했지만 이후 '근데 여기서 볼 필요가 있나? 어차피 테스트 코드에서 테스트 파라미터로 더 많은 예제를 볼 수 있는데 굳이?'라는 생각이 들었습니다.
Spring restdocs
Spring의 Rest Docs는 srping에서 작성된 테스트에서 생성된 snippet을 사용하여 만듭니다.
테스트를 통해 API 문서에 대해 문서와 실제 코드가 동기화되고 이 API 문서 자체가 올바르다는 신뢰성을 보장해 줍니다.
간단 용어
- Asciidoctor: asciidoc을 HTML 등으로 변환하기 위한 프로세서
- Snippet: 문서화에 필요한 문서 조각
- 문서 조각 = `http-request`, `http-response`등을 나타낸다.
요구 사항
Spring Rest Docs를 사용하기 위해서는 최소 요구 사항이 있습니다. `Java17`과 `Spring Framework6`가 기반이 되어야 사용이 가능합니다.
Build Configuration
1. 아스키닥터 plugin 적용
plugins {
id "org.asciidoctor.jvm.convert" version "3.3.2"
}
2. Configurations: Asciidoctor를 확장하기 위한 의존성 구성
configurations {
asciidoctorExt
}
3. AsciidoctorExt 구성에 `spring-restdocs-asciidoctor`추가
dependencies {
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor:{project-version}'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc:{project-version}'
}
Asciidoctor가 API문서를 생성하기 위해 snippet을 찾아볼 위치를 `build/generated-snippets`로 자동 지정이 됩니다.
또한 asciidoc의 장점 중 하나인 `::` operation블록을 사용할 수 있습니다.
사용하고자 하는 test 라이브러리에 따라 다른 라이브러리를 넣어주면 됩니다.
- `MockMvc`: spring-restdocs-mockmvc
- `WebClient`: spring-restdocs-webtestclient
- `REST Assured`: spring-restdocs-restassured
5. Output Location: snippet들을 generate 하고 나온 결과물 위치
ext {
snippetsDir = file('build/generated-snippets')
}
test {
outputs.dir snippetsDir
}
6. asciidoctor가 수행할 task 설정
asciidoctor {
inputs.dir snippetsDir
configurations 'asciidoctorExt'
baseDirFollowsSourceFile()
dependsOn test
}
baseDirFollowsSourceFile()은 Asciidoctor가 변환 작업을 할 때 소스 파일의 디렉터리 구조 유지용
여기까지 하고 빌드를 하게 되면
빌드에 여러 스니펫들이 존재하는 것을 볼 수 있습니다.
여기서 하나 알고 가야 할 점은 snippet을 generate 하기 전에 해야 할 것이 있습니다.
바로. adoc파일을 만들어야 하는데 하나의 기준점이라 생각하면 될 거 같습니다.
따라서 `src/docs/asciidoc/`에 `*. adoc`파일을 하나 만들고 asciidoctor를 실행해 주면 html로 변환해 주는데, 우리의 목표는 직접 프로세스를 돌리는 것이 아니라 jar로 같이 묶는 것까지 하는 것이므로 추가적인 gradle설정을 보고 가겠습니다.
7. bootJar 패키징
bootJar {
dependsOn(tasks.named("copyDocs"))
from ("${asciidoctor.outputDir}/html5") {
into 'static/docs'
}
}
순서대로 bootJar가 asciidoctor를 의존하고, asciidoctor는 test를 의존합니다.
따라서 순서는 test > asciidoctor > bootJar순으로 수행됩니다.
이후 build를 하면, `./build/generated-snippets/html5`에 html파일이 생성되고, jar안의 `/static/docs/`폴더에 복사가 됩니다.
8. 파일 복사
tasks.register("copyDocs", Copy) {
dependsOn asciidoctor
from file("build/docs/asciidoc")
into file("src/main/resources/static")
}
출력 결과물을 resoures.static으로 옮기기 위한 과정
9. /static/docs의 파일들 삭제
asciidoctor.doFirst {
delete file('src/main/resources/static/docs')
}
지속적으로 파일들이 새로 가져오게 되는데, 이전 값들이 남아있으면 원하는 결과가 아닐 수 있기 때문에 삭제해 줍니다.
10. index.adoc파일 생성
= OAuth API 문서
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 3
== Google OAuth Client
=== Request
include::{snippets}/login-google/http-request.adoc[]
=== Response
include::{snippets}/login-google/http-response.adoc[]
위치는 위에서 언급했듯, `src/docs/asciidoc/index.adoc`을 만들어 주면 된다.
이후 bootJar로 빌드하도록 해놓았기 때문에
`./gradlew bootJar로 실행을 하게 되면 된다.
Rest Docs Test 작성
테스트는 JUnit 버전에 따라 다르게 작성할 수 있었습니다. 물론 상위호환이 되기 때문에 선택하여 사용하면 될 거 같습니다.
RestDocumentation생성
Junit5.1에서는 `RestDocumentationExtension`을 어노테이션을 통해 합성하여 사용할 수 있도록 제공한다.
@RegisterExtension
final RestDocumentationExtension restDocumentationExtension = new RestDocumentationExtension();
JUnit5.0에서는 Extension을 클래스 어노테이션 `ExtendWith`로 제공한다.
@ExtendWith(RestDocumentationExtension.class)
@WebMvcTest(OAuthController.class)
class OAuthControllerTest {
}
와 같이 확장하여 사용하면 된다.
또한 기본 설정의 경우에도
@BeforeEach
void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation)
.uris()
.withScheme("https")
.withHost("docs.api.com")
//.withPort(443) // default로 가능
.and()
.snippets()
.withEncoding("UTF-8") // default는 JVM의 기본 Charset
// .withTemplateFormat(TemplateFormats.markdown()) // 기본 asciidoctor
/**
* private List<Snippet> defaultSnippets = new ArrayList<>(Arrays.asList(CliDocumentation.curlRequest(),
* CliDocumentation.httpieRequest(), HttpDocumentation.httpRequest(), HttpDocumentation.httpResponse(),
* PayloadDocumentation.requestBody(), PayloadDocumentation.responseBody()));
*/
// .withDefaults()
.and()
.operationPreprocessors()
//.withRequestDefaults(modifyUris().scheme("https").host("docs.api.com").port(443)) // 해당 전처리기에만 적용
.withResponseDefaults(prettyPrint())
)
.build();
}
물론 이렇게 안 하고 `@AutoConfigureRestDocs`가 존재하지만, 어노테이션이다 보니 커스텀할 수 있는 부분이 더 적다.
@DisplayName("")
@Test
void redirect_login_success() throws Exception {
// when
ResultActions result = mockMvc.perform(
get("/login/google")
.accept(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
);
// then
result.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
.andDo(
document("login-google",
responseHeaders(
headerWithName("Location").description("Google OAuth Login Page Redirect Url")
))
);
}
이렇게 간단하게 했을 때
이런 식으로 나오는 걸 볼 수 있다.
추가적으로 snippet을 customizing 해야 하는 경우도 생기는데
만약 curl-request라는 snippet을 만든다면, `src/test/resources/org/springframework/restdocs/templates/asciidoctor`에 넣어주면 됩니다. 이후 테스트코드 입력 포맷은 `AbstractFieldsSnippet`을 상속받아서 사용할 수 있습니다.
Spring restdocs의 장점과 단점
장점
- 테스트 코드를 기반으로. adoc파일을 만들기 때문에 테스트 코드가 강제가 된다.
- API문서와 코드가 완전히 동기화된다.
단점
- . adoc 형식의 문법을 알아야 했습니다.
- 적용하는데 생각보다 많은 설정이 필요했고 사용되는 코드도 점점 복잡해졌습니다.
restdocs를 사용함으로써 기존 Swagger에서 느꼈던 제품코드에 집중할 수 없었던 문제를 해결할 수 있었습니다. 또한 이전에 테스트 코드를 거의 작성하지 않았던 프로젝트에서 안 좋은 기억이 있어서 다시 한번 테스트 코드의 중요성을 느껴 테스트 커버리지도 높이던 찰나에 테스트 코드를 통한 API문서 생성은 더 좋게 다가왔다.
하지만, 가장 아쉬웠던 부분은 적용해야 할 부분이 많다였습니다. 물론 커스터마이징 할 수 있다는 것 자체가 장점으로 받아 들어졌지만, 그만큼 snippets를 새로 만들고 테스트코드에 추가하고 등의 작업이 해야 할 일이 많다고 느껴진 것은 사실이었습니다. 그리고 ui의 단순함 또한 아쉽게 느껴졌는데, 기본 UI자체가 swagger-ui보다 단조로웠던 부분이 있습니다.
내가 선택한 방법
사실 어떤 것이 더 좋냐는 위에서 나왔던 장단점들을 보며 선택을 할 수 있었지만, 현재 상황에 대해서도 생각을 해봐야 했습니다.
현 상황
저는 지금 정말 새로운 프로젝트를 들어가는 상황이었습니다. 따라서 API스펙은 주기적으로 바뀌는 상황입니다.
따라서 레거시방식이 없기 때문에 마이그레이션을 할 필요가 없었고 하나로 통일만 하면 되는 상태였습니다.
결정
사실, 처음에는 restdocs로 정하려 했는데, `document()`만 하게 된다면 기본적으로 테스트 코드에 맞게 자동으로 생성해 줄 수 있어서 스펙이 변경이 될 때마다 문서화를 위한 코드도 변경하지 않고 제품코드만 변경하면 될 거 같다 생각했습니다.
그런데 인프콘에서 어느 개발자분과 소중한 시간을 가지며 질의를 가질 수 있었는데, 거기서 하셨던 말씀이 제가 생각하지 못했던 부분을 집어주셔서 그 방식과 비슷하게 선택하게 되었습니다.
'거기서 나왔던 부분은 프로젝트 초기에 API스펙이 나와도 지속적으로 나오게 될 것이고, 그때마다 고치는 것은 사실 많은 리소스를 잡는데, 그렇기 때문에 우리는 swagger-ui과 yaml로만 작성을 한다'라고 했었습니다.
협업을 하는 입장에서 API Spec을 통해 프런트가 개발을 하게 될 건데 문제는 만약 `@Controller`클래스를 먼저 만들게 되면, 스펙에 따라 입력받는 dto와 반환 상태 등 여러 값이 변경되게 되고, 이에 따라 API문서화를 위한 어노테이션도 변경해야 합니다. 따라서 하나의 변경이 두 변경으로 이어지고 리소스가 많이 들어가게 됩니다.
따라서 처음에는 swagger-ui와 yaml을 통해 API Spec을 전달하고자 했고, 이후 테스트 코드를 작성하게 되면 그때 하나씩 spring rest docs와 swagger-ui를 함께 사용할 수 있게 변경하도록 결정했습니다.
1. OAS를 사용하여 작성한 YAML/JSON -> UI로 변경
yaml을 swagger ui로 변환하기 위해 크게 2가지 방식으로 많이 진행한다.
이 중에서 1번의 경우 버전업이 될 경우 계속 다운로드하여야 한다 생각이 돼서 2번으로 진행했습니다.
먼저 generate해주는 라이브러리는 gradle에서 작업을 수행할 수 있도록 도와주는데 이러한 `tasks`를 수행하기 위해
plugins {
...
id 'org.hidetake.swagger.generator' version '2.19.2'
}
dependencies {
...
swaggerUI 'org.webjars:swagger-ui:3.52.5'
}
룰 넣어주었다.
이후 직접 만든 api를 넣어주었다.
openapi: 3.0.3
info:
version: 1.0.0
title: Simple API
description: A simple API to illustrate OpenAPI concepts
servers:
- url: https://example.io/v1
components:
securitySchemes:
BasicAuth:
type: http
scheme: basic
security:
- BasicAuth: []
paths:
/artists:
get:
description: Returns a list of artists
# ----- Added lines ----------------------------------------
parameters:
- name: limit
in: query
description: Limits the number of items on a page
schema:
type: integer
- name: offset
in: query
description: Specifies the page number of the artists to be displayed
schema:
type: integer
# ---- /Added lines ----------------------------------------
responses:
'400':
description: Invalid request
content:
application/json:
schema:
type: object
properties:
message:
type: string
post:
responses:
'201':
description: "성공"
(예시를 가져왔습니다)
이 openapi를 ui로 변경하기 위해
gradle에 `swaggerSources`를 넣어주면 된다.
swaggerSources {
oauthtest {
inputFile = file("src/main/resources/api.yaml")
}
}
여기까지 작성 후 generateSwaggerUIOauthtest를 실행하게 되면
./gradlew generateSwaggerUIOauthtest
swagger-ui가 적용된 폴더를 확인할 수 있다.
이후 build를 할 때에는 jar안에 넣어줘야 배포 시 접근이 가능하기 때문에, `swagger-ui-oauthtest`를 static폴더 내부로 옮겨야 합니다.
tasks.register('copySwaggerUI') {
dependsOn generateSwaggerUIOauthtest
doLast {
copy {
def generateSwaggerUITask = tasks.named('generateSwaggerUIOauthtest', GenerateSwaggerUI).get()
from "${generateSwaggerUITask.outputDir}"
into "${project.buildDir}/resources/main/static/docs"
}
}
}
bootJar {
dependsOn 'copySwaggerUI'
}
따라서 위와 같이 실행하게 되면
정상적으로 동작하게 됩니다.
이렇게 yaml을 통해 작성을 하다. 추후 Controller에서 test코드의 작성과 함께 restdocs를 적용하게 되었습니다.
2. spring-restdocs를 yaml로 변경하여 ui로 변경할 수 있도록 적용하기
spring-restdocs의 결과물로는 `asciidoct`파일이 들어오게 된다. 하지만 swagger-ui에 적용하려면 `yaml`이나 `json`으로 만들어야 하기 때문에 변환을 해주는 과정이 필요하다 이를 도와주는 라이브러리가 있는데 `spring rest docs api specification`입니다.
이 라이브러리의 task들을 넣기 위해서
plugins {
...
id 'com.epages.restdocs-api-spec' version '0.18.2'
}
dependencies {
testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.18.2'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}
위와 같이 gradle을 넣어주었습니다.
이후 제공해 주는 tasks 또한 넣어주면 코드를 변환할 준비는 끝납니다.
openapi3 {
setServer("http://localhost:8008")
title = "Simple API2"
description = "description OpenAPI concepts"
version= "1.0.0"
format = "yaml"
}
여기서 생각해야 할 부분은
test코드에서 파일을 만드는 과정을 `MockMvcRestDocumentationWrapper`의 document로 만들어야 한다는 것이다.
@Test
void redirect_login_success() throws Exception {
// when
ResultActions result = mockMvc.perform(
RestDocumentationRequestBuilders.get("/login/google")
.accept(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
);
// then
result.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
.andDo(
MockMvcRestDocumentationWrapper.document("/login-google",
new ResourceSnippetParametersBuilder()
.tag("Google login")
.description("OAuth Login")
.responseFields(
PayloadDocumentation.fieldWithPath("email").description("이메일")
)
)
);
}
이렇게
gradlew openapi3
를 하게 되면,
위와 같이 yaml값을 만들어지는 것을 볼 수 있습니다.
이제 이 yaml을 jar파일에 넣어줘야 하기 때문에 swagger때와 같이 실행할 수 있도록 task의 의존성을 연결해 줘야 합니다.
bootJar {
dependsOn 'copySwaggerUI'
}
tasks.withType(GenerateSwaggerUI) {
dependsOn 'openapi3'
}
tasks.register('copySwaggerUI') {
dependsOn 'generateSwaggerUIOauthtest', 'generateSwaggerUIOauthtest2'
doLast {
copy {
def generateSwaggerUITask = tasks.named('generateSwaggerUIOauthtest', GenerateSwaggerUI).get()
from "${generateSwaggerUITask.outputDir}"
into "${project.buildDir}/resources/main/static/docs/v1"
}
copy {
def generateSwaggerUITask2 = tasks.named("generateSwaggerUIOauthtest2", GenerateSwaggerUI).get()
from "${generateSwaggerUITask2.outputDir}"
into "${project.buildDir}/resources/main/static/docs/v2"
}
}
}
따라서 위와 같이 이전 yaml만 적용했을 때의 `generateSwaggerUIOauthtest`와 restdocs를 yaml로 변경한 `generateSwaggerUIOauthtest2`를 의존하고 실행을 하게 되면
이렇게 spring rest docs를 정상적으로 yaml로 변경하고 swagger-ui로 적용하는 것을 볼 수 있습니다.
결론
이렇게 실제로 yaml을 직접 swagger-ui로 적용시키고, spring-restdocs 또한 yaml로 변경 후 swagger-ui로 적용하면서 API를 보냈습니다. swagger만을 사용했을 때의 문제점은 spring-restdocs를 통해 보완이 가능했던 점을 겪을 수 있었고, restdocs만을 사용했을 때의 문제점 또한 순수 yaml과 swagger-ui를 통해 trade-off를 할 수 있었습니다.
막 개발이 시작되고 MVP기능에 따른 요구 사항이 계속 바뀌는 현시점에서 swagger만을 사용한다면, controller에 어노테이션이 들어가면서 지속적으로 docs를 최신화하고, 메인 기능을 볼 수 없다는 단점과 spring-restdocs를 사용하기에는 테스트 코드를 작성해야 하는데, 내가 테스트 코드를 완성하고 통과되기까지 API 문서를 공유 못한다는 점과 ui자체가 보기 힘들고, test기능도 없다는 단점이 있었기에 위와 같은 과정을 통해 수정했습니다.
위 방식이 best practice도 아닐 것이고, 각자에게 맞는 도구가 있기 때문에 원하는 방식으로 하면 될 것이라 생각합니다.
참고 문헌
Swagger:
- About Swagger Specification | Documentation | Swagger
- springdoc.org
- Swagger-ui github
- [Spring-API First Develope 연재-1] Spring-docs를 이용한 Swagger API 문서 자동생성 (sk.com)
- [SpringBoot] Swagger API 문서 자동화 간단 연동, 테스트하기 (tistory.com)
- Gradle swagger generator plugin - github
Restdocs:
- Spring Rest Docs 적용 | 우아한형제들 기술블로그 (woowahan.com)
- Spring REST Docs v1.0.1 레퍼런스 :: 스프링부트는 사랑입니다 (tistory.com)
- Spring Rest Docs API Specification Integration - github
Swagger to Restdocs:
- SwaggerUI + Spring REST Docs 함께 사용하기(feat. Rest Assured) (tistory.com)
- 내가 만든 API를 널리 알리기 - Spring REST Docs 가이드편 - 컬리 기술 블로그 (kurly.com)
- ePages-de/restdocs-api-spec: Adds API specification support to Spring REST Docs (github.com)
- OpenAPI Specification을 이용한 더욱 효과적인 API 문서화 | 카카오페이 기술 블로그 (kakaopay.com)