어떻게 하면 문서를 항상 최신화할 수 있을까?
API는 클라이언트에게 기능만 제공하기 때문에, API를 어떻게 사용해야하는지 문서화를해 제공한다. 개발자들은 Swagger, Postman, 심지어 워드 등 다양한 툴을 사용해 문서화를 해왔다. 기존 문서화의 가장 큰 문제점은 코드와 문서의 불일치였다. 코드와 문서가 분리되어 있다보니, 문서를 항상 최신으로 업데이트하는 것은 귀찮은 일이었고, 이때문에 누락되기 일쑤였다. Rest Docs는 코드와 문서의 강결합을 통해 이를 해결하고자 한다
Rest Docs의 문서는 코드를 실행해 생성된 snippet의 조합으로 이루어진다
spring-restdocs-mockmvc
의존성을 제공한다 <dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
requestFields
snippet과 responseFields
snippets을 만들 수 있다. MockMvc 테스트를 위해 입력된 요청이나 반환된 응답에 대해 모든 필드를 정의해줘야한다. 만약 프로덕션 코드와 테스트 코드는 수정되어 요청/응답이 변했지만, rest docs의 정의가 변하지 않았으면 테스트는 실패한다. API와 문서가 항상 동일하게 유지할 수 있는 것이다 @Test
@DisplayName("게시글을 저장할 수 있다")
void savePost() throws Exception {
// Given
PostRequest postRequest = new PostRequest("test title", "");
// When Then
mockMvc
.perform(post("/api/v1/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(postRequest)))
.andExpect(status().isCreated())
.andDo(print())
.andDo(document("save-post", // Rest Docs
requestFields(
fieldWithPath("title").type(JsonFieldType.STRING).description("post title"),
fieldWithPath("content").type(JsonFieldType.STRING).description("post content")
),
responseFields(
fieldWithPath("data.id").type(JsonFieldType.NUMBER).description("id"),
fieldWithPath("data.title").type(JsonFieldType.STRING).description("title"),
fieldWithPath("data.content").type(JsonFieldType.STRING).description("content"),
fieldWithPath("serverDateTime").type(JsonFieldType.STRING).description("server time")
)
));
}
target
디렉토리 하위의 generated-snippets
에 snippet들이 생성된다 document()
메소드의 인자로 preprocessXXXX(prettyPrint())
메소드를 전달해 해결할 수 있다 .andDo(document("save-post",
preprocessRequest(prettyPrint()), // 요청 포멧팅
preprocessResponse(prettyPrint()), // 응답 포멧팅
requestFields(
fieldWithPath("title").type(JsonFieldType.STRING).description("post title"),
fieldWithPath("content").type(JsonFieldType.STRING).description("post content")
),
responseFields(
fieldWithPath("data.id").type(JsonFieldType.NUMBER).description("id"),
fieldWithPath("data.title").type(JsonFieldType.STRING).description("title"),
fieldWithPath("data.content").type(JsonFieldType.STRING).description("content"),
fieldWithPath("serverDateTime").type(JsonFieldType.STRING).description("server time")
)
));
.adoc
이라는 확장자로 생성된것을 확인할 수 있다. Spring Rest Docs는 기본적으로 asciidoc을 사용해 문서화한다 // poject.src.docs.asciidocs 디렉토리에 작성한다
// index.asciidoc
:hardbreaks:
ifndef::snippets[]
:snippets: ../../../target/generated-snippets // snippet의 위치를 지정한다
endif::[]
== 게시판
== 게시글 생성
=== POST /posts
.Request
include::{snippets}/save-post/http-request.adoc[] // snippet을 사용
include::{snippets}/save-post/request-fields.adoc[] // snippet을 사용
.Response
include::{snippets}/save-post/http-response.adoc[] // snippet을 사용
include::{snippets}/save-post/response-fields.adoc[] // snippet을 사용
Asciidoctor is a fast, open source, Ruby-based text processor for parsing AsciiDoc® into a document model and converting it to output formats such as HTML 5, DocBook 5, manual pages, PDF, EPUB 3, and other formats.
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-asciidoctor</artifactId>
<version>${spring-restdocs.version}</version>
</dependency>
</dependencies>
</plugin>
targer/generated-docs
경로에 html 파일이 생성된다Spring Rest Docs : https://docs.spring.io/spring-restdocs/docs/current/reference/html5/
AsciiDoc : https://asciidoc-py.github.io/userguide.html