1. Overview
1.概述
Spring REST Docs and OpenAPI 3.0 are two ways to create API documentation for a REST API.
Spring REST Docs和OpenAPI 3.0是为REST API创建API文档的两种方式。
In this tutorial, we’ll examine their relative advantages and disadvantages.
在本教程中,我们将研究它们的相对优势和劣势。
2. A Brief Summary of Origins
2.起源简述
Spring REST Docs is a framework developed by the Spring community in order to create accurate documentation for RESTful APIs. It takes a test-driven approach, wherein the documentation is written either as Spring MVC tests, Spring Webflux’s WebTestClient, or REST-Assured.
Spring REST Docs是由Spring社区开发的一个框架,目的是为RESTful API创建准确的文档。它采用了测试驱动的方法,其中,文档被写成Spring MVC测试、Spring Webflux的WebTestClient或REST-Assured。
The output of running the tests is created as AsciiDoc files which can be put together using Asciidoctor to generate an HTML page describing our APIs. Since it follows the TDD method, Spring REST Docs automatically brings in all its advantages such as less error-prone code, reduced rework, and faster feedback cycles, to name a few.
运行测试的输出被创建为AsciiDoc文件,可以使用Asciidoctor将其组合起来,生成描述我们的API的HTML页面。由于遵循TDD方法,Spring REST Docs自动带来了其所有的优势,例如更少的错误代码、减少的返工以及更快的反馈周期,等等。
OpenAPI, on the other hand, is a specification born out of Swagger 2.0. Its latest version as of writing this is 3.0 and has many known implementations.
OpenAPI则是诞生于Swagger 2.0的一个规范。截至本文撰写时,其最新版本为3.0,并且有许多已知的实施。
As any other specification would, OpenAPI lays out certain ground rules for its implementations to follow. Simply put, all OpenAPI implementations are supposed to produce the documentation as a JSON object, either in JSON or YAML format.
就像其他规范一样,OpenAPI为其实现者制定了一些基本规则。简单地说,所有OpenAPI的实现都应该将文档生成为JSON对象,可以是JSON格式,也可以是YAML格式。
There also exist many tools that take this JSON/YAML in and spit out a UI to visualize and navigate the API. This comes in handy during acceptance testing, for example. In our code samples here, we’ll be using springdoc – a library for OpenAPI 3 with Spring Boot.
此外,还有许多工具可以接收这些JSON/YAML,并吐出一个UI来可视化和导航API。例如,这在验收测试中非常有用。在我们的代码示例中,我们将使用springdoc – 一个用于OpenAPI 3和Spring Boot的库。
Before looking at the two in detail, let’s quickly set up an API to be documented.
在详细研究这两者之前,让我们快速设置一个要记录的API。
3. The REST API
3.REST API
Let’s put together a basic CRUD API using Spring Boot.
让我们用Spring Boot来拼凑一个基本的CRUD API。
3.1. The Repository
3.1.存储库
Here, the repository that we’ll be using is a bare-bones PagingAndSortingRepository interface, with the model Foo:
在这里,我们要使用的存储库是一个原始的PagingAndSortingRepository接口,模型为Foo。
@Repository
public interface FooRepository extends PagingAndSortingRepository<Foo, Long>{}
@Entity
public class Foo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(nullable = false)
private String title;
@Column()
private String body;
// constructor, getters and setters
}
We’ll also load the repository using a schema.sql and a data.sql.
我们还将使用schema.sql和data.sql加载版本库。
3.2. The Controller
3.2.控制器
Next, let’s look at the controller, skipping its implementation details for brevity:
接下来,让我们看看控制器,为了简洁起见,跳过其实现细节。
@RestController
@RequestMapping("/foo")
public class FooController {
@Autowired
FooRepository repository;
@GetMapping
public ResponseEntity<List<Foo>> getAllFoos() {
// implementation
}
@GetMapping(value = "{id}")
public ResponseEntity<Foo> getFooById(@PathVariable("id") Long id) {
// implementation
}
@PostMapping
public ResponseEntity<Foo> addFoo(@RequestBody @Valid Foo foo) {
// implementation
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteFoo(@PathVariable("id") long id) {
// implementation
}
@PutMapping("/{id}")
public ResponseEntity<Foo> updateFoo(@PathVariable("id") long id, @RequestBody Foo foo) {
// implementation
}
}
3.3. The Application
3.3.应用
And finally, the Boot App:
最后,启动应用程序。
@SpringBootApplication()
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4. OpenAPI / Springdoc
4.OpenAPI / Springdoc
Now let’s see how springdoc can add documentation to our Foo REST API.
现在让我们看看springdoc如何为我们的Foo REST API添加文档。
Recall that it’ll generate a JSON object and a UI visualization of the API based on that object.
回顾一下,它将生成一个JSON对象和一个基于该对象的API的UI可视化。
4.1. Basic UI
4.1.基本用户界面
To begin with, we’ll just add a couple of Maven dependencies – springdoc-openapi-data-rest for generating the JSON, and springdoc-openapi-ui for rendering the UI.
首先,我们只需添加几个Maven依赖项–springdoc-openapi-data-rest用于生成JSON,springdoc-openapi-ui用于渲染用户界面。
The tool will introspect the code for our API, and read the controller methods’ annotations. On that basis, it’ll generate the API JSON which will be live at http://localhost:8080/api-docs/. It’ll also serve a basic UI at http://localhost:8080/swagger-ui-custom.html:
该工具将对我们的API代码进行内省,并读取控制器方法的注释。在此基础上,它将生成API JSON,并在http://localhost:8080/api-docs/上运行。它还将在http://localhost:8080/swagger-ui-custom.html提供一个基本的用户界面。
As we can see, without adding any code at all, we obtained a beautiful visualization of our API, right down to the Foo schema. Using the Try it out button, we can even execute the operations and view the results.
正如我们所看到的,在没有添加任何代码的情况下,我们获得了一个漂亮的可视化的API,直到Foo模式。使用Try it out按钮,我们甚至可以执行这些操作并查看结果。
Now, what if we wanted to add some real documentation to the API? In terms of what the API is all about, what all its operations mean, what should be input, and what responses to expect?
现在,如果我们想为API添加一些真正的文档呢?就API的内容而言,它的所有操作意味着什么,应该输入什么,以及期望得到什么响应?
We’ll look at this in the next section.
我们将在下一节看这个问题。
4.2. Detailed UI
4.2.详细的用户界面
Let’s first see how to add a general description to the API.
让我们先看看如何为API添加一般描述。
For that, we’ll add an OpenAPI bean to our Boot App:
为此,我们将向我们的Boot App添加一个OpenAPIbean。
@Bean
public OpenAPI customOpenAPI(@Value("${springdoc.version}") String appVersion) {
return new OpenAPI().info(new Info()
.title("Foobar API")
.version(appVersion)
.description("This is a sample Foobar server created using springdocs - " +
"a library for OpenAPI 3 with spring boot.")
.termsOfService("http://swagger.io/terms/")
.license(new License().name("Apache 2.0")
.url("http://springdoc.org")));
}
Next, to add some information to our API operations, we’ll decorate our mappings with a few OpenAPI-specific annotations.
接下来,为了给我们的API操作添加一些信息,我们将用一些OpenAPI特有的注解来装饰我们的映射。
Let’s see how we can describe getFooById. We’ll do this inside another controller, FooBarController, which is similar to our FooController:
让我们看看如何描述getFooById.我们将在另一个控制器FooBarController中进行,它与我们的FooController类似。
@RestController
@RequestMapping("/foobar")
@Tag(name = "foobar", description = "the foobar API with documentation annotations")
public class FooBarController {
@Autowired
FooRepository repository;
@Operation(summary = "Get a foo by foo id")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "found the foo", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = Foo.class))}),
@ApiResponse(responseCode = "400", description = "Invalid id supplied", content = @Content),
@ApiResponse(responseCode = "404", description = "Foo not found", content = @Content) })
@GetMapping(value = "{id}")
public ResponseEntity getFooById(@Parameter(description = "id of foo to be searched")
@PathVariable("id") String id) {
// implementation omitted for brevity
}
// other mappings, similarly annotated with @Operation and @ApiResponses
}
Now let’s see the effect on the UI:
现在让我们看看对用户界面的影响。
So with these minimal configurations, the user of our API can now see what it’s about, how to use it, and what results to expect. All we had to do was compile the code and run the Boot App.
因此,通过这些最低限度的配置,我们的API的用户现在可以看到它是什么,如何使用它,以及期待什么结果。我们所要做的就是编译代码和运行Boot App。
5. Spring REST Docs
5.Spring REST文档
REST docs is a totally different take on API documentation. As described earlier, the process is test-driven, and the output is in the form of a static HTML page.
REST docs是对API文档的一种完全不同的看法。如前所述,该过程是测试驱动的,输出是静态HTML页面的形式。
In our example here, we’ll be using Spring MVC Tests to create documentation snippets.
在我们这里的例子中,我们将使用Spring MVC测试来创建文档片段。
At the outset, we’ll need to add the spring-restdocs-mockmvc dependency and the asciidoc Maven plugin to our pom.
首先,我们需要将spring-restdocs-mockmvc依赖关系和asciidoc Maven插件添加到我们的pom。
5.1. The JUnit5 Test
5.1.JUnit5测试
Now let’s have a look at the JUnit5 test which includes our documentation:
现在让我们来看看JUnit5的测试,其中包括我们的文档。
@ExtendWith({ RestDocumentationExtension.class, SpringExtension.class })
@SpringBootTest(classes = Application.class)
public class SpringRestDocsIntegrationTest {
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
public void setup(WebApplicationContext webApplicationContext,
RestDocumentationContextProvider restDocumentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation))
.build();
}
@Test
public void whenGetFooById_thenSuccessful() throws Exception {
ConstraintDescriptions desc = new ConstraintDescriptions(Foo.class);
this.mockMvc.perform(get("/foo/{id}", 1))
.andExpect(status().isOk())
.andDo(document("getAFoo", preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
pathParameters(parameterWithName("id").description("id of foo to be searched")),
responseFields(fieldWithPath("id")
.description("The id of the foo" +
collectionToDelimitedString(desc.descriptionsForProperty("id"), ". ")),
fieldWithPath("title").description("The title of the foo"),
fieldWithPath("body").description("The body of the foo"))));
}
// more test methods to cover other mappings
}
}
After running this test, we get several files in our targets/generated-snippets directory with information about the given API operation. Particularly, whenGetFooById_thenSuccessful will give us eight adocs in a getAFoo folder in the directory.
运行这个测试后,我们会在targets/generated-snippets目录下得到几个文件,其中有关于给定API操作的信息。特别是,whenGetFooById_thenSuccessful会在目录中的getAFoo文件夹中给我们提供八个adoc。
Here’s a sample http-response.adoc, of course containing the response body:
这里有一个样本http-response.adoc,当然包含响应体。
[source,http,options="nowrap"]
----
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 60
{
"id" : 1,
"title" : "Foo 1",
"body" : "Foo body 1"
}
----
5.2. fooapi.adoc
5.2. fooapi.adoc
Now we need a master file that will weave all these snippets together to form a well-structured HTML.
现在我们需要一个主文件,将所有这些片段编织在一起,形成一个结构良好的HTML。
Let’s call it fooapi.adoc and see a small portion of it:
让我们称它为fooapi.adoc,并看看它的一小部分。
=== Accessing the foo GET
A `GET` request is used to access the foo read.
==== Request structure
include::{snippets}/getAFoo/http-request.adoc[]
==== Path Parameters
include::{snippets}/getAFoo/path-parameters.adoc[]
==== Example response
include::{snippets}/getAFoo/http-response.adoc[]
==== CURL request
include::{snippets}/getAFoo/curl-request.adoc[]
After executing the asciidoctor-maven-plugin, we get the final HTML file fooapi.html in the target/generated-docs folder.
执行asciidoctor-maven-plugin后,我们在target/generated-docs文件夹中得到最终的HTML文件fooapi.html。
And this is how it’ll look when opened in a browser:
这是在浏览器中打开时的样子。
6. Key Takeaways
6.主要收获
Now that we’ve looked at both the implementations, let’s summarize the advantages and disadvantages.
现在我们已经看了这两种实现方式,让我们总结一下其优缺点。
With springdoc, the annotations we had to use cluttered our rest controller’s code and reduced its readability. Also, the documentation was tightly coupled to the code and would make its way into production.
使用springdoc时,我们必须使用的注解使我们的休息控制器的代码杂乱无章,并降低了其可读性。此外,这些文档与代码紧密相连,并将进入生产。
Needless to say, maintaining the documentation is another challenge here – if something in the API changed, would the programmer always remember to update the corresponding OpenAPI annotation?
不用说,维护文档是这里的另一个挑战–如果API中的某些东西改变了,程序员会一直记得更新相应的OpenAPI注释吗?
On the other hand, REST Docs neither looks as catchy as the other UI did nor can it be used for acceptance testing. But it has its advantages.
另一方面,REST Docs既不像其他UI那样看起来很吸引人,也不能用于验收测试。但它也有其优点。
Notably, the successful completion of the Spring MVC test not only gives us the snippets but also verifies our API as any other unit test would. This forces us to make documentation changes corresponding to API modifications if any. Also, the documentation code is completely separate from the implementation.
值得注意的是,成功完成Spring MVC测试不仅给了我们片段,而且还像其他单元测试一样验证了我们的API。这就迫使我们对API的修改进行相应的文档修改,如果有的话。另外,文档代码与实现完全分开。
But again, on the flip side, we had to write more code to generate the documentation. First, the test itself which is arguably as verbose as the OpenAPI annotations, and second, the master adoc.
但是,从另一个角度看,我们不得不写更多的代码来生成文档。首先,测试本身可以说和OpenAPI注释一样冗长,其次,主adoc。
It also needs more steps to generate the final HTML – running the test first and then the plugin. Springdoc only required us to run the Boot App.
它还需要更多的步骤来生成最终的HTML–先运行测试,然后再运行插件。 Springdoc只要求我们运行Boot App。
7. Conclusion
7.结语
In this tutorial, we looked at the differences between the OpenAPI based springdoc and Spring REST Docs. We also saw how to implement the two to generate documentation for a basic CRUD API.
在本教程中,我们研究了基于OpenAPI的springdoc和Spring REST Docs之间的区别。我们还看到了如何实现这两者来为基本的CRUD API生成文档。
In summary, both have their pros and cons, and the decision of using one over the other is subject to our specific requirements.
总之,两者都有其优点和缺点,决定使用其中一个还是另一个,要视我们的具体要求而定。
As always, source code is available over on GitHub.
一如既往,源代码可在GitHub上获取。