Автоматизация спецификации API на базе Swagger PoC

Автоматизация спецификации API на базе Swagger PoC

Фон написания

В среде разработки проекта были ограничения, которые усложняли прямое применение библиотеки Swagger в сервисах клиента. В общих случаях на сервисах на основе Spring можно автоматически генерировать спецификации API на основе Controller и DTO с помощью Springfox или springdoc-openapi, но в среде клиента добавление зависимостей для каждого сервиса и интеграция во время выполнения были менее гибкими.

Тем не менее, клиент хотел использовать Swagger для управления спецификацией API. В частности, у него была потребность видеть текущий список API и подробную структуру request и response в Swagger UI, и, что касается дополнительного, возникали VOC из-за ручной документации API, разработанных разными командами, и задержек с обновлениями.

Отправной точкой PoC было проверить, возможно ли предоставить опыт документирования на основе Swagger, сохраняя эти ограничения. То есть, цель заключалась в создании структуры, которая позволяла бы проверять спецификации API через центральный сервер Swagger, не подключая библиотеку Swagger непосредственно к каждому сервису.

Определение проблемы

Ключевым моментом проекта было создание возможности для всей команды разработчиков клиента подключаться к одному серверу Swagger и проверять текущие спецификации API. Поэтому был запланирован подход, при котором центральный сервер Swagger собирал и предоставлял несколько спецификаций API, а не демонстрация документации Swagger внутри отдельных сервисов.

  • Цель заключалась в том, чтобы спецификации API в реальном времени обновлялись на центральный сервер Swagger, управляемый отдельно, собранный на момент развертывания.

  • Файлы спецификации должны были быть в формате YAML, который может быть прочитан непосредственно в Swagger UI.

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

В PoC эта цель была разделена на два этапа для проверки. Сначала информация Req/Res, собранная от API, автоматически генерировалась в формате YAML Swagger и предоставлялась отдельно на сервере, а затем была проверена возможность создания библиотеки на основе анализа API и VO, основываясь на Custom Annotation для генерации YAML.

Обзор структуры PoC

Сервис был условно разделен на три области. swagger-apis выполняет роль внешнего хранилища, в котором хранятся автоматически сгенерированные файлы спецификации API YAML, swagger-demo - это сервер Spring Boot, который читает эти YAML файлы и отображает в Swagger UI. swagger-file-generator проверяет, возможно ли на основе Java объектов генерировать файлы Swagger YAML, и представляет собой проект для создания библиотеки, предоставляемой каждой команде разработчиков.

Папка

Роль

Описание

swagger-apis

Репозиторий спецификаций

Внешний каталог для хранения файлов swagger.yaml для каждого API.

swagger-demo

Сервер Swagger

Регистрация нескольких YAML в Swagger UI с использованием Spring Boot и Springfox.

swagger-file-generator

Эксперимент по созданию YAML

Преобразование модели объектов Java в строку YAML и сохранение в файл.

Преимущество этой структуры заключается в том, что она позволяет разделить ответственность за предоставление интерфейса Swagger UI и создание спецификаций API. Код сервиса сосредоточен на создании спецификаций, а центральный сервер Swagger предоставляет интерфейс на основе файлов YAML.

Конфигурация центрального сервера Swagger

Проект swagger-demo является сервером Swagger на базе Spring Boot. Этот сервер не документирует собственный API, а регистрирует различные Swagger YAML файлы, предоставленные внешними командами разработчиков, в качестве ресурсов, доступных для выбора в Swagger UI. Каждая команда разработчиков внутри клиента может использовать это, получая доступ к Swagger UI этого сервера, чтобы проверить актуальные спецификации API.

Ключевая реализация заключается в переопределении SwaggerResourcesProvider. Мы не используем предоставляемый Springfox стандартный список ресурсов Swagger, а читаем файлы YAML из внешнего каталога и напрямую формируем список SwaggerResource.

@Primary
@Bean
public SwaggerResourcesProvider swaggerResourcesProvider(
        InMemorySwaggerResourcesProvider defaultResourcesProvider
) {
    return () -> {
        List<SwaggerResource> resources = new ArrayList<>();
        List<File> innerFiles = createFilesFromExternalDir();

        innerFiles.forEach(file -> {
            String path = "/" + file.getParentFile().getParentFile().getName()
                    + "/" + file.getParentFile().getName()
                    + "/" + file.getName();
            String title = Files.readAllLines(file.toPath()).stream()
                    .filter(f -> f.contains("title"))
                    .findFirst()
                    .map(m -> m.split(":")[1].split("\"")[1])
                    .orElseGet(() -> "No Title");
            resources.add(loadResource(path, title));
        });
        return resources;
    };
}

Ключевым моментом в данном коде является динамическое формирование ресурсов, которые будут отображаться в Swagger UI. Путь к каждому YAML файлу создается как статический ресурс Spring Boot, а значение title внутри YAML используется как имя, отображаемое в выпадающем меню Swagger UI.

При такой конфигурации сервера отдельным командам разработчиков не нужно отдельно запускать сервер Swagger UI. Достаточно развернуть центральный сервер Swagger, и каждое спецификация API будет добавлена в виде файлов YAML и отображаться в Swagger UI в виде доступных элементов.

Сбор внешнего YAML и отражение статических ресурсов

swagger-demo использует директорию swagger-apis на корневом уровне как внешний репозиторий спецификаций. При запуске сервера он проходит по папкам API под ../swagger-apis/ и копирует существующие .yaml файлы в src/main/resources/static/swagger-apis/.

private List<File> createFilesFromExternalDir() throws IOException {
    String externalDir = "../swagger-apis/";
    String innerDir = "src/main/resources/static/swagger-apis/";

    List<String> externalFilePaths = Files.list(Paths.get(externalDir))
            .filter(path -> Files.isDirectory(path))
            .map(path -> Files.list(path)
                    .filter(f -> f.toString().contains(".yaml"))
                    .findFirst().get())
            .map(path -> path.getParent().getFileName() + "/" + path.getFileName())
            .toList();

    return externalFilePaths.stream()
            .map(filePath -> {
                File newFile = new File(innerDir + filePath);
                Files.createDirectories(newFile.toPath().getParent());
                Files.copy(
                        Paths.get(externalDir + filePath),
                        Paths.get(newFile.getPath()),
                        StandardCopyOption.REPLACE_EXISTING
                );
                return newFile;
            })
            .toList();
}

YAML, скопированный в область статических ресурсов, может быть доступен по URL, например, /swagger-apis/api2/swagger.yaml, и Swagger UI считывает этот URL для рендеринга документации API. При развитии в сторону производственной структуры необходимо выделить путь к внешнему репозиторию спецификаций, который в настоящее время находится в относительном пути, в параметры конфигурации.

Направление библиотеки на основе пользовательской аннотации

Расширение PoC заключалось в том, чтобы сделать этот проект общим библиотекой. Каждая команда разработчиков добавляет эту библиотеку в качестве зависимости в свой проект и прикрепляет пользовательскую аннотацию к API, который они хотят специфицировать. Затем, когда проект развертывается, библиотека начинает работать и исследует API с прикрепленной аннотацией.

Этот подход является обходом ограничений среды клиентов, при этом повышая удобство использования для команды разработчиков. Разработчики не имеют дело с сложными настройками Swagger напрямую, а просто добавляют аннотацию к целевым API. Библиотека анализирует requestBody и response VO на основе данных Controller или Handler и собирает информацию, необходимую для генерации Swagger YAML.

  • Идентификация API: исследует API-методы с прикрепленной пользовательской аннотацией.

  • Анализ запроса: перебирает тип и структуру полей requestBody.

  • Анализ ответа: перебирает тип response VO и вложенную структуру полей.

  • Создание YAML: конструирует paths, parameters, definitions, responses в соответствии с форматом Swagger 2.0.

  • Отражение на сервере: создает или передает сгенерированный YAML файл в место, доступное для чтения сервером Swagger.

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

Роль проекта создания YAML

swagger-file-generator — это экспериментальный проект, целью которого является проверка возможности создания файлов Swagger YAML на основе модели объектов Java. В текущей реализации основные компоненты документации Swagger представлены в виде VO-классов, а строки YAML собираются в методе toString() каждого объекта.

Этот проект скорее валидация возможности логики создания YAML, необходимой перед созданием библиотеки генерации спецификаций на основе аннотации, чем завершенная библиотека. Если поместить результаты обхода requestBody и response VO в объекты, такие как SwaggerDefinition, SwaggerProperty, SwaggerParameter, их можно преобразовать в конечный файл Swagger YAML.

SwaggerYamlContentFactory factory = SwaggerYamlContentFactory.builder()
        .info(SwaggerInfo.builder()
                .description("This is build test")
                .version("1.0.0")
                .title("TEST").build())
        .host("localhost:8080")
        .basePath("/execute")
        .paths(Arrays.asList(
                SwaggerPath.builder()
                        .url("/v1/tenants/KOREA/sample")
                        .httpMethod("post")
                        .tag("test")
                        .summary("For Sample Test")
                        .parameters(Arrays.asList(
                                SwaggerParameter.builder()
                                        .name("SampleQdo")
                                        .inType("body")
                                        .required(true)
                                        .type("object")
                                        .description("sample qdo").build()))
                        .responses(Arrays.asList(
                                SwaggerResponse.builder()
                                        .code("200")
                                        .description("sample response").build()))
                        .build()))
        .build();

String yamlFormat = factory.toString();
FileGenerator.writeObjectToFile(
        yamlFormat,
        FileGenerator.createInnerDirAndFile("test")
);

Данный тестовый код создает объект Java, затем с помощью factory.toString() создает строку YAML и сохраняет ее в файл swagger.yaml через FileGenerator. На этапе фактической библиотечной интеграции эта часть создания объекта может быть заменена на результаты анализа на основе рефлексии.

Способ моделирования документа Swagger

SwaggerYamlContentFactory отвечает за сборку всего документа Swagger. Внутри него содержатся info, host, basePath, paths, commonParameters, commonDefinitions, и каждый из этих компонентов последовательно преобразуется в строку.

@Getter
@Builder
public class SwaggerYamlContentFactory {
    private SwaggerInfo info;
    private String host;
    private String basePath;
    private List<SwaggerPath> paths;
    private List<SwaggerParameter> commonParameters;
    private List<SwaggerDefinition> commonDefinitions;
    
    @Override
    public String toString() {
        return getBasicFormat()
             + getPathsFormat()
             + getCommonParametersFormat()
             + getCommonDefinitionsFormat();
    }
}

Эта модель практически в точности соответствует структуре документа Swagger. SwaggerPath представляет собой API URL, HTTP метод, параметры и ответы, а SwaggerDefinition описывает схему request или response VO. SwaggerProperty отвечает за преобразование одного поля VO в Swagger свойство.

Класс

Область Swagger

Основная роль

SwaggerInfo

информация

Структурирует описание документа, версию и информацию о заголовке.

SwaggerPath

пути

Структурирует API URL, HTTP метод, параметры и ответы.

SwaggerParameter

параметры

заголовок, запрос, параметр тела выражает.

SwaggerDefinition

определения

выражает схему VO запроса/ответа.

SwaggerProperty

свойства

выражает тип, пример и описание полей VO.

SwaggerResponse

ответы

выражает код ответа и ссылку на схему.

такое моделирование становится важной основой для библиотек. Найдя аннотированный API, можно пройти через requestBody и response VO, а затем сопоставить результаты с вышеупомянутыми объектами. После этого, сериализовав объект в YAML, можно создать файл спецификации, читаемый Swagger UI.

общий поток работы

Окончательная структура, к которой мы стремимся, это поток, который соединяет проекты каждой команды разработчиков, общую библиотеку для создания Swagger, центральный сервер Swagger и Swagger UI. Команды разработчиков добавляют библиотеку в свои проекты и прикрепляют аннотации. На этапе развертывания библиотека анализирует API и VO и генерирует YAML-файл. Центральный сервер Swagger собирает созданный YAML и отображает его в UI.

각 개발팀 프로젝트
  custom annotation 적용
        ↓
공통 Swagger 생성 라이브러리
  annotation 대상 API 분석
  requestBody / response VO 순회
  swagger.yaml 생성
        ↓
Swagger 서버
  YAML 파일 수집 및 리소스 등록
        ↓
Swagger UI
  전사 개발팀이 배포 API 명세 확인

Этот поток минимизирует настройки Swagger для каждого сервиса, предоставляя клиентам желаемый опыт просмотра спецификации на основе Swagger UI. Также, так как ответственность за создание спецификаций API сосредоточена в общей библиотеке, можно уменьшить отклонения в реализации между командами разработчиков.

ожидаемые эффекты

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

Второе ожидаемое воздействие — это уменьшение нагрузки по обновлению документации. После завершения преобразования в библиотеку на основе пользовательской аннотации разработчики смогут принимать участие в процессе генерации YAML, просто добавив аннотацию к целевому API спецификации. Спецификация создается на основе requestBody и response VO в момент развертывания, что снижает вероятность пропусков по сравнению с ручным составлением документов.

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

  • Можно унифицировать местоположение просмотра спецификаций API через общую Swagger UI компании.

  • Можно уменьшить отклонения в настройках Swagger для каждой команды разработчиков и стандартизировать способ генерации спецификаций на основе общей библиотеки.

  • Можно связать поток развертывания и поток генерации спецификаций, чтобы стремиться к документации на основе последних стандартов API.

Ограничения, выявленные в PoC

Реализация на этапе PoC была сосредоточена на проверке возможностей, поэтому есть аспекты, которые необходимо улучшить для операционного применения. В настоящее время swagger-demo использует относительный путь для внешнего YAML, что зависит от места выполнения. В операционной среде этот путь должен быть отделен как настройка, и место хранения файлов спецификации должно быть четко определено.

Кроме того, проект генерации YAML создает документы Swagger с помощью метода сборки строк. Этот подход понятен, но по мере усложнения структуры документа могут возникнуть пропуски или ошибки с отступами. Для того чтобы развить его до уровня операционной библиотеки, также следует рассмотреть использование библиотеки сериализации YAML или объектов модели OpenAPI.

Итог

Основной идеей этого PoC было проверить способ предоставления спецификаций API на основе Swagger, сохраняя существующие ограничения среды разработки клиента. Клиент желал проверки спецификации API через Swagger, а команде разработчиков необходимо было уменьшить нагрузку по ручному составлению документов и обновлению спецификаций.

Для этого была разработана центральная Swagger-сервер, и можно было выбирать и просматривать несколько файлов Swagger YAML через Swagger UI. Более того, проект был преобразован в библиотеку, чтобы каждая команда разработчиков могла просто применить пользовательскую аннотацию, и в момент развертывания проходить через requestBody и response VO для генерации Swagger YAML.

В заключение, этот проект стал PoC по проверке способа автоматической генерации и обмена спецификациями API через общую библиотеку и центральный Swagger-сервер, а не по непосредственному внедрению Swagger в каждую службу. Это имеет значение, так как было создано основание, позволяющее команде разработчиков клиента проверять опубликованные спецификации API на одном и том же Swagger UI.

NZ

Site footer