Spring REST Docs срещу OpenAPI

ПОЧИВКА Най-горе

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА

1. Общ преглед

Spring REST Docs и OpenAPI 3.0 са два начина за създаване на API документация за REST API.

В този урок ще разгледаме относителните им предимства и недостатъци.

2. Кратко резюме на произхода

Spring REST Docs е рамка, разработена от общността Spring, за да създаде точна документация за RESTful API. Отнема тест подход, ориентиран към, която документацията е писано или като тестове Spring MVC, Пролет Webflux на WebTestClient, или за почивка с гарантирано.

Резултатът от изпълнението на тестовете се създава като AsciiDoc файлове, които могат да бъдат обединени с помощта на Asciidoctor за генериране на HTML страница, описваща нашите API. Тъй като следва метода TDD, Spring REST Docs автоматично въвежда всички свои предимства , като по-малко предразположен към грешки код, намалена преработка и по-бързи цикли на обратна връзка, за да назовем само няколко.

OpenAPI, от друга страна, е спецификация, породена от Swagger 2.0. Най-новата му версия към момента на написването е 3.0 и има много известни реализации.

Както всяка друга спецификация, OpenAPI излага някои основни правила, които следва да се изпълняват. Най-просто казано, всички реализации на OpenAPI трябва да създават документацията като JSON обект, във формат JSON или YAML .

Съществуват и много инструменти, които внасят този JSON / YAML и изплюват потребителски интерфейс, за да визуализират и навигират в API. Това е полезно по време на тестове за приемане например. В нашите примерни кодове тук ще използваме springdoc - библиотека за OpenAPI 3 с Spring Boot.

Преди да разгледаме двете подробно, нека бързо настроим API, който да бъде документиран.

3. REST API

Нека да съберем основен API за CRUD, използвайки Spring Boot.

3.1. Хранилището

Тук хранилището, което ще използваме, е интерфейс PagingAndSortingRepository с голи кости с модела 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 }

Също така ще заредим хранилището, използвайки schema.sql и data.sql .

3.2. Контролерът

След това нека разгледаме контролера, като пропуснем подробностите за неговото изпълнение за краткост:

@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. Приложението

И накрая, Boot App:

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

4. OpenAPI / Springdoc

Сега нека видим как springdoc може да добави документация към нашия Foo REST API.

Спомнете си, че ще генерира JSON обект и визуализация на потребителския интерфейс на API на базата на този обект .

4.1. Основен потребителски интерфейс

Като начало просто ще добавим няколко зависимости на Maven - springdoc-openapi-data-rest за генериране на JSON и springdoc-openapi-ui за изобразяване на потребителския интерфейс.

Инструментът ще разгледа само кода за нашия API и ще прочете поясненията на методите на контролера. На тази основа той ще генерира API JSON, който ще бъде активен на // localhost: 8080 / api-docs / . Той ще обслужва и основен потребителски интерфейс на //localhost:8080/swagger-ui-custom.html :

Както виждаме, без да добавяме никакъв код, получихме красива визуализация на нашия API, чак до схемата Foo . Използвайки бутона Изпробване , можем дори да изпълним операциите и да видим резултатите.

Сега, какво ще стане ако ние искахме да добавите някои недвижими документация за API? По отношение на това какво представлява API, какво означават всички негови операции, какво трябва да се въведе и какви отговори да очакваме?

Ще разгледаме това в следващия раздел.

4.2. Подробен потребителски интерфейс

Нека първо видим как да добавим общо описание към API.

За това ще добавим OpenAPI боб към нашето приложение за стартиране:

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

След това, за да добавим малко информация към нашите API операции, ще украсим нашите картографирания с няколко специфични за OpenAPI анотации.

Нека да видим как можем да опишем 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 } 

Сега нека видим ефекта върху потребителския интерфейс:

Така че с тези минимални конфигурации потребителят на нашия API вече може да види за какво става въпрос, как да го използва и какви резултати да очаква. Всичко, което трябваше да направим, беше да компилираме кода и да стартираме Boot App.

5. Пролетни 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.

Освен това се нуждае от повече стъпки за генериране на крайния HTML - първо стартиране на теста и след това приставката. Springdoc изискваше само да стартираме приложението Boot.

7. Заключение

В този урок разгледахме разликите между базиран на OpenAPI Springdoc и Spring REST Docs. Видяхме също как да внедрим двете, за да генерираме документация за основен CRUD API.

В обобщение, и двете имат своите плюсове и минуси и решението да се използва едното над другото зависи от нашите специфични изисквания.

Както винаги, изходният код е достъпен в GitHub.

ПОЧИВКА отдолу

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА