Spring REST Docs versus OpenAPI

REST Top

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

1. Overzicht

Spring REST Docs en OpenAPI 3.0 zijn twee manieren om API-documentatie te maken voor een REST API.

In deze tutorial zullen we hun relatieve voor- en nadelen onderzoeken.

2. Een korte samenvatting van de oorsprong

Spring REST Docs is een raamwerk dat is ontwikkeld door de Spring-community om nauwkeurige documentatie voor RESTful API's te creëren. Er is een testgestuurde aanpak voor nodig, waarin de documentatie is geschreven als Spring MVC-tests, Spring Webflux's WebTestClient, of REST-Assured.

De uitvoer van het uitvoeren van de tests wordt gemaakt als AsciiDoc-bestanden die kunnen worden samengesteld met Asciidoctor om een ​​HTML-pagina te genereren die onze API's beschrijft. Omdat het de TDD-methode volgt, brengt Spring REST Docs automatisch al zijn voordelen met zich mee zoals minder foutgevoelige code, minder nabewerking en snellere feedbackcycli, om er maar een paar te noemen.

OpenAPI, aan de andere kant, is een specificatie die voortkomt uit Swagger 2.0. De nieuwste versie op het moment van schrijven is 3.0 en heeft veel bekende implementaties.

Zoals elke andere specificatie, legt OpenAPI bepaalde basisregels uit voor de implementaties die moeten worden gevolgd. Simpel gezegd, allemaal OpenAPI-implementaties worden verondersteld de documentatie te produceren als een JSON-object, in JSON- of YAML-indeling.

Er zijn ook veel tools die deze JSON / YAML opnemen en een gebruikersinterface uitspugen om de API te visualiseren en er doorheen te navigeren. Dit komt bijvoorbeeld goed van pas bij acceptatietesten. In onze codevoorbeelden hier gebruiken we springdoc - een bibliotheek voor OpenAPI 3 met Spring Boot.

Laten we, voordat we de twee in detail bekijken, snel een API instellen die moet worden gedocumenteerd.

3. De REST API

Laten we een basis CRUD API samenstellen met Spring Boot.

3.1. De repository

Hier is de repository die we zullen gebruiken een kale PagingAndSortingRepository interface, met het model Foo:

@Repository openbare interface FooRepository breidt PagingAndSortingRepository uit {} @Entity openbare klasse Foo {@Id @GeneratedValue (strategie = GenerationType.IDENTITY) privé lang id; @Column (nullable = false) private String-titel; @Column () private String body; // constructeur, getters en setters}

We laden de repository ook met een schema.sql en een data.sql.

3.2. De controller

Laten we vervolgens naar de controller kijken en de implementatiedetails voor beknoptheid overslaan:

@RestController @RequestMapping ("/ foo") openbare klasse FooController {@Autowired FooRepository-opslagplaats; @GetMapping openbare ResponseEntity getAllFoos () {// implementatie} @GetMapping (waarde = "{id}") openbare ResponseEntity getFooById (@PathVariable ("id") Lange id) {// implementatie} @PostMapping openbare ResponseEntity addFoo (@RequestBody @Valid Foo foo ) {// implementatie} @DeleteMapping ("/ {id}") openbare ResponseEntity deleteFoo (@PathVariable ("id") lange id) {// implementatie} @PutMapping ("/ {id}") openbare ResponseEntity updateFoo (@ PathVariable ("id") lange id, @RequestBody Foo foo) {// implementatie}}

3.3. De applicatie

En tot slot, de Boot-app:

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

4. OpenAPI / Springdoc

Laten we nu eens kijken hoe springdoc kan documentatie toevoegen aan onze Foo REST API.

Herhaal dat het genereert een JSON-object en een UI-visualisatie van de API op basis van dat object.

4.1. Basis gebruikersinterface

Om te beginnen voegen we een paar Maven-afhankelijkheden toe - springdoc-openapi-data-rest voor het genereren van de JSON, en springdoc-openapi-ui voor het weergeven van de gebruikersinterface.

De tool zal de code voor onze API introspecteren en de annotaties van de controllermethode lezen. Op basis daarvan genereert het de API JSON die live zal zijn // localhost: 8080 / api-docs /. Het zal ook dienen als een eenvoudige gebruikersinterface op //localhost:8080/swagger-ui-custom.html:

Zoals we kunnen zien, hebben we zonder enige code toe te voegen een prachtige visualisatie van onze API verkregen, tot aan de Foo schema. De ... gebruiken Probeer het knop kunnen we zelfs de bewerkingen uitvoeren en de resultaten bekijken.

Nu, wat als we wat echte documentatie aan de API wilden toevoegen? In termen van waar de API over gaat, wat al zijn bewerkingen betekenen, wat moet worden ingevoerd en welke reacties kunnen worden verwacht?

We zullen dit in de volgende sectie bekijken.

4.2. Gedetailleerde gebruikersinterface

Laten we eerst kijken hoe we een algemene beschrijving aan de API kunnen toevoegen.

Daarvoor voegen we een OpenAPI bean naar onze Boot-app:

@Bean public OpenAPI customOpenAPI (@Value ("$ {springdoc.version}") String appVersion) {retourneer nieuwe OpenAPI (). Info (nieuwe Info () .title ("Foobar API") .version (appVersion) .description ( "Dit is een voorbeeld van een Foobar-server gemaakt met springdocs -" + "een bibliotheek voor OpenAPI 3 met spring boot.") .TermsOfService ("// swagger.io/terms/") .license (nieuwe licentie (). Naam (" Apache 2.0 ") .url (" // springdoc.org "))); } 

Om vervolgens wat informatie aan onze API-bewerkingen toe te voegen, zullen we onze toewijzingen versieren met een paar OpenAPI-specifieke annotaties.

Laten we eens kijken hoe we het kunnen beschrijven getFooById. We doen dit in een andere controller, FooBarController, die vergelijkbaar is met onze FooController:

@RestController @RequestMapping ("/ foobar") @Tag (name = "foobar", description = "de foobar API met documentatieannotaties") openbare klasse FooBarController {@Autowired FooRepository-repository; @Operation (summary = "Get a foo by foo id") @ApiResponses (value = {@ApiRespons (responseCode = "200", description = "found the foo", content = {@Content (mediaType = "application / json" , schema = @Schema (implementatie = Foo.class))}), @ApiResponse (responseCode = "400", description = "Ongeldige id opgegeven", content = @Content), @ApiResponse (responseCode = "404", description = "Foo niet gevonden", content = @Content)}) @GetMapping (value = "{id}") openbare ResponseEntity getFooById (@Parameter (description = "id van te doorzoeken foo") @PathVariable ("id") String id) {// implementatie weggelaten voor beknoptheid} // andere toewijzingen, op dezelfde manier geannoteerd met @Operation en @ApiResponses} 

Laten we nu eens kijken naar het effect op de gebruikersinterface:

Met deze minimale configuraties kan de gebruiker van onze API nu zien waar het over gaat, hoe het te gebruiken en welke resultaten hij kan verwachten. Het enige dat we hoefden te doen, was de code compileren en de Boot-app uitvoeren.

5. Veersteun Docs

REST-documenten is een totaal andere kijk op API-documentatie. Zoals eerder beschreven, is het proces testgestuurd en heeft de output de vorm van een statische HTML-pagina.

In ons voorbeeld hier, we zullen Spring MVC-tests gebruiken om documentatiefragmenten te maken.

Om te beginnen moeten we de spring-restdocs-mockmvc afhankelijkheid en de asciidoc Maven-plug-in naar onze pom.

5.1. De JUnit5-test

Laten we nu eens kijken naar de JUnit5-test die onze documentatie bevat:

@ExtendWith ({RestDocumentationExtension.class, SpringExtension.class}) @SpringBootTest (klassen = Application.class) openbare klasse SpringRestDocsIntegrationTest {privé MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @BeforeEach openbare void-installatie (WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {this.mockMvc = MockMvcBuilders.webAppContextSetup (webApplicationContext) .apply (documentationConfiguration (restDocumentation)) .build (); } @Test openbare leegte whenGetFooById_thenSuccessful () gooit uitzondering {ConstraintDescriptions desc = nieuwe 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 van te doorzoeken foo")), responseFields (fieldWithPath ("id") .description ("De id van de foo" + collectionToDelimitedString (desc.descriptionsForProperty ("id"), ".")), fieldWithPath ("title"). description ("The title of the foo"), fieldWithPath ("body"). description ("The body of the foo")))) ; } // meer testmethoden om andere toewijzingen te dekken

}

Na het uitvoeren van deze test, krijgen we verschillende bestanden in ons doelen / gegenereerde fragmenten directory met informatie over de gegeven API-bewerking. In het bijzonder whenGetFooById_thenSuccessful zal ons acht geven adocs in een getAFoo map in de directory.

Hier is een voorbeeld http-response.adoc, met uiteraard de antwoordtekst:

[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

Nu hebben we een hoofdbestand nodig dat al deze fragmenten samenvoegt om een ​​goed gestructureerde HTML te vormen.

Laten we het noemen fooapi.adoc en zie een klein deel ervan:

=== Toegang tot de foo GET Een `GET` verzoek wordt gebruikt om toegang te krijgen tot de foo read. ==== Verzoekstructuur omvat: {snippets} /getAFoo/http-request.adoc [] ==== Padparameters omvatten: {snippets} /getAFoo/path-parameters.adoc [] ==== Voorbeeldantwoord include :: {snippets} /getAFoo/http-response.adoc [] ==== CURL-verzoek include :: {snippets} /getAFoo/curl-request.adoc []

Na het uitvoeren van het asciidoctor-maven-plugin, we krijgen het uiteindelijke HTML-bestand fooapi.html in de target / generated-docs map.

En zo ziet het eruit wanneer het in een browser wordt geopend:

6. Belangrijkste afhaalrestaurants

Nu we naar beide implementaties hebben gekeken, laten we de voor- en nadelen samenvatten.

Met springdoc, de annotaties die we moesten gebruiken, maakten de code van onze restcontroller onoverzichtelijk en verminderden de leesbaarheid. Ook was de documentatie nauw aan de code gekoppeld en zou deze in productie gaan.

Onnodig te zeggen dat het bijhouden van de documentatie hier een andere uitdaging is - als er iets in de API zou veranderen, zou de programmeur er dan altijd aan denken om de bijbehorende OpenAPI-annotatie bij te werken?

Aan de andere kant, REST Docs ziet er niet zo pakkend uit als de andere gebruikersinterface en kan ook niet worden gebruikt voor acceptatietests. Maar het heeft zo zijn voordelen.

Met name de succesvolle afronding van de Spring MVC-test geeft ons niet alleen de fragmenten, maar verifieert ook onze API zoals elke andere unit-test zou doen. Dit dwingt ons om eventuele wijzigingen in de documentatie aan te brengen die overeenkomen met API-wijzigingen. Ook staat de documentatiecode volledig los van de implementatie.

Maar nogmaals, aan de andere kant, we moesten meer code schrijven om de documentatie te genereren. Ten eerste de test zelf, die aantoonbaar net zo uitgebreid is als de OpenAPI-annotaties, en ten tweede de master adoc.

Er zijn ook meer stappen nodig om de uiteindelijke HTML te genereren - eerst de test uitvoeren en daarna de plug-in. Springdoc vereist alleen dat we de Boot-app draaien.

7. Conclusie

In deze tutorial hebben we gekeken naar de verschillen tussen de op OpenAPI gebaseerde springdoc en Spring REST Docs. We hebben ook gezien hoe we de twee kunnen implementeren om documentatie te genereren voor een eenvoudige CRUD-API.

Samenvattend hebben beide hun voor- en nadelen, en de beslissing om de ene boven de andere te gebruiken, is onderworpen aan onze specifieke vereisten.

Zoals altijd is de broncode beschikbaar op GitHub.

REST onder

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS