使用 Spring REST Docs 生成 API 文档

1、概览

API 文档在团队开发中极其重要,特别是在 API 接口及其复杂的情况下,良好的 API 文档不仅能提升开发效率,还能显示产品的质量。如果一家公司的 API 文档写得马马虎虎,那么它的 API 也可能写得马马虎虎。

程序员都讨厌写自己文档和别人不写文档

本文将带你了解如何使用 Spring REST Docs 自动地生成 API 文档。

2、API 接口

一个简单 API 如下:

@RestController
@RequestMapping("/books")
public class BookController {
    private final BookService service;

    public BookController(BookService service) {
        this.service = service;
    }

    @GetMapping
    public List<Book> getBooks(@RequestParam(name = "page") Integer page) {
        return service.getBooks(page);
    }
}

该 API 返回系统中的 Book 数据。不过,由于可用图书数量庞大,不可能一次性返回所有。所以给客户端提供了一个查询参数 page,用于控制数据分页。

而且,需要把该参数设置为必须参数(默认就是必须参数),避免客户端一次性请求过多数据。如果客户端未提交该查询参数,就会收到状态为 400 的错误提示。

3、文档

编写文档的通常方法是 “手写文档”,这意味着开发人员必须把同一件事写两遍。首先写代码,然后再写对应的文档。太麻烦!

文档是一种相当正式的文件,其目标是清晰明了,并不需要太多花里胡哨的东西。因此,我们可以根据代码生成文档,这样就不用重复写同样的东西,而且所有的改动都会反映在文档中。

我们可以通过 Spring REST Docs 来生成 API 文档。不过,它不是从代码中生成文档,因为代码没有提供太多上下文,而是从测试中生成文档。这可以表达相当复杂的案例和示例。另一个好处是,如果测试失败,文档也不会生成。

4、测试

Spring REST Docs 支持常用的 REST 测试框架。如 MockMvcWebTestClientREST-assured,然而,无论使用哪种测试框架,主要的思想和结构都是相似的。

本文使用 JUnit 5 作为测试用例的基础,但也可以在 JUnit 4 中设置 Spring REST Docs。

以下所有测试方法都需要额外扩展(Extensio):

@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})

这都是用于生成文档的特殊类。

4.1、WebTestClient

首先从 WebTestClient 开始,这是一种更现代的 REST 测试方法。

如前所述,需要用额外的扩展来扩展测试类。此外,还需要对其进行配置:

@BeforeEach
public void setUp(ApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
    this.webTestClient = WebTestClient.bindToApplicationContext(webApplicationContext)
      .configureClient()
      .filter(documentationConfiguration(restDocumentation))
      .build();
}

之后,就可以编写一个测试,它不仅会测试我们的 API,还会提供有关请求的信息:

@Test
@WithMockUser
void givenEndpoint_whenSendGetRequest_thenSuccessfulResponse() {
    webTestClient.get().uri("/books?page=2")
      .exchange().expectStatus().isOk().expectBody()
      .consumeWith(document("books",
        requestParameters(parameterWithName("page").description("The page to retrieve"))));
}

4.2、WebMvcTest 和 MockMvc

这种方法与上一种方法非常相似。它同样需要正确的设置:

@BeforeEach
public void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
    this.mockMvc = webAppContextSetup(webApplicationContext)
      .apply(documentationConfiguration(restDocumentation))
      .alwaysDo(document("{method-name}", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
      .build();
}

除了使用了 MockMvc 及其 API 之外,测试方法看起来是一样的:

@Test
void givenEndpoint_whenSendGetRequest_thenSuccessfulResponse() throws Exception {
    mockMvc.perform(get("/books?page=2"))
      .andExpect(status().isOk())
      .andDo(document("books",
        requestParameters(parameterWithName("page").description("The page to retrieve"))));
}

4.3、REST-assured

最后,来看看使用 REST-assured 的示例。由于我们需要一个正在运行的服务器,因此不能使用 @WebMvcTest@AutoconfigureMockMvc

这里,我们使用 @AutoconfigureWebMvc,并提供正确的端口:

@BeforeEach
void setUp(RestDocumentationContextProvider restDocumentation, @LocalServerPort int port) {
    this.spec = new RequestSpecBuilder()
      .addFilter(documentationConfiguration(restDocumentation))
      .setPort(port)
      .build();
}

这些测试看起来大致相同:

@Test
@WithMockUser
void givenEndpoint_whenSendGetRequest_thenSuccessfulResponse() {
    RestAssured.given(this.spec).filter(document("users", requestParameters(
        parameterWithName("page").description("The page to retrieve"))))
      .when().get("/books?page=2")
      .then().assertThat().statusCode(is(200));
}

5、生成文档

此时,文档还没生成。还有几个步骤需要完成。

5.1、生成文档片段

运行测试后,可以在 target 文件夹中找到生成的片段(也可以配置输出的目录),看起来如下:

[source,bash]
----
$ curl 'http://localhost:8080/books?page=2' -i -X GET
----

同时,还可以查看保存在 .adoc 文件中的参数信息。

|===
|Parameter|Description

|`+page+`
|The page to retrieve

|===

5.2、文档生成

下一步是为 AsciiDoctor 提供一个配置,以创建可读性更强的 HTML 文档。AsciiDoc 是一种简单而强大的标记语言。可用于各种用途,例如生成 HTML 和 PDF 文件或撰写书籍。

由于我们想生成 HTML 文档,因此需要为 HTML 提供模板大纲:

= Books With Spring REST Docs

How you should interact with our bookstore:

.request
include::{snippets}/books/http-request.adoc[]

.request-parameters
include::{snippets}/books/request-parameters.adoc[]

.response
include::{snippets}/books/http-response.adoc[]

在本例中,使用的是一种简单的格式,但也可以创建一种更复杂的自定义格式,这样既吸引人又能提供更多信息。AsciiDoc 非常灵活。

5.3、生成 HTML

正确设置和配置后,就可以将 generation goal 添加到 Maven phase:

<executions>
    <execution>
        <id>generate-docs</id>
        <phase>package</phase>
        <goals>
            <goal>process-asciidoc</goal>
        </goals>
        <configuration>
            <backend>html</backend>
            <doctype>book</doctype>
            <attributes>
                <snippets>${snippetsDirectory}</snippets>
            </attributes>
            <sourceDirectory>src/docs/asciidocs</sourceDirectory>
            <outputDirectory>target/generated-docs</outputDirectory>
        </configuration>
    </execution>
</executions>

运行 mvn 命令并触发生成,我们在上一节中定义的模板会生成以下 HTML:

Spring REST Docs 生成的 HTML 文档

可以将这一流程添加到 CI/DI Pipeline 上,每一次构建都会生成最新的文档。另一个好处是,这一流程减少了人工操作,而人工操作既费时又容易出错。

6、总结

文档是软件的重要组成部分,大家都知道,但是大家都不喜欢写文档。通过 Spring REST Docs,我们可以根据代码,以最小的工作量生成优秀的 API 文档。


Ref:https://www.baeldung.com/spring-rest-document-query-parameters