Docs Spring REST vs OpenAPI

Haut REST

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS

1. Vue d'ensemble

Spring REST Docs et OpenAPI 3.0 sont deux façons de créer une documentation API pour une API REST.

Dans ce didacticiel, nous examinerons leurs avantages et inconvénients relatifs.

2. Bref résumé des origines

Spring REST Docs est un framework développé par la communauté Spring afin de créer une documentation précise pour les API RESTful. Il adopte une approche axée sur les tests, dans laquelle la documentation est écrite sous forme de tests Spring MVC, WebTestClient de Spring Webflux ou REST-Assured.

La sortie de l'exécution des tests est créée sous forme de fichiers AsciiDoc qui peuvent être assemblés à l'aide d'Asciidoctor pour générer une page HTML décrivant nos API. Puisqu'il suit la méthode TDD, Spring REST Docs apporte automatiquement tous ses avantages tels qu'un code moins sujet aux erreurs, des retouches réduites et des cycles de rétroaction plus rapides, pour n'en nommer que quelques-uns.

OpenAPI, d'autre part, est une spécification née de Swagger 2.0. Sa dernière version au moment de l'écriture est la 3.0 et a de nombreuses implémentations connues.

Comme toute autre spécification, OpenAPI définit certaines règles de base pour ses implémentations à suivre. En termes simples, toutes les implémentations d'OpenAPI sont censées produire la documentation sous forme d'objet JSON, au format JSON ou YAML .

Il existe également de nombreux outils qui prennent ce JSON / YAML et crachent une interface utilisateur pour visualiser et naviguer dans l'API. Cela est utile lors des tests d'acceptation, par exemple. Dans nos exemples de code ici, nous utiliserons springdoc - une bibliothèque pour OpenAPI 3 avec Spring Boot.

Avant d'examiner les deux en détail, configurons rapidement une API à documenter.

3. L'API REST

Créons une API CRUD de base à l'aide de Spring Boot.

3.1. Le référentiel

Ici, le référentiel que nous allons utiliser est une interface PagingAndSortingRepository simple , avec le modèle Foo :

@Repository public interface FooRepository extends PagingAndSortingRepository{} @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 }

Nous chargerons également le référentiel en utilisant un schema.sql et un data.sql .

3.2. Le controlle

Ensuite, regardons le contrôleur, en sautant ses détails d'implémentation par souci de concision:

@RestController @RequestMapping("/foo") public class FooController { @Autowired FooRepository repository; @GetMapping public ResponseEntity
    
      getAllFoos() { // implementation } @GetMapping(value = "{id}") public ResponseEntity getFooById(@PathVariable("id") Long id) { // implementation } @PostMapping public ResponseEntity addFoo(@RequestBody @Valid Foo foo) { // implementation } @DeleteMapping("/{id}") public ResponseEntity deleteFoo(@PathVariable("id") long id) { // implementation } @PutMapping("/{id}") public ResponseEntity updateFoo(@PathVariable("id") long id, @RequestBody Foo foo) { // implementation } }
    

3.3. L'application

Et enfin, l'application Boot:

@SpringBootApplication() public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

4. OpenAPI / Springdoc

Voyons maintenant comment springdoc peut ajouter de la documentation à notre API Foo REST.

Rappelez- vous qu'il générera un objet JSON et une visualisation de l'interface utilisateur de l'API en fonction de cet objet .

4.1. Interface utilisateur de base

Pour commencer, nous allons simplement ajouter quelques dépendances Maven - springdoc-openapi-data-rest pour générer le JSON et springdoc-openapi-ui pour le rendu de l'interface utilisateur.

L'outil introspectera le code de notre API et lira les annotations des méthodes du contrôleur. Sur cette base, il générera l'API JSON qui sera en direct sur // localhost: 8080 / api-docs / . Il servira également une interface utilisateur de base à //localhost:8080/swagger-ui-custom.html :

Comme nous pouvons le voir, sans ajouter de code du tout, nous avons obtenu une belle visualisation de notre API, jusqu'au schéma Foo . En utilisant le bouton Try it out , nous pouvons même exécuter les opérations et afficher les résultats.

Maintenant, que faire si nous voulions ajouter une vraie documentation à l'API? En termes de ce qu'est l'API, que signifient toutes ses opérations, que faut-il entrer et à quelles réponses s'attendre?

Nous examinerons cela dans la section suivante.

4.2. UI détaillée

Voyons d'abord comment ajouter une description générale à l'API.

Pour cela, nous allons ajouter un bean OpenAPI à notre application de démarrage:

@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("//swagger.io/terms/") .license(new License().name("Apache 2.0") .url("//springdoc.org"))); } 

Ensuite, pour ajouter des informations à nos opérations API, nous décorerons nos mappages avec quelques annotations spécifiques à OpenAPI.

Voyons comment nous pouvons décrire getFooById. Nous allons le faire dans un autre contrôleur, FooBarController , qui est similaire à notre 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 } 

Voyons maintenant l'effet sur l'interface utilisateur:

Ainsi, avec ces configurations minimales, l'utilisateur de notre API peut désormais voir de quoi il s'agit, comment l'utiliser et à quels résultats s'attendre. Tout ce que nous avions à faire était de compiler le code et d'exécuter l'application de démarrage.

5. Documentation 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.

In our example here, we'll be using Spring MVC Tests to create documentation snippets.

At the outset, we'll need to add the spring-restdocs-mockmvc dependency and the asciidoc Maven plugin to our pom.

5.1. The JUnit5 Test

Now let's have a look at the JUnit5 test which includes our documentation:

@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.

Here's a sample http-response.adoc, of course containing the response body:

[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

Now we need a master file that will weave all these snippets together to form a well-structured HTML.

Let's call it fooapi.adoc and see a small portion of it:

=== 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.

And this is how it'll look when opened in a browser:

6. Key Takeaways

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.

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?

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.

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.

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.

Il faut également plus d'étapes pour générer le HTML final - en exécutant d'abord le test, puis le plugin. Springdoc nous demandait uniquement d'exécuter l'application de démarrage.

7. Conclusion

Dans ce didacticiel, nous avons examiné les différences entre Springdoc basé sur OpenAPI et Spring REST Docs. Nous avons également vu comment implémenter les deux pour générer de la documentation pour une API CRUD de base.

En résumé, les deux ont leurs avantages et leurs inconvénients, et la décision d'utiliser l'un plutôt que l'autre est soumise à nos exigences spécifiques.

Comme toujours, le code source est disponible sur sur GitHub.

REST bas

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS