Index
1: 독립형 Swagger UI 서비스
2: 도커 컴포즈 파일
3: Rest Docs를 Open API로 정적 배포(Gradle Task)
4: Web Config(CORS) 및 Security Config
플러그인은 이렇게 두 개를 추가한다.
Asciidoctor
restdocs-api-spec
의존성도 넣어 놨다.
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.0'
id 'io.spring.dependency-management' version '1.1.0'
// Added:
id 'org.asciidoctor.jvm.convert' version '3.3.2'
id 'com.epages.restdocs-api-spec' version '0.17.1'
}
dependencies {
// ...
// RestDoc to Open API
implementation "org.springdoc:springdoc-openapi-ui:1.6.14"
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.17.1'
}
멀티 모듈인 경우 apply plugin: 'org.asciidoctor.jvm.convert'
등도 적용해 준다.
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.0'
id 'io.spring.dependency-management' version '1.1.0'
// Added:
id 'org.asciidoctor.jvm.convert' version '3.3.2'
id 'com.epages.restdocs-api-spec' version '0.17.1'
}
// if multi-module project (example)
allprojects {
// ...
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'org.asciidoctor.jvm.convert'
apply plugin: 'com.epages.restdocs-api-spec'
// ...
dependencies {
// ...
implementation 'com.google.code.gson:gson:2.10.1'
// RestDoc
implementation "org.springdoc:springdoc-openapi-ui:1.6.14"
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.17.1'
}
}
epages
에서 제공한 위 변환 플러그인을 추가했다면, openapi
, openapi3
, postman
등 태스크를 사용할 수 있다.
그중 우리는 openapi3
태스크를 사용할 것이다.
이처럼 build.gradle
에 태스크를 추가해 주고,
테스트 후 알아서 수행되도록 테스트 작업에 추가해 준다.
멀티모듈인 경우 아래 예시는 allprojects { ... }
안에 작성하면 된다.
(루트프로젝트를 사용하지 않으면 subprojects { ... }
안에 작성해도 된다.)
tasks.named('test') {
useJUnitPlatform()
finalizedBy 'openapi3' // added
}
// rest docs open api
openapi3 {
println("project.rootProject.rootDir: $project.rootProject.rootDir")
println("project.name: ${project.name}")
server = 'https://localhost:8080'
title = 'My API'
description = 'My API description'
// tagDescriptionsPropertiesFile = "${project.rootProject.projectDir}/docs/tag-descriptions.yml"
version = '0.1.0'
format = 'json'
outputDirectory = 'src/main/resources/static/docs'
outputFileNamePrefix = "openapi3.${project.name}"
project.mkdir "${project.projectDir}/${outputDirectory}"
}
이런 API를 테스트해 보겠다.
@RestController
public final class SampleApi {
public record SampleRequestDto(@NotBlank String name, @NotNull @Min(0) Integer age) {
public SampleRequestDto { name = name.strip(); }
}
@Builder
public record SampleResponseDto(Boolean success) {}
@PostMapping("/")
public SampleResponseDto sample(@RequestBody @Valid SampleRequestDto body) {
return SampleResponseDto.builder()
.success(true)
.build();
}
}
테스트 코드와 문서화 과정이다.
@SpringBootTest(webEnvironment = RANDOM_PORT)
@AutoConfigureMockMvc // MockMvcBuilders.webAppContextSetup(webApplicationContext) ... .build()
@AutoConfigureRestDocs // .apply(documentationConfiguration(restDocumentation))
@ExtendWith(SpringExtension.class)
class SampleApiTest {
@Autowired private MockMvc mockMvc;
@Autowired private Gson gson; // object mapper를 사용해도 됨.
@Test
public void test() throws Exception {
SampleRequestDto dto = new SampleRequestDto("홍길동", 17);
ResultActions perform = this.mockMvc.perform(post("/")
.content(gson.toJson(dto)) // {"name": "홍길동", "age": 17}
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
// .with(csrf())
)
.andExpect(status().is2xxSuccessful());
perform.andDo(print())
.andDo(
document("my-sample-api-identifier",
// pathParameters(parameterWithName("").description(""), ... ),
requestFields(
fieldWithPath("name").description("이름"),
fieldWithPath("age").description("나이")
),
responseFields(
fieldWithPath("success").description("성공")
)
)
)
.andDo(
document("sample",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
resource(
ResourceSnippetParameters.builder()
.summary("API 설명 요약입니다.")
.description("이것이 바로 API 설명입니다.")
// .pathParameters(parameterWithName("").description(""), ... ),
.requestFields(
fieldWithPath("name").description("이름"),
fieldWithPath("age").description("나이")
)
.responseFields(
fieldWithPath("success").description("성공")
)
.build()
)
)
);
}
}
임포트한 것들이 헷갈리면 참고하면 된다.
(일부는 epages가 제공하는 것으로 대체하여 사용해도 됨.)
import com.epages.restdocs.apispec.ResourceSnippetParameters;
import com.example.demo.v1.api.SampleApi.SampleRequestDto;
import com.google.gson.Gson;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import static com.epages.restdocs.apispec.ResourceDocumentation.resource;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
이렇게 했을 때 src/main/resources
에 static/docs/openapi3.{프로젝트이름}.json
이런 파일이 생겨나면 된다. 열어 보면 { "openapi" : "3.0.1", ...
이렇게 시작하고 있을 것이다. 우리가 실행해 둔 Swagger UI 컨테이너가 이 Open API 문서를 읽기 때문에, 각 서버 애플리케이션은 여기까지만 작업을 수행해 주면 된다.
대충 이런 과정이다.
Test + Create Rest Docs
→ Ascii Doctor: mkbuild/generated-snippets/*/~.adoc
files
→ ePages: mkopanapi3.{yourProjectName}.json
이렇게 하고 나면, Swagger UI 화면이 이 서버 애플리케이션 API에 접근할 수 있도록 CORS 설정이 필요할 수 있다.
(이 설정을 생략해도 되는지 모르겠다면, 생략하면 안 되는 사람일 것이다.)
Web Config(CORS) 및 Security Config >
< 도커 컴포즈 파일