Spring REST Docs vs OpenAPI

REST Top

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO

1. Panoramica

Spring REST Docs e OpenAPI 3.0 sono due modi per creare documentazione API per un'API REST.

In questo tutorial, esamineremo i relativi vantaggi e svantaggi.

2. Un breve riassunto delle origini

Spring REST Docs è un framework sviluppato dalla comunità Spring per creare una documentazione accurata per le API RESTful. Adotta un approccio basato sui test, in cui la documentazione è scritta come test Spring MVC, WebTestClient di Spring Webflux o REST-Assured.

L'output dell'esecuzione dei test viene creato come file AsciiDoc che possono essere assemblati utilizzando Asciidoctor per generare una pagina HTML che descrive le nostre API. Poiché segue il metodo TDD, Spring REST Docs offre automaticamente tutti i suoi vantaggi come codice meno soggetto a errori, rielaborazione ridotta e cicli di feedback più rapidi, solo per citarne alcuni.

OpenAPI, d'altra parte, è una specifica nata da Swagger 2.0. La sua ultima versione al momento in cui scrivo è 3.0 e ha molte implementazioni note.

Come qualsiasi altra specifica, OpenAPI stabilisce alcune regole di base per le sue implementazioni da seguire. In poche parole, tutte le implementazioni OpenAPI dovrebbero produrre la documentazione come oggetto JSON, in formato JSON o YAML .

Esistono anche molti strumenti che prendono questo JSON / YAML e sputano un'interfaccia utente per visualizzare e navigare nell'API. Ciò è utile durante i test di accettazione, ad esempio. Nei nostri esempi di codice qui, useremo springdoc , una libreria per OpenAPI 3 con Spring Boot.

Prima di esaminare i due in dettaglio, configuriamo rapidamente un'API da documentare.

3. L'API REST

Mettiamo insieme un'API CRUD di base utilizzando Spring Boot.

3.1. Il repository

Qui, il repository che useremo è un'interfaccia semplice PagingAndSortingRepository , con il modello 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 }

Caricheremo anche il repository utilizzando uno schema.sql e un data.sql .

3.2. Il controller

Successivamente, esaminiamo il controller, saltando i dettagli di implementazione per brevità:

@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'applicazione

E infine, l'app di avvio:

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

4. OpenAPI / Springdoc

Ora vediamo come springdoc può aggiungere documentazione alla nostra API REST di Foo .

Ricorda che genererà un oggetto JSON e una visualizzazione dell'interfaccia utente dell'API basata su quell'oggetto .

4.1. Interfaccia utente di base

Per cominciare, aggiungeremo solo un paio di dipendenze Maven: springdoc-openapi-data-rest per la generazione di JSON e springdoc-openapi-ui per il rendering dell'interfaccia utente.

Lo strumento analizzerà il codice per la nostra API e leggerà le annotazioni dei metodi del controller. Su questa base, genererà l'API JSON che sarà disponibile su // localhost: 8080 / api-docs / . Servirà anche un'interfaccia utente di base su //localhost:8080/swagger-ui-custom.html :

Come possiamo vedere, senza aggiungere alcun codice, abbiamo ottenuto una bella visualizzazione della nostra API, fino allo schema Foo . Utilizzando il pulsante Prova , possiamo anche eseguire le operazioni e visualizzare i risultati.

E se volessimo aggiungere una documentazione reale all'API? In termini di cosa tratta l'API, cosa significano tutte le sue operazioni, cosa dovrebbe essere input e quali risposte aspettarsi?

Vedremo questo nella prossima sezione.

4.2. Interfaccia utente dettagliata

Vediamo prima come aggiungere una descrizione generale all'API.

Per questo, aggiungeremo un bean OpenAPI alla nostra app di avvio:

@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"))); } 

Successivamente, per aggiungere alcune informazioni alle nostre operazioni API, decoreremo le nostre mappature con alcune annotazioni specifiche di OpenAPI.

Vediamo come possiamo descrivere getFooById. Lo faremo all'interno di un altro controller, FooBarController , che è simile al nostro 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 } 

Ora vediamo l'effetto sull'interfaccia utente:

Quindi, con queste configurazioni minime, l'utente della nostra API può ora vedere di cosa si tratta, come usarla e quali risultati aspettarsi. Tutto quello che dovevamo fare era compilare il codice ed eseguire l'app di avvio.

5. Spring REST Docs

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.

Sono inoltre necessari più passaggi per generare l'HTML finale, eseguendo prima il test e poi il plug-in. Springdoc ci ha richiesto solo di eseguire l'app di avvio.

7. Conclusione

In questo tutorial, abbiamo esaminato le differenze tra springdoc basato su OpenAPI e Spring REST Docs. Abbiamo anche visto come implementare i due per generare la documentazione per un'API CRUD di base.

In sintesi, entrambi hanno i loro pro e contro e la decisione di utilizzare l'uno sull'altro è soggetta ai nostri requisiti specifici.

Come sempre, il codice sorgente è disponibile su GitHub.

REST fondo

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO