Рубрики
Без рубрики

Документы весеннего ОТДЫХА против OpenAPI

Изучите различия между spring doc на основе открытого API и Spring REST Docs.

Автор оригинала: 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 .