1. Overview
1.概述
Documentation is crucial for any piece of code that we intend to share with the world, especially if this code is relatively complex. Good API documentation not only attracts developers to use it but also shows the quality of the product. A company with sloppy written documentation might also have a sloppy written API.
对于我们打算与世界共享的任何代码而言,文档都是至关重要的,尤其是在代码相对复杂的情况下。良好的 API 文档不仅能吸引开发人员使用它,还能体现产品的质量。
However, developers like writing code for machines, not text for people.
然而,开发人员喜欢为机器编写代码,而不是为人编写文本。
In this tutorial, we’ll explore how to combine writing documentation and writing APIs with Spring REST Docs. We’ll take query parameter documentation as an example.
在本教程中,我们将探讨如何使用 Spring REST 文档将编写文档和编写 API 结合起来。我们将以查询参数文档为例。
2. API
2.应用程序接口
Let’s consider a straightforward API with a single endpoint:
让我们来看看一个只有一个端点的简单应用程序接口:
@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);
}
}
This endpoint returns a collection of books available on our website. However, due to the massive volume of the books available, we cannot return all of them. Clients provide a page number of our catalog, and we send them the information only for this page.
该端点返回我们网站上的图书集。但是,由于可用图书数量庞大,我们无法返回所有图书。客户提供我们目录的页码,我们只向他们发送该页的信息。
We decided to make this parameter required. In this case, it’s a default setup. This way, we improve the performance of our service and don’t allow clients to request too much data in one go.
我们决定将该参数设置为必填参数。在这种情况下,这是一个默认设置。这样,我们就能提高服务性能,避免客户一次性请求过多数据。
However, we must provide information about our decision and explain the rules clients should follow. In this case, the clients receive an error message if the parameters aren’t present.
不过,我们必须提供有关我们决定的信息,并解释客户应遵循的规则。在这种情况下,如果参数不存在,客户端会收到一条错误信息。
3. Documentation
3.文件
The usual approach to writing documentation is to write documentation, meaning that a developer has to write the same thing twice. First in the code and then in the text, explaining how to interact with the system. However, this is wasteful, and we cannot assume that all developers follow this.
编写文档的通常方法是撰写文档,这意味着开发人员必须把同一件事写两遍。首先在代码中,然后在文本中解释如何与系统交互。然而,这种做法是一种浪费,我们不能假定所有开发人员都会这样做。
Documentation is quite a formal document that aims for clarity rather than inspirational insights, clever wording, or innovative plot structure. Thus, why don’t we generate documentation from code? This way, we won’t write the same thing twice, and all the changes are reflected in the documentation.
文档是一种相当正式的文件,其目标是清晰明了,而不是富有启发性的见解、巧妙的措辞或创新的情节结构。因此,我们为什么不从代码中生成文档呢?这样,我们就不会重复写同样的东西,而且所有改动都会反映在文档中。
Spring REST Docs does precisely this. However, it generates the documentation not from the code, as it doesn’t provide much context but from the tests. This way, we can express quite complex cases and examples. Another benefit is that documentation won’t be generated if our tests fail.
Spring REST 文档正是这样做的。不过,它不是根据代码生成文档,因为代码没有提供太多上下文,而是根据测试生成文档。另一个好处是,如果测试失败,文档也不会生成。
4. Tests With Documentation
4.附带文件的测试
Spring REST Docs support major testing frameworks for REST testing. We’ll consider the example for MockMvc, WebTestClient, and REST-assured. However, the main idea and the structure would be similar for all of them.
Spring REST 文档支持用于 REST 测试的主要测试框架。 我们将考虑 MockMvc、WebTestClient 和 REST-assured的示例。不过,它们的主旨和结构都是相似的。
Also, we’ll be using JUnit 5 as the base for our test cases, but it’s possible to set up Spring REST Docs with JUnit 4.
此外,我们将使用 JUnit 5 作为测试用例的基础,但也可以通过 JUnit 4。
All of the testing methods below require an additional extension:
以下所有测试方法都需要额外扩展:
@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
These are special classes for documentation generation.
这些是用于生成文档的特殊类。
4.1. WebTestClient
4.1 WebTestClient</em
Let’s start with WebTestClient, a more modern REST testing method. As was mentioned previously, we need to extend the test class with additional extensions. Also, we need to configure it:
让我们从 WebTestClient 开始,这是一种更现代的 REST 测试方法。如前所述,我们需要使用额外的扩展来扩展测试类。此外,我们还需要对其进行配置:
@BeforeEach
public void setUp(ApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
this.webTestClient = WebTestClient.bindToApplicationContext(webApplicationContext)
.configureClient()
.filter(documentationConfiguration(restDocumentation))
.build();
}
After that, we can write a test that wouldn’t only check our API but also provide information about the request:
之后,我们就可以编写一个测试,它不仅会检查我们的 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 and MockMvc
4.2.WebMvcTest 和 MockMvc
In general, this method is very similar to the previous one. It also requires a correct setup:
总的来说,这种方法与前一种方法非常相似。它同样需要正确的设置:
@BeforeEach
public void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
this.mockMvc = webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation))
.alwaysDo(document("{method-name}", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
.build();
}
The test method looks the same, except for the fact that we’re using MockMvc and its API:
除了我们使用了 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
4.3.REST 保证
Lastly, let’s check an example with REST-assured. Because we need a running server for this one, we shouldn’t use @WebMvcTest or @AutoconfigureMockMvc. Here, we use @AutoconfigureWebMvc and also provide the correct port:
最后,让我们使用 REST-assured 检查一个示例。因为我们需要一个正在运行的服务器,所以不应该使用 @WebMvcTest 或 @AutoconfigureMockMvc。在这里,我们使用 @AutoconfigureWebMvc,并提供正确的端口:
@BeforeEach
void setUp(RestDocumentationContextProvider restDocumentation, @LocalServerPort int port) {
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(restDocumentation))
.setPort(port)
.build();
}
However, the tests look generally the same:
不过,这些测试看起来大致相同:
@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. Generated Documentation
5.生成文件
However, at this point, we still don’t have a generated documentation. To get the result, we need to go through additional steps.
然而,此时我们仍然没有生成文档。要得到结果,我们还需要经过更多的步骤。
5.1. Generated Snippets
5.1.生成的片段
We can find generated snippets in the target folder after running our tests. However, we can configure the output directory to define a different place to store the snippets. In general, they look like this:
运行测试后,我们可以在目标文件夹中找到生成的片段。不过,我们可以配置输出目录,定义不同的片段存储位置。一般来说,它们看起来是这样的
[source,bash]
----
$ curl 'http://localhost:8080/books?page=2' -i -X GET
----
At the same time, we can see the information about our parameters, which is stored in a .adoc file.
同时,我们还可以查看保存在 .adoc 文件中的参数信息。
|===
|Parameter|Description
|`+page+`
|The page to retrieve
|===
5.2. Documentation Generation
5.2.文件生成
The next step is to provide a configuration for AsciiDoctor to create an HTML with more readable documentation. AsciiDoc is a simple yet powerful markup language. We can use it for various purposes, such as generating HTML and PDFs or writing a book.
下一步是为 AsciiDoctor 提供 配置,以创建更具可读性的 HTML 文档。AsciiDoc是一种简单而强大的标记语言。我们可以将它用于多种用途,例如生成 HTML 和 PDF 文件或 撰写书籍。
Thus, as we want to generate HTML documentation, we need to outline the template for our HTML:
因此,既然我们要生成 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[]
In our case, we use a simple format, but it’s possible to create a more elaborate custom format that would be appealing and informative. The flexibility of AsciiDoc helps us with this.
在我们的案例中,我们使用的是一种简单的格式,但也可以创建一种更复杂的自定义格式,这样既吸引人又能提供更多信息。AsciiDoc 的灵活性可以帮助我们做到这一点。
5.3. Generated HTML
5.3.生成 HTML
After correct setup and configuration, when we can attach the generation goal to a Maven phase:
正确设置和配置后,我们就可以将生成目标附加到 Maven 阶段:
<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>
We can run a required mvn command and trigger the generation. The template we defined in the previous section renders the following HTML:
我们可以运行所需的 mvn 命令并触发生成。我们在上一节中定义的模板会生成以下 HTML:
We can attach this process to our pipeline and always have relevant and correct documentation. Another benefit is that this process reduces manual work, which is wasteful and error-prone.
我们可以将这一流程附加到我们的流水线上,并始终拥有相关的正确文档。另一个好处是,这一流程减少了人工工作,而人工工作既浪费又容易出错。
6. Conclusion
6.结论
Documentation is an essential part of software. The developers acknowledge this, but only a few consistently write or maintain it. Spring REST Docs allow us to generate good documentation with minimal effort based on the code rather than our understanding of what API should do.
文档是软件的重要组成部分。开发人员都承认这一点,但只有少数人坚持编写或维护文档。通过 Spring REST 文档,我们可以根据代码而不是我们对 API 应该做什么的理解,以最小的工作量生成优秀的文档。
As usual, all the code from this tutorial is available over on GitHub.
与往常一样,本教程中的所有代码都可以在 GitHub 上获取。