Веб-приложение часто содержит API для взаимодействия с ним. Документирование API позволит клиентам быстрее понять, как использовать ваши сервисы. Если API закрыт от внешнего мира, то все равно стоит уделить время спецификации — это поможет вашим новым коллегам быстрее разобраться с системой.


Создание документации вручную — утомительный процесс. Swagger поможет вам упростить эту работу.


Что такое Swagger?


Swagger автоматически генерирует документацию API в виде json. А проект Springdoc создаст удобный UI для визуализации. Вы не только сможете просматривать документацию, но и отправлять запросы, и получать ответы.


Также возможно сгенерировать непосредственно клиента или сервер по спецификации API Swagger, для этого нужен генератор кода Swagger-Codegen.


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


Вы найдете все примеры представленные тут в моем репозитории.


Создание REST API


Чтобы документировать API, для начала напишем его :smile: Вы можете перейти к следующей главе, чтобы не тратить время.


Добавим примитивные контроллеры и одно DTO. Суть нашей системы — программа лояльности пользователей.


Для наших примеров достаточно слоя контроллеров, поэтому я позволю себе вольность опустить серверный и репозиторный слой и добавить бизнес логику в контроллер. В своих проектах старайтесь так не делать.


В качестве DTO у нас будет класс UserDto — это пользователь нашей системы. У него пять полей, из которых 3 обязательны: имя, уникальный ключ, пол пользователя, количество баллов, дата регистрации


public class UserDto {

    private String key;
    private String name;
    private Long points = 0L;
    private Gender gender;
    private LocalDateTime regDate = LocalDateTime.now();

    public UserDto() {
    }

    public UserDto(String key, String name, Gender gender) {
        this.key = key;
        this.name = name;
        this.gender = gender;
    }

    public static UserDto of(String key, String value, Gender gender) {
        return new UserDto(key, value, gender);
    }

    // getters and setters

}

public enum Gender {
    MAN, WOMAN
}

Для взаимодействия с нашей бизнес-логикой, добавим три контроллера: UserController, PointContoller, SecretContoller.


UserController отвечает за добавление, обновление и получение пользователей.


@RestController
@RequestMapping("/api/user")
public class UserController {

    private final Map<String, UserDto> repository;

    public UserController(Map<String, UserDto> repository) {
        this.repository = repository;
    }

    @PutMapping(produces = APPLICATION_JSON_VALUE)
    public HttpStatus registerUser(@RequestBody UserDto userDto) {
        repository.put(userDto.getKey(), userDto);
        return HttpStatus.OK;
    }

    @PostMapping(produces = APPLICATION_JSON_VALUE)
    public HttpStatus updateUser(@RequestBody UserDto userDto) {
        if (!repository.containsKey(userDto.getKey())) return HttpStatus.NOT_FOUND;
        repository.put(userDto.getKey(), userDto);
        return HttpStatus.OK;
    }

    @GetMapping(value = "{key}", produces = APPLICATION_JSON_VALUE)
    public ResponseEntity<UserDto> getSimpleDto(@PathVariable("key") String key) {
        return ResponseEntity.ok(repository.get(key));
    }

}

PointContoller отвечает за взаимодействие с баллами пользователя. Один метод этого контроллера отвечает за добавление и удаление балов пользователям.


@RestController
@RequestMapping("api/user/point")
public class PointController {

    private final Map<String, UserDto> repository;

    public PointController(Map<String, UserDto> repository) {
        this.repository = repository;
    }

    @PostMapping("{key}")
    public HttpStatus changePoints(
            @PathVariable String key,
            @RequestPart("point") Long point,
            @RequestPart("type") String type
    ) {
        final UserDto userDto = repository.get(key);
        userDto.setPoints(
                "plus".equalsIgnoreCase(type) 
                    ? userDto.getPoints() + point 
                    : userDto.getPoints() - point
        );
        return HttpStatus.OK;
    }

}

Метод destroy в SecretContoller может удалить всех пользователей.


@RestController
@RequestMapping("api/secret")
public class SecretController {

    private final Map<String, UserDto> repository;

    public SecretController(Map<String, UserDto> repository) {
        this.repository = repository;
    }

    @GetMapping(value = "destroy")
    public HttpStatus destroy() {
        repository.clear();
        return HttpStatus.OK;
    }

}

Настраиваем Swagger


Теперь добавим Swagger в наш проект. Для этого добавьте следующие зависимости в проект.


<dependency>
    <groupId>io.swagger.core.v3</groupId>
    <artifactId>swagger-annotations</artifactId>
    <version>2.1.6</version>
</dependency>
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.5.2</version>
</dependency>

Swagger автоматически находит список всех контроллеров, определенных в нашем приложении. При нажатии на любой из них будут перечислены допустимые методы HTTP (DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT).


Для каждого метода доступные следующие данные: статус ответа, тип содержимого и список параметров.


Поэтому после добавления зависимостей, у нас уже есть документация. Чтобы убедиться в этом, переходим по адресу: localhost:8080/swagger-ui.html


Swagger запущенный с дефолтными настройками


Также можно вызвать каждый метод с помощью пользовательского интерфейса. Откроем метод добавления пользователей.



Пока у нас не очень информативная документация. Давайте исправим это.


Для начала создадим класс конфигурации сваггера SwaggerConfig — имя произвольное.


@Configuration
public class SwaggerConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(
                        new Info()
                                .title("Example Swagger Api")
                                .version("1.0.0")
                );
    }

}

  • title — это название вашего приложения
  • version — версия вашего API

Эти данные больше для визуальной красоты UI документации.


Добавление авторов


Добавьте разработчиков API, чтобы было понятно, кто в ответе за это безобразие


@Bean
public OpenAPI customOpenAPI() {
    return new OpenAPI()
            .info(
                    new Info()
                            .title("Loyalty System Api")
                            .version("1.0.0")
                            .contact(
                                    new Contact()
                                            .email("me@upagge.ru")
                                            .url("https://uPagge.ru")
                                            .name("Struchkov Mark")
                            )
            );
}

Разметка контроллеров


Переопределим описания контроллеров, чтобы сделать документацию понятнее. Для этого пометим контроллеры аннотацией @Tag.


@Tag(name="Название контроллера", description="Описание контролера")
public class ControllerName {

    // ... ... ... ... ...

}

Добавили описание контроллеров в Swagger


Скрыть контроллер


У нас есть контроллер, который мы хотим скрыть — SecretController. Аннотация @Hidden поможет нам в этом.


@Hidden
@Tag(name = "Секретный контролер", description = "Позволяет удалить всех пользователей")
public class SecretController {

    // ... ... ... ... ...

}

Аннотация скрывает контроллер только из Swagger. Он все также доступен для вызова. Используйте другие методы для защиты вашего API.


Наша документация стала намного понятнее, но давайте добавим описания для каждого метода контроллера.


Разметка методов


Аннотация @Operation описывает возможности методов контроллера. Достаточно определить следующие значения:


  • summary — короткое описание.
  • description — более полное описание.

@Operation(
    summary = "Регистрация пользователя", 
    description = "Позволяет зарегистрировать пользователя"
)
public HttpStatus registerUser(@RequestBody UserDto userDto) {

    // ... ... ... ... ...

}

Метод с аннотацией Operation


Разметка переменных метода


При помощи аннотации Parameter также опишем переменные в методе, который отвечает за управление баллами пользователей.


public HttpStatus changePoints(
    @PathVariable @Parameter(description = "Идентификатор пользователя") String key,
    @RequestPart("point") @Parameter(description = "Количество баллов") Long point,
    @RequestPart("type") @Parameter(description = "Тип операции") TypeOperation type
) {

    // ... ... ... ... ...

}

С помощью параметра required можно задать обязательные поля для запроса. По умолчанию все поля необязательные.


Разметка DTO


Разработчики стараются называть переменные в классе понятными именами, но не всегда это помогает. Вы можете дать человеко-понятное описание самой DTO и ее переменным с помощью аннотации @Schema


@Schema(description = "Сущность пользователя")
public class UserDto {

    @Schema(description = "Идентификатор")
    private String key;

    // ... ... ... ... ...

}

Сваггер заполнит переменные, формат которых он понимает: enum, даты. Но если некоторые поля DTO имеют специфичный формат, то помогите разработчикам добавив пример.


@Schema(description = "Идентификатор", example = "A-124523")

Выглядеть это будет так:


Разметка аннотацией Schema


Разметка аннотацией Schema


Но подождите, зачем мы передаем дату регистрации. Да и уникальный ключ чаще всего будет задаваться сервером. Скроем эти поля из swagger с помощью параметра Schema.AccessMode.READ_ONLY:


public class UserDto {

    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
    private String key;

    ...

}

Валидация


Добавим валидацию в метод управления баллами пользователя в PointController. Мы не хотим, чтобы можно было передать отрицательные баллы.


Подробнее о валидации данных в этой статье.


public HttpStatus changePoints(
    // ... ... ... ... ...
    @RequestPart("point") @Min(0) @Parameter(description = "Количество баллов") Long point,
    // ... ... ... ... ...
) {

    // ... ... ... ... ...

}

Давайте посмотрим на изменения спецификации. Для поля point появилось замечание minimum: 0.


Валидация в swagger


И все это нам не стоило ни малейшего дополнительного усилия.


Итог


Этих знаний вам хватит, чтобы сделать хорошее описание API вашего проекта.


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