Бинго-бонго и джимбо-джанго!
Недавно, была поставлена задача, собрать MVP:
- front-end приложение, состоящее из нескольких микро-фронтов (Build-time интеграция)
В добавок к этому были следующие ограничения:
- разработка веб-апп должна быть на VueJS+TS
- переиспользовать общие компоненты (дизайн система, сетевой слой, проверка прав пользователя)
- использование контрактной системы
- интеграция с новыми сервисами должна быть максимально быстрой
Рассмотрим упрощенный пример с аналогичным стеком, имеем
- backend – FastAPI
- front-end – VueJS + TS
Как происходит обычно?
После того как было реализовано API на стороне backend, начинается процесс интеграции с ним. Как правило, это ручной процесс. Разработчик должен описать request-schema's, response-schema's, обработку http-ошибок, модели сущностей и так далее. Выглядит как монотонная и трудоемкая операция. Не стоит огорчаться раньше времени
Как вы знаете, в FastAPI одной из фишек является возможность описывать сущности, формат запросов и ответов используя pydantic + автоматически генерировать документацию для API-методов в swagger-схему. Хм, давайте подумаем как можно использовать готовое описание апи в дальнейшем?
Решение проблемы
Одно из решений проблемы – "слепить" наши два приложения в одно, используя jinja2, благо в FastAPI есть такая возможность. Но таким образом мы получаем ворох проблем:
- монолитное приложение
- сложно распараллелить работу команды, тк есть связанные компоненты
- сложность внесения отдельных изменений
- сложность сборки, поддержки зависимостей
- сложность "разделения" в будущем на отдельные два приложения
- сложность доставки/отката сборки приложения (деплой)
- сложность доставки и ускорения раздачи "статики"
- данное решение противоречит поставленной задаче
Получается все как-то коряво и не то. Где наши самостоятельные микро-сервисы? Где новые технологии? Со временем такой проект станет доставлять больше негативных эмоций не только разработчикам, но и BO, PO, аналитикам, в конце концов команде сопровождения. В общем имеем всем знакомые проблемы при работе с монолитом (но монолиты и опыт работы с ними бывает и положительный).
Решаем проблему со вкусом
Итак, у нас имеется swagger-схема. Как мы знаем, сообщество swagger очень большое, да в целом имеет большое кол-во вспомогательных инструментов. Один из таких – swagger-codegen. Говоря простыми словами – он позволяет из OpenAPI (swagger-схемы), используя подход кодо-генерации, подготовить библиотеку-"клиента". Один из ярких примеров использования такого подхода – python клиент для взаимодействия с апи kubernetes
Как подготовить клиента самому?
Для начала необходимо установить openapi-generator, подробный процесс установки описан тут.
Приступаем к сборке клиента
Собираем клиента для Python
openapi-generator generate -i https://<host>:<port>/openapi.json -g python --additional-properties=packageName=<repository-name (сокращенный)>_api,library=asyncio -o <path>
Собираем клиента для TypeScript
Тут имеются небольшие отличия (подробное описание возможных параметров - тут)
openapi-generator generate -i https://<host>:<port>/openapi.json -g typescript-axios --additional-properties=packageName=<repository_name(сокращенный)>_api,withSeparateModelsAndApi=true,modelPackage=models,apiPackage=api -o <path>
- withSeparateModelsAndApi - разделять хранение апи и модели в разных директориях
- modelPackage – где будут лежать модели
- apiPackage - где будут лежать апи-методы
Кажется, что задача решена, но нет. Каждый раз запускать команды выше, куда-то копировать эти файлы, как-то определять, где старая или актуальная версия …
Так, как источником swagger-схемы является репозиторий бэкенд-приложения, дальнейшие шаги будут производиться в нем
- для версионирования выбираем любой удобный и принятый в вашей команде инструмент (git tag, файл version.txt, comitizen) + интегрируем этот шаг в наш пайплайн в gitlab-ci.yml (в моем случае это был файл version.txt, передаваемый в виде артефакта между стадиями пайплайна)
- шаг с запуском линтеров
- шаг установки необходимых зависимостей
- шаги для генерации и публикации собранных библиотек
Теперь про важное. Внимательный читатель обратил внимание на последний скрин-шот и задался вопросом: а куда, собственно, мы публикуем собранные клиенты? Тут все зависит от того, что имеется в вашей компании, команде – nexus, npm-registry, gitlab package registry, e.t.c. Если ранее не было опыта работы с такими системами, то советую ознакомиться с npm-registry в gitlab package registry.
В целом это все. После того как настроили публикацию собранного клиента, остается только добавить собранную библиотеку в качестве зависимости в front-end приложении.
Заключение
Мы проделали немалую работу, которая в будущем скажется на процессе интеграции между сервисами.
Что мы имеем:
- backend приложение имеет swagger-схему aka документацию
- на основе swagger-схемы мы можем автоматически генерировать библиотеку-клиента под любой ЯП
- каждая сборка клиента имеет свою версию
- использование контрактной системы между микро-сервисами
- наличие готовых клиентов – ускоряет процесс интеграции и разработки