Документируй это

от автора

Всем привет! В данной статье хотел бы рассмотреть инструменты документирования в принципиально разных подходах в разработке REST API, а именно для CodeFirst — инструменты SpringRestDocs (а также его надстройку SpringAutoRestDocs) и для ApiFirst — инструменты экосистемы Swagger(Open-Api).

Дисклеймер: В подробности холивара на тему что же лучше CodeFirst или ApiFirst я вдаваться не будут, а постараюсь продемонстрировать возможную практику документации в обоих вариантах.

CodeFirst плюс SpringAutoRestDocs

Как уже описывали в статье про SpringRestDocs это инструмент достаточно широкого использования, позволяющий генерировать различную документацию (аскедок, хтмл и т.д.) на основе тестов. Пожалуй один из немногих недостатков этого инструмента, является его многословность в тестах, а именно — необходимо описывать каждый параметр, каждое поле и т.д. В свою очередь тесы в SpringAutoRestDocs, используя JSR и Spring аннотации, а также Javadoc, становятся более коротким и немногословными.

Чуть более подробно про SpringAutoRestDocs

SpringAutoRestDocs это надстройка над SpringRestDocs, которая ставит своей целью уменьшение написания кода и документации. Основное преимущество этого иструмента — является меньшее количество написанной документации и большая связаность с рабочим кодом.

Некоторые из фичей инструмента:

  • Автоматическое документирования полей запроса и ответа, хедеров и параметров запроса с использованием Jackson, а также описательной части на основе Javadocs

  • Автоматическое документирование опциональности и ограничения полей по спецификации JSR-303

  • Возможность кастомизации итоговых снипеттов

Подробнее можно почитать в официальной документации.

Для демонстрации возьмем классический Swagger PetStore (с небольшой модернизацией, подробно можно посмотреть спеку в репозитории) и имплементируем несколько методов контроллера (addPet, deletePet, getPetById). В статье будет приведен пример на основе одного метода getPetById

Контроллер:

@RestController @RequiredArgsConstructor public class PetController implements PetApi {      private final PetRepository petRepository;          @Override     public ResponseEntity<Pet> getPetById(Long petId) {         return new ResponseEntity<>(petRepository.getPetById(petId), HttpStatus.OK);     }   //и другие имплементированные методы }

Репозиторий является обычным листом с несколькими методами доступа.

Базовые настройки для тестовой автодокументации:

Для начала нам необходимо определить базовый класс для всех тестов, который опишет основные правила нашей автоматической документации. Основное что нам понадобится, это сконфигурированный MockMvc (более подробно можно посмотреть в репозитории):

@Before void setUp() throws Exception {   this.mockMvc = MockMvcBuilders     .webAppContextSetup(context)     .alwaysDo(prepareJackson(objectMapper, new TypeMapping()))     .alwaysDo(commonDocumentation())     .apply(documentationConfiguration(restDocumentation)            .uris().withScheme("http").withHost("localhost").withPort(8080)            .and()            .snippets().withTemplateFormat(TemplateFormats.asciidoctor())            .withDefaults(curlRequest(), httpRequest(), httpResponse(),                          requestFields(), responseFields(), pathParameters(),                          requestParameters(), description(), methodAndPath(),                          section(), links(), embedded(), authorization(DEFAULT_AUTHORIZATION),                          modelAttribute(requestMappingHandlerAdapter.getArgumentResolvers())))     .build()   }  protected RestDocumentationResultHandler commonDocumentation(Snippet... snippets) {   return document("rest-auto-documentation/{class-name}/{method-name}", commonResponsePreprocessor(), snippets)   }  protected OperationResponsePreprocessor commonResponsePreprocessor() {   return preprocessResponse(replaceBinaryContent(), limitJsonArrayLength(objectMapper), prettyPrint())   }
Чуть более подробно про настройки MockMVC

WebApplicationContext получаем от @SpringBootTest

prepareJackson(objectMapper, new TypeMapping()) позволяет настроить ResultHandler, который подготовит наши pojo для библиотеки документирования

withDefaults(curlRequest(), httpRequest(), и т.д.) набор сниппетов, которые будут сгенерированы

commonDocumentation() описывает директорию, куда в build папке разместятся сгенерированные сниппеты, а также препроцессинг ответа контроллера.

Непосредственно сам тест:

Пример теста более подробно можно посмотреть в репозитории.

@Test void getPetTest() { //given def petId = 1L //when def resultActions = mockMvc 	.perform(RestDocumentationRequestBuilders 		.get("/pet/{petId}", petId) 		.header("Accept", "application/json")) //then resultActions 	.andExpect(status().isOk()) 	.andExpect(content().string(objectMapper.writeValueAsString(buildReturnPet()))) }

Здесь приведен стандартный MockMvc тест, с ожидаемым статусом и ожидаемым телом ответа.

Итого на выходе:

Набор снипеттов с «главным» сниппетом под названием auto-section.adoc (который содержит информацию из всех остальных, указанных в настройках MockMVC) из которых позже можно собрать общий index.adoc для всех методов API. Готовая структура сниппетов:

Готовые сниппеты документы можно посмотреть в репозитории: сниппеты, html сгенеренный на основе сниппетов.

Также при настройках библиотеки мы можем изменять шаблоны либо добавлять свои сниппеты и SprinAutoRestDocs их распознает и воспроизведет ту функциональность, что вам нужна. Дока кастомизации сниппетов, констрейнтов.

Чуть подробнее про кастомизацию сниппетов и ограничений

Функционал кастомизации в SpringAutoRestDocs базируется на таковом же в SpringRestDocs.

  • На примере аскедок шаблонов — в ресурсы вашего проекта в папку org/springframework/restdocs/templates/asciidoctor необходимо добавить нужный вам сниппет без приставки default-Список дефолтных аскедок сниппетов.

  • На примере ограничений — в ресурсы вашего проекта в папку org/springframework/restdocs/constraints необходимо добавить файл DefaultConstraintDescriptions.properties с переопределенным списком ограничений Список дефолтных ограничений и их сообщения.

К примеру русификация шапок сниппетов и описание ограничений:

ApiFirst плюс Swagger

Как уже описывалось во множестве статей (пример1, пример2) swagger — это open-source проект, включающий OpenApi Specification и широкий набор инструментов для описания, визуализации и документирования REST api.

Чуть более подробно про Swagger

Swagger состоит из двух основных частей — это OpenApi Specification и Swagger Tools.

  • OpenApi Specification — это спецификация описывающая процесс создания REST контракта для более простого процесса разработки и внедрения API. Спецификация может быть описана в формате YAML и JSON. Описание базового синтаксиса, а также более подробную информацию можно изучить по ссылке.

  • Swagger Tools — это инструменты визуализации, документации и генерации клиентно-серверной части REST api. Состоят из трех основных блоков: Swagger Editor(браузерный эдитор, для более удобного написания контракта с поддержкой валидации ситаксиса), Swagger UI(рендер спецификации в качестве интерактивной документации API), Swagger Codegen(генератор серверно-клиентной части, а также некоторых типов документаций)

Ниже мы рассмотрим мульти-модульный проект со следущей структурой: библиотека со свагер генератором (swagger-library), стартер с swagger-ui(swagger-webjar-ui-starter), приложение которое имплементирует классы библиотеки(spring-auto-rest-docs).

Для демонстрации возможностей Swagger возьмем классический Swagger PetStore из примера SpringAutoRestDocs выше.

Настройки build.gradle для модуля библиотеки:

Для реализации генерации server stub и документации на основе Swagger контракта используем OpenApi Generator.

Чуть более подробно про OpenApi Generator

В модуле используется gradle плюс gradle plugin OpenApi Generator.

Сам плагин дает возможность генерить как серверно-клиентную часть, так и различные форматы документации. Более подробно можно посмотреть в ссылке на репозиторий.

Основная таска для генерации библиотеки и ее настройки:

openApiGenerate {     generatorName = 'spring'     inputSpec = specFile     outputDir = "${project.projectDir}/"     id = "${artifactId}"     groupId = projectPackage     ignoreFileOverride = ignoreFile     apiPackage = "${projectPackage}.rest.api"     invokerPackage = "${projectPackage}.rest.invoker"     modelPackage = "${projectPackage}.rest.model"     configOptions = [             dateLibrary            : 'java8',             hideGenerationTimestamp: 'true',             interfaceOnly          : 'true',             delegatePattern        : 'false',             configPackage          : "${projectPackage}.configuration"     ] }
Чуть более подробно про настройки таски openApiGenerate

Незабываем выполнять генерацию кода до компиляции проекта:

task codegen(dependsOn: ['openApiGenerate', 'copySpecs'])  compileJava.dependsOn(codegen) compileJava.mustRunAfter(codegen)

Копируем спеки в ресурсы, чтобы была позже возможность отобразить UI прямо из спек:

task copySpecs(type: Copy) {     from("${project.projectDir}/specs")     into("${project.projectDir}/src/main/resources/META-INF/specs") }

При необходимости также можно сгенерить Asciidoc или Html2.

Swagger-ui стартер:

Стартер добавляет в регистр ресурсов спеки с определенными в дефолтными настройками и webjar swagger-ui с измененным путем до дефолтного контракта.

Rest-docs API:

В самом приложении достаточно имплементировать сгенерированный интерфейс и переопределить настройки UI стартера:

@RestController @RestController @RequiredArgsConstructor public class PetController implements PetApi {      private final PetRepository petRepository;          @Override     public ResponseEntity<Pet> getPetById(Long petId) {         return new ResponseEntity<>(petRepository.getPetById(petId), HttpStatus.OK);     }   //и другие имплементированные методы }
swagger:   ui:     indexHandler:       enabled: true       resourceHandler: "/api/**"     apis:       - url: http://localhost:8080/specs/some_swagger.yaml         name: My api

Итого на выходе:

Server stub — готовая библиотека с сущностями и интерфейсом для реализации серверной части API.

Swagger UI — с помощью которого мы получаем наглядную визуализацию API и возможность направлять запросы прямо из UI:

Также примеры Asciidoc или Html2 из самого swagger контракта:

Заключение:

Что дает SpringAutoRestDocs:

  • Возможность хранить документацию как в проекте (к примеру в форме собранного index.html из множества сниппетов), так и в любом другом удобном месте.

  • Документация и модель данных всегда синхронизирована с кодом, так как документация генерируется на основе «зеленых» тестов контроллера.

  • Возможность кастомизации сниппетов и ограничений.

Что дает Swagger:

  • Единый артефакт на всех этапах разработки.

  • Документация и модель данных всегда синхронизирована с кодом, так как код генерируется на основе контракта.

  • Возможность использовать UI, в котором будет вся та же информация, что в контракте с возьможностью направлять запросы.

Какой бы вы не выбрали путь разработки, инструменты выше практически всегда позволят вам содержать актуальную документацию, тесно связанную с рабочим кодом.

ссылка на оригинал статьи https://habr.com/ru/post/556400/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *