Автор оригинала: Sampada Wagde.
1. Обзор
Spring REST Docs и Open API 3.0 – это два способа создания документации API для REST API.
В этом уроке мы рассмотрим их относительные преимущества и недостатки.
2. Краткое изложение происхождения
Spring REST Docs – это фреймворк, разработанный сообществом Spring для создания точной документации для API RESTful. Он использует подход, основанный на тестировании, в котором документация написана либо в виде тестов Spring MVC, Spring Webflux WebTestClient, или REST-Assured.
Результаты выполнения тестов создаются в виде файлов AsciiDoc , которые могут быть объединены с помощью Asciidoctor для создания HTML-страницы, описывающей наши API. Поскольку он следует методу TDD, Spring REST Docs автоматически приносит все свои преимущества, такие как меньшее количество ошибок в коде, меньшее количество переделок и более быстрые циклы обратной связи, и это лишь некоторые из них.
С другой стороны , Open API – это спецификация, рожденная из Swagger 2.0. Его последняя версия на момент написания этой статьи-3.0 и имеет много известных реализаций .
Как и любая другая спецификация, OpenAPI устанавливает определенные основные правила для своих реализаций. Проще говоря, реализации call Open API должны создавать документацию в виде объекта JSON либо в формате JSON, либо в формате YAML .
Также существует много инструментов , которые принимают этот JSON/YAML и выплевывают пользовательский интерфейс для визуализации и навигации по API. Это пригодится, например, во время приемочных испытаний. В наших примерах кода здесь мы будем использовать springdoc – библиотеку для OpenAPI 3 с загрузкой Spring.
Прежде чем подробно рассмотреть эти два вопроса, давайте быстро настроим API для документирования.
3. API REST
Давайте соберем базовый CRUD API с помощью 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. Приложение
И, наконец, загрузочное приложение:
@SpringBootApplication() public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
4. OpenAPI/Spring doc
Теперь давайте посмотрим, как spring doc может добавить документацию в наш Foot REST API.
Напомним, что он создаст объект JSON и визуализацию пользовательского интерфейса API на основе этого объекта .
4.1. Базовый пользовательский интерфейс
Для начала мы просто добавим пару зависимостей Maven – springdoc-openapi-data-rest для генерации JSON и springdoc-openapi-ui для рендеринга пользовательского интерфейса.
Инструмент проанализирует код нашего API и прочитает аннотации методов контроллера. На этой основе он сгенерирует API JSON, который будет жить в http://localhost:8080/api-docs/ . Он также будет обслуживать базовый пользовательский интерфейс в http://localhost:8080/swagger-ui-custom.html :
Как мы видим, без добавления какого-либо кода мы получили красивую визуализацию нашего API, вплоть до схемы Foo . Используя кнопку Try it out , мы даже можем выполнять операции и просматривать результаты.
Теперь, что, если бы мы хотели добавить какую-то реальную документацию в API? С точки зрения того, что такое API, что означают все его операции, что должно быть введено и каких ответов ожидать?
Мы рассмотрим это в следующем разделе.
4.2. Подробный пользовательский интерфейс
Давайте сначала посмотрим, как добавить общее описание в API.
Для этого мы добавим Open API bean в наше приложение на стенде:
@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"))); }
Затем, чтобы добавить некоторую информацию к нашим операциям 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 теперь может видеть, что это такое, как его использовать и каких результатов ожидать. Все, что нам нужно было сделать, это скомпилировать код и запустить загрузочное приложение.
5. Документы ДЛЯ весеннего ОТДЫХА
REST docs – это совершенно другой взгляд на документацию API. Как описано ранее, процесс управляется тестированием, а выходные данные представлены в виде статической HTML-страницы.
В нашем примере здесь мы будем использовать Тесты Spring MVC для создания фрагментов документации .
В самом начале нам нужно будет добавить зависимость spring-restdocs-mockmvc и плагин asciidoc Maven в наш pom .
5.1. Тест JUnit5
Теперь давайте посмотрим на тест 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
}
После выполнения этого теста мы получаем несколько файлов в нашем каталоге targets/generated-snippets с информацией о данной операции API. В частности, whenGetFooById_then Successful даст нам восемь adoc s в папке getAFoo в каталоге.
Вот пример http-response.a doc , конечно, содержащий тело ответа:
[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
Теперь нам нужен мастер-файл, который объединит все эти фрагменты вместе, чтобы сформировать хорошо структурированный HTML.
Давайте назовем это foo api.doc и увидеть небольшую его часть:
=== 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[]
После выполнения asciidoctor-maven-плагина мы получаем окончательный HTML-файл fooapi.html в папке target/generated-docs /.
И вот как это будет выглядеть при открытии в браузере:
6. Ключевые Выносы
Теперь, когда мы рассмотрели обе реализации, давайте обобщим преимущества и недостатки.
С spring doc , аннотации, которые мы должны были использовать, загромождали код нашего контроллера rest и снижали его читаемость . Кроме того, документация была тесно связана с кодом и должна была попасть в производство.
Излишне говорить, что поддержание документации здесь является еще одной проблемой – если что-то в API изменится, будет ли программист всегда помнить об обновлении соответствующей аннотации OpenAPI?
С другой стороны, REST Docs не выглядит так броско, как другой пользовательский интерфейс, и его нельзя использовать для приемочного тестирования . Но у этого есть свои преимущества.
Примечательно, что успешное завершение весеннего теста MVC не только дает нам фрагменты, но и проверяет наш API, как и любой другой модульный тест . Это заставляет нас вносить изменения в документацию, соответствующие изменениям API, если таковые имеются. Кроме того, код документации полностью отделен от реализации.
Но опять же, с другой стороны, нам пришлось написать больше кода для создания документации . Во-первых, сам тест, который, возможно, так же многословен, как и аннотации открытого API, и, во-вторых, мастер idoc .
Также требуется больше шагов для создания окончательного HTML – кода-сначала запуск теста, а затем плагина. Spring doc требовал от нас только запуска загрузочного приложения.
7. Заключение
В этом уроке мы рассмотрели различия между открытым API на основе spring doc и Spring REST Docs. Мы также видели, как реализовать эти два метода для создания документации для базового API CRUD.
Таким образом, у обоих есть свои плюсы и минусы, и решение об использовании одного над другим зависит от наших конкретных требований.
Как всегда, исходный код доступен на GitHub .