Всем привет! В данной статье хотел бы рассмотреть инструменты документирования в принципиально разных подходах в разработке 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
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
@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, в котором будет вся та же информация, что в контракте с возьможностью направлять запросы.
Какой бы вы не выбрали путь разработки, инструменты выше практически всегда позволят вам содержать актуальную документацию, тесно связанную с рабочим кодом.