Всем привет! В данной статье хотел бы рассмотреть инструменты документирования в принципиально разных подходах в разработке REST API, а именно для CodeFirst - инструменты SpringRestDocs (а также его надстройку SpringAutoRestDocs) и для ApiFirst - инструменты экосистемы Swagger(Open-Api).
Дисклеймер: В подробности холивара на тему что же лучше CodeFirst или ApiFirst я вдаваться не будут, а постараюсь продемонстрировать возможную практику документации в обоих вариантах.
CodeFirst плюс SpringAutoRestDocs
Как уже описывали в статье про SpringRestDocs это инструмент достаточно широкого использования, позволяющий генерировать различную документацию (аскедок, хтмл и т.д.) на основе тестов. Пожалуй один из немногих недостатков этого инструмента, является его многословность в тестах, а именно - необходимо описывать каждый параметр, каждое поле и т.д. В свою очередь тесы в SpringAutoRestDocs, используя JSR и Spring аннотации, а также Javadoc, становятся более коротким и немногословными.
Чуть более подробно про SpringAutoRestDocsSpringAutoRestDocs это надстройка над SpringRestDocs, которая ставит своей целью уменьшение написания кода и документации. Основное преимущество этого иструмента - является меньшее количество написанной документации и большая связаность с рабочим кодом.
Некоторые из фичей инструмента:
-
Автоматическое документирования полей запроса и ответа, хедеров и параметров запроса с использованием Jackson, а также описательной части на основе Javadocs
-
Автоматическое документирование опциональности и ограничения полей по спецификации JSR-303
-
Возможность кастомизации итоговых снипеттов
Подробнее можно почитать в официальной документации.
Для демонстрации возьмем классический Swagger PetStore (с небольшой
модернизацией, подробно можно посмотреть спеку в репозитории) и
имплементируем несколько методов контроллера (addPet
,
deletePet
, getPetById
). В статье будет
приведен пример на основе одного метода getPetById
Контроллер:
@RestController@RequiredArgsConstructorpublic class PetController implements PetApi { private final PetRepository petRepository; @Override public ResponseEntity<Pet> getPetById(Long petId) { return new ResponseEntity<>(petRepository.getPetById(petId), HttpStatus.OK); } //и другие имплементированные методы}
Репозиторий является обычным листом с несколькими методами доступа.
Базовые настройки для тестовой автодокументации:
Для начала нам необходимо определить базовый класс для всех
тестов, который опишет основные правила нашей автоматической
документации. Основное что нам понадобится, это сконфигурированный
MockMvc
(более подробно можно посмотреть в репозитории):
@Beforevoid 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 папке разместятся сгенерированные сниппеты, а также
препроцессинг ответа контроллера.
Непосредственно сам тест:
Пример теста более подробно можно посмотреть в репозитории.
@Testvoid getPetTest() {//givendef petId = 1L//whendef resultActions = mockMvc.perform(RestDocumentationRequestBuilders.get("/pet/{petId}", petId).header("Accept", "application/json"))//thenresultActions.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.
Чуть более подробно про SwaggerSwagger состоит из двух основных частей - это 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
-
generatorName - в качестве основы для генерации серверной части используем
-
dateLibrary - позволяет изменить шаблон используемых в генерации библиотек работы с датой (joda, JSR-310 и т.д.)
-
также можно изменять глобальные проперти генератора, не зависящие от конкретной реализации
Незабываем выполнять генерацию кода до компиляции проекта:
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@RequiredArgsConstructorpublic 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, в котором будет вся та же информация, что в контракте с возьможностью направлять запросы.
Какой бы вы не выбрали путь разработки, инструменты выше практически всегда позволят вам содержать актуальную документацию, тесно связанную с рабочим кодом.