БЛОГ AXIOM JDK
Загрузка...

Как документировать REST API с помощью Swagger в Spring Boot 3

Из этой статьи вы узнаете, как документировать REST API с помощью Swagger в Spring Boot 3

10 мин чтения
Как документировать REST API с помощью Swagger в Spring Boot 3

Документирование RESTful API (Application Programming Interface) — неотъемлемая часть создания веб-приложений. На смену ручному документированию API пришли инструменты, которые решают эту задачу быстрее.

Из этой статьи вы узнаете, как внедрить Swagger, основанный на спецификации OpenAPI 3.0, в проект на Spring Boot.

Содержание:

Что такое Swagger?

Swagger — набор инструментов для создания, редактирования, кодогенерации и использования API-документации в соответствии со спецификацией OpenAPI. С появлением Swagger отпала необходимость писать длинную API-документацию вручную. Swagger умеет читать структуру API, определённую с помощью аннотаций в коде, и генерировать из неё спецификацию API.

У Swagger также есть UI, который показывает интерактивную API-документацию, как в примере Petstore. Этот UI также позволяет тестировать API, выполняя запросы в браузере.

Ключевые компоненты Swagger:

  • Swagger Editor для написания и редактирования API-спецификации,
  • Swagger UI для создания интерактивной API-документации,
  • Swagger Codegen для генерации серверного макета или клиентской библиотеки для вашего API.

В контексте API-документации термины Swagger и OpenAPI часто идут вместе, но это не синонимы. OpenAPI — спецификация для описания API, а Swagger — фреймворк для создания API-документации в соответствии с этой спецификацией.

Внедрение Swagger в проект на Spring Boot

После прочтения этого раздела вы создадите своё небольшое CRUD-приложение, сгенерируете к нему API-документацию и увидите её в Swagger UI.

Предварительные требования:

  • JDK 17 или новее (мы используем Axiom JDK),
  • Maven,
  • Docker,
  • любая IDE.

1. Создайте проект REST API

Если у вас уже есть готовый проект, то пропустите этот шаг. Вы можете идти по руководству или использовать его отдельные примеры в своём проекте.

Код демо-проекта доступен на GitHub.

У нашего демо-приложения на Spring Boot будет REST API для управления сотрудниками. У сотрудника есть:

  • идентификационный номер (id),
  • имя (firstName),
  • фамилия (lastName).

Вот таблица с методами API, которые мы реализуем:

<table class="bs-u-table bs-u-zebra"><thead><tr><th>Метод</th><th>Эндпоинт</th><th>Описание</th></tr></thead><tbody><tr><td>GET</td><td>/v1/employees</td><td>Получить список всех сотрудников</td></tr><tr><td>GET</td><td>/v1/employees/{employeeID}</td><td>Получить данные о конкретном сотруднике</td></tr><tr><td>POST</td><td>/v1/employees</td><td>Добавить сотрудника</td></tr><tr><td>PUT</td><td>/v1/employees/</td><td>Обновить данные о сотруднике</td></tr><tr><td>DELETE</td><td>/v1/employees/{employeeID}</td><td>Удалить сотрудника</td></tr></tbody></table>

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

Чтобы создать основу проекта, сделайте следующее:

  1. Откройте Spring Initializr.
  1. В разделе Language выберите Java,
  2. ProjectMaven,
  3. Project Metadata -&gt; Artifact — openapidemo (или любое другое имя на ваш выбор),
  4. Project Metadata -&gt; Name — имя проекта (у нас это OpenapiSpringDemo),
  5. PackagingJar,
  6. Java17 или ту, с которой вы работаете.

<p class="hint"> В некоторых IDE, например в IntelliJ IDEA Ultimate, Spring Initializr доступен в виде плагина. Это значит, что вам не нужно скачивать архив проекта, распаковывать его и импортировать в IDE. Достаточно просто настроить всё, что нужно, не выходя из IDE. </p>

  1. Добавьте следующие зависимости, нажав ADD DEPENDENCIES…
  1. Spring Web,<br>
  2. Lombok,
  3. Spring Data JDBC,
  4. PostgreSQL Driver,
  5. Testcontainers,
  6. Jakarta Persistence API,
  7. Spring Boot DevTools. DevTools экономит время привнесении изменений в коде. Вам не нужно перезапускатьприложение каждый раз, когда вы вносите изменения,перекомпиляция выполняется “на лету”.
Добавьте необходимые зависимости
  1. Сгенерируйте проект, нажав GENERATE. У вас скачается файл demo.zip. Разархивируйте его и импортируйте проект в свою IDE.

Структура проекта будет простая:

  • Класс Employee, который будет предоставлять информацию о сотрудниках.
  • Контроллер EmployeeController, который будет предоставлять RESTful API для работы с объектами класса Employee.
  • Интерфейс EmployeeRepository для работы с объектами класса Employee и выполнения базовых операций CRUD (Create, Read, Update, Delete).
  • Класс EmployeeNotFoundException для обработки ошибок.

Employee.java

package com.example.openapidemo;

import jakarta.persistence.GenerationType;
import jakarta.validation.constraints.NotNull;
import jakarta.persistence.GeneratedValue;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.Id;

@Getter
@Setter
@RequiredArgsConstructor
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @NotNull
    private String firstName;

    @NotNull
    private String lastName;

}

EmployeeRepository.java

package com.example.openapidemo;

import org.springframework.data.repository.ListCrudRepository;

public interface EmployeeRepository extends ListCrudRepository<Employee, Integer> { }

EmployeeController.java

package com.example.openapidemo;


import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.HttpStatus;

import java.util.List;

@RestController
@AllArgsConstructor
@RequestMapping("/v1")
public class EmployeeController {
    
    private EmployeeRepository repository;

    @GetMapping("/employees")
    public List<Employee> findAllEmployees() {
        return repository.findAll();
    }

    @GetMapping("/employees/{employeeId}")
    public Employee getEmployee(@PathVariable int employeeId) {
        return repository.findById(employeeId)
                .orElseThrow(() -> new EmployeeNotFoundException(employeeId));
    }

    @PostMapping("/employees")
    public ResponseEntity<Employee> addEmployee(@RequestBody Employee newEmployee) {
        Employee savedEmployee = repository.save(newEmployee);
        return ResponseEntity.status(HttpStatus.CREATED).body(savedEmployee);
    }

    @PutMapping("/employees/")
    public ResponseEntity<Employee> updateEmployee(@RequestBody Employee employee) {
        return repository.findById(employee.getId())
                .map(existingEmployee -> {
                    existingEmployee.setFirstName(employee.getFirstName());
                    existingEmployee.setLastName(employee.getLastName());
                    Employee updatedEmployee = repository.save(existingEmployee);
                    return ResponseEntity.ok(updatedEmployee);
                })
                .orElseGet(() -> ResponseEntity.notFound().build());
    }

    @DeleteMapping("/employees/{employeeId}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public ResponseEntity<Void> deleteEmployee(@PathVariable int employeeId) {
        repository.findById(employeeId).ifPresentOrElse(
                employee -> repository.deleteById(employeeId),
                () -> {
                    throw new EmployeeNotFoundException(employeeId);
                }
        );
        return ResponseEntity.noContent().build();
    }

}

EmployeeNotFoundException.java

package com.example.openapidemo;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class EmployeeNotFoundException extends RuntimeException {
    public EmployeeNotFoundException (int employeeId) {
        super("Сотрудник с ID " + employeeId + " не найден");
    }
}
  1. В файл pom.xml добавьте зависимость для Jakarta Persistence API:
<dependency>
    <groupId>jakarta.persistence</groupId>
    <artifactId>jakarta.persistence-api</artifactId>
</dependency>
  1. В папке test откройте класс TestOpenapiSpringDemoApplication (имя вашего класса может отличаться). Добавьте аннотацию @RestartScope в метод PostgreSQLContainer&lt;?&gt; postgresContainer(). Так сохранится бин. БД не будет перезапускаться каждый раз с перезапуском приложения.
  2. Предоставьте схему базы данных. Для этого в директории resources создайте файл schema.sql со следующим содержимым:
CREATE TABLE IF NOT EXISTS employee (
    id           SERIAL PRIMARY KEY,
    first_name   VARCHAR(255) NOT NULL,
    last_name    VARCHAR(255) NOT NULL
);
  1. (Необязательно) Добавьте в БД несколько записей, чтобы не видеть страницу ошибки в браузере при запуске приложения. Для разработки API это не нужно. В той же директории resources создайте файл data.sql:
TRUNCATE employee;
INSERT INTO employee (first_name, last_name)
VALUES ('Иван', 'Иванов'),
       ('Петр', 'Петров');
  1. Установите режим инициализации SQL, добавив в файл aplication.properties следующую строку:

spring.sql.init.mode=always

Это нужно, потому что мы инициализируем БД с помощью SQL-скрипта.

  1. Проверьте, что всё работает как ожидается. Для этого запустите на своей машине Docker и запустите класс TestOpenapiSpringDemoApplication. Spring Boot подтянет образ базы данных PostgreSQL за вас.

В браузере откройте http://localhost:8080/v1/employees. Вы должны увидеть что-то подобное:

[
  {
    "id": 1,
    "firstName": "Иван",
    "lastName": "Иванов"
  },
  {
    "id": 2,
    "firstName": "Петр",
    "lastName": "Петров"
  }
]

Простое CRUD-приложение готово для экспериментов.

2. Добавьте зависимость для springdoc-openapi

Для работы с Swagger вам нужна библиотека springdoc-api. Эта библиотека нужна для генерации API-документации, совместимой с спецификацией OpenAPI, для проектов на Spring Boot. springdoc-api поддерживает Swagger UI и другие инструменты, такие как OAuth2 и GraalVM Native Image.

В файл pom.xml добавьте зависимость для springdoc-api:

<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
   <version>2.6.0</version>
</dependency>

Никаких дополнительных настроек не требуется.

3. Сгенерируйте API-документацию

Документация OpenAPI генерируется при сборке проекта. Убедитесь, что всё работает корректно. Для этого запустите ваш проект и перейдите на страницу API-документации по умолчанию: http://localhost:8080/v3/api-docs.

<p class="hint"> Чтобы выложить документацию в продакшен, замените localhost:8080 на публичный адрес. </p>

Вы увидите эндпоинты (endpoints) и данные по ним в формате JSON:

{
  "openapi": "3.0.1",
  "info": {
    "title": "OpenAPI definition",
    "version": "1.0"
  },
  "servers": [
    {
      "url": "http://localhost:8080",
      "description": "Generated server url" 
    }
  ],
  "paths": {
    "/employees": {
      "get": {
        "tags": [
          "employee-controller"
        ],
        "operationId": "findAllEmployees",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Employee"
                  }
                }
              }
            }
          }
        }
      },
...

Вы также можете открыть YAML-документ с теми же данными по следующей ссылке: http://localhost:8080/v3/api-docs.yaml.

При необходимости вы можете изменить путь по умолчанию до API-документации в файле application.properties. Например:

springdoc.api-docs.path=/api-docs

Теперь документация доступна по ссылке: http://localhost:8080/api-docs.

4. Откройте Swagger UI

Поскольку библиотека springdoc-openapi уже поддерживает Swagger UI, вам не нужно настраивать этот инструмент отдельно.

По ссылке http://localhost:8080/swagger-ui/index.html вы увидите интерфейс для взаимодействия с эндпоинтами:

Интерфейс для взаимодействия с эндпоинтами

Все эндпоинты можно раскрыть и посмотреть как будет выглядеть API-запрос и ответ на него. Для этого нажмите Try it out и Execute.

Как будет выглядеть API-запрос и ответ на него

Расширенная настройка Swagger в Spring Boot 3 с помощью аннотаций

Текущая API-документация не сильно информативна. Её можно дополнить с помощью аннотаций в коде приложения. Ниже приведены самые популярные аннотации. Список всех аннотаций можно посмотреть в вики Swagger.

Добавление описания Swagger API

Добавьте в API-документацию имя и контакты автора проекта, а также описание, для чего нужен этот API. Для этого создайте класс OpenAPIConfiguration:

<p class="hint">

Из соображений безопасности приложения переопределите информацию о URL сервера и номере порта в application.properties:

api.server.url=http://localhost:8080

</p>

package com.example.openapidemo;

import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import java.util.List;

@Configuration
@AllArgsConstructor
public class OpenAPIConfiguration {

    private Environment environment;

    @Bean
    public OpenAPI defineOpenAPI () {
        Server server = new Server();
        String serverUrl = environment.getProperty("api.server.url");
        server.setUrl(serverUrl);
        server.setDescription("Development");

        Contact myContact = new Contact();
        myContact.setName("Имя Фамилия");
        myContact.setEmail("my.email@example.com");

        Info info = new Info()
                .title("Системное API для управления сотрудниками")
                .version("1.0")
                .description("Это API предоставляет эндпоинты для управления сотрудниками.")
                .contact(myContact);
        return new OpenAPI().info(info).servers(List.of(server));
    }
}

Вы также можете добавить информацию о лицензии и др. Кода выше достаточно для того, чтобы посмотреть, как это будет выглядеть в документации. Запустите проект, откройте страницу с API-документацией по ссылке http://localhost:8080/swagger-ui/index.html и убедитесь, что введённые вами данные отображаются:

как это будет выглядеть в документации

Валидация бина (Bean)

Библиотека springdoc-openapi поддерживает стандарт JSR 303: Bean Validation (например, @NotNull, @Min, @Max и @Size) для валидации входных данных. Добавьте эти аннотации в код проекта, и у вас автоматически сгенерируется схема в разделе Schemas Swagger UI.

Определите аннотации в классе Employee:

package com.example.openapidemo;

import jakarta.persistence.GenerationType;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import jakarta.persistence.GeneratedValue;

@Getter
@Setter
@RequiredArgsConstructor
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @NotNull
    @Size(min = 1, max = 50)
    private String firstName;

    @NotNull
    @Size(min = 1, max = 70)
    private String lastName;

}

Перекомпилируйте проект и откройте документацию. Вы увидите, что раздел Schemas обновился:

Schemas

@Tag

С помощью @Tag можно группировать классы и методы API.

Вот как можно использовать эту аннотацию для группировки GET-методов в файле контроллера EmployeeController:

import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "get", description = "GET-методы Employee API")
@GetMapping("/employees")
public List<Employee> findAllEmployees() {
    return repository.findAll();
}

@Tag(name = "get", description = "GET-методы Employee API")
@GetMapping("/employees/{employeeId}")
public Employee getEmployee(@Parameter(
        description = "ID сотрудника, данные по которому запрашиваются",
        required = true)
                            @PathVariable int employeeId) {
    return repository.findById(employeeId)
            .orElseThrow(() -> new EmployeeNotFoundException(employeeId));
    }

Когда вы откроете документацию, то увидите, что группировка методов изменилась: GET-методы теперь сгруппированы отдельно от остальных.

GET-методы

@Operation

С помощью @Operation можно добавить краткое описание (summary) и расширенное описание (description) метода API.

Рассмотрим применение @Operation для описания метода updateEmployee() в том же файле контроллера EmployeeController:

import io.swagger.v3.oas.annotations.Operation;

@Operation(summary = "Обновить данные о сотруднике", description = "В ответе возвращается объект Employee c полями id, firstName и lastName.")
@PutMapping("/employees/")
public ResponseEntity<Employee> updateEmployee(@RequestBody Employee employee) {
    return repository.findById(employee.getId())
            .map(existingEmployee -> {
                existingEmployee.setFirstName(employee.getFirstName());
                existingEmployee.setLastName(employee.getLastName());
                Employee updatedEmployee = repository.save(existingEmployee);
                return ResponseEntity.ok(updatedEmployee);
                })
            .orElseGet(() -> ResponseEntity.notFound().build());
}

Описание API-метода в Swagger UI будет выглядеть так:

Описание API-метода

@ApiResponses

@ApiResponses даёт описание ответа для метода. Каждый ответ помечается тегом @ApiResponse:

import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;

@ApiResponses({
        @ApiResponse(responseCode = "204", description = "Сотрудник успешно удалён"),
        @ApiResponse(responseCode = "404", description = "Сотрудник не найден")
})
@DeleteMapping("/employees/{employeeId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public ResponseEntity<Void> deleteEmployee(@PathVariable int employeeId) {
    repository.findById(employeeId).ifPresentOrElse(
            employee -> repository.deleteById(employeeId),
            () -> {
                throw new EmployeeNotFoundException(employeeId);
            }
    );
    return ResponseEntity.noContent().build();
}

После перекомпиляции класса EmployeeController, описание API-ответа обновится:

EmployeeController

@Parameter

Аннотация @Parameter используется для параметра API-метода и описывает этот параметр операции.

Рассмотрим на примере метода getEmployee():

import io.swagger.v3.oas.annotations.Parameter;

@GetMapping("/employees/{employeeId}")
public Employee getEmployee(@Parameter(
        description = "ID сотрудника, данные по которому запрашиваются",
        required = true)
                            @PathVariable int employeeId) {
    return repository.findById(employeeId)
            .orElseThrow(() -> new EmployeeNotFoundException(employeeId));
}

Атрибут description даёт дополнительную информацию о назначении параметра, а required = true показывает, что параметр обязателен для заполнения.

В Swagger UI вы увидите следующее:

Swagger UI

Заключение

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

Приложения на Spring Boot разворачиваются в контейнерах. Для этого отлично подходит Axiom Runtime Container Pro. Это такая же хорошая практика, как и документирование API.

Сергей Лунегов

Сергей Лунегов

Директор по продуктам Axiom JDK