Всем привет! В данной статье хотел бы рассмотреть инструменты документирования в принципиально разных подходах в разработке 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.

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

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

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