Всем привет! Продолжаем наш цикл статей о внедрении подхода Design API First на проектах нашей компании. Ранее мы рассмотрели использование этого подхода, описали плюсы и минусы, узнали, как на практике выглядит проектирование API на примере сервиса аутентификации. Сегодня расскажем о том, как мы встраиваем Design API First в наш конвейер разработки, подробно остановимся на инструментах, помогающих с технической точки зрения организовать этот процесс. Объясним, как реагировать на изменения требований и обеспечивать версионность, а также что использовать для мокирования данных. Рассмотрим различные варианты применения: для нового проекта, для существующего проекта (где изначально был Code First).
Вместе с коллегами из Архитектурного комитета SimbirSoft мы попытались унифицировать процесс поставки изменений в контрактах, таким образом упростив жизнь командам разработки и ускорив внедрение изменений в проектах с «Непрерывной доставкой и/или развертыванием» (Continuous delivery and Continuous deployment). Для проверки жизнеспособности данного подхода в наших условиях мы используем Design API First для проектирования прикладного слоя сервисов API, предназначенных прежде всего для взаимодействия со слоем Frontend.
Такой уровень взаимодействия был выбран неслучайно, поскольку он является условной границей разделения двух миров — Frontend и Backend. Они взаимодействуют между собой согласно единому источнику: спецификациям контрактов, определяющих предметную область и функциональные возможности этого взаимодействия.
В разрезе периодических изменений спецификации необходимо применять эти изменения в обоих мирах. Зачастую реальность устроена так, что владельцем контракта является команда Backend, и изменения, вносимые в спецификацию, сначала применяются в API Backend (подход Code First), а затем поддерживаются на стороне Frontend.
Нужно отметить, что при создании небольших программных продуктов такой подход может быть необоснован. Однако в случае итеративной разработки продуктовых решений, сложных многоуровневых систем или платформ, ориентированных на веб и массового пользователя, предполагающих множественные интеграции, последовательный рост и развитие, подход Design API First для разработки API — незаменимый помощник и, как минимум, заслуживает внимания в плане общего знакомства.
В рамках нашей задачи мы инвертируем поток обновления контрактов (относительно Code First) согласно подходу Design API First.
Это позволит командам frontend-разработки получить оперативные изменения спецификаций и приступить к реализации изменений параллельно с командой Backend.
Для успешного решения задачи и достижения поставленных целей потребуется решить ряд очевидных вопросов, которые можно сгруппировать следующим образом:
Организационные (например, взаимодействие архитектора, аналитика, разработчика, которые занимаются проектированием спецификации; разработка регламента внесения изменений в спецификацию, уведомление команд о ее готовности и др.).
Структурные (хранение артефактов проектирования в Gitlab, настройка Gitflow, изменения в CI/CD).
Функциональные (поддержка mock-серверов для актуальных версий спецификации, использование ApiGateway в среде разработки с настроенной на эти сервера маршрутизацией).
Для предварительной оценки сложностей во внедрении подхода Design API First в рабочие процессы, а также с целью выявления «подводных камней», мы использовали способ, похожий на «стрельбу трассирующими» из книги «Программист-прагматик. Путь от подмастерья к мастеру» (Дейв Томас и Энди Хант), то есть создали одно простое программное решение по методологии Design API First.
Предварительно мы определили предметную область: для примера взяли файловое хранилище (Рис. 2), описали функциональные и нефункциональные требования (Таблица 1), описали основные элементы этого сервиса (Таблица 2) и протоколы интеграции (Таблица 3), сформировали список первичных тестовых сценариев использования.
Таблица 1. Требования к реализации системы.
Функциональные требования |
1. С системой могут работать как авторизованные, так и неавторизованные пользователи. 2. Зарегистрированный пользователь может загрузить файл в хранилище: – Размер файла не более 1 Мб. – Один пользователь не может загрузить более 10 файлов в минуту. 3. Зарегистрированный пользователь может удалить файл из хранилища. 4. Зарегистрированный пользователь может изменить свое имя. 5. Зарегистрированный пользователь может посмотреть статистику по своему файлу: количество загрузок, имена пользователей загрузивших файл. 6. Любой пользователь может просмотреть список всех загруженных файлов. – В списке выводится ссылка на файл, наименование файла, имя владельца, количество загрузок файла. – Поиск по названию файла и имени владельца. – Сортировка по имени файла, имени пользователя, количеству загрузок файла. – Пагинация. 7. Любой пользователь может загрузить файл из хранилища. После каждой загрузки счетчик загрузок файла увеличивается на 1. |
Требования по реализации системы |
1. Пользователь взаимодействует с системой через единый шлюз доступа по протоколу HTTP. 2. Система состоит из нескольких отдельных сервисов. 3. У каждого сервиса своя БД. 4. Сервисы взаимодействуют асинхронно, передавая и получая события об изменениях через шину. 5. Падение одного сервиса не должно приводить к недоступности всей системы. 6. Допускается «согласованность в конечном счете». |
Таблица 2. Описание элементов системы.
Код |
Имя |
Назначение |
G1 |
Шлюз |
Принимает все запросы пользователей. Выполняет роутинг запросов к сервисам. Участвует в процессе аутентификации/авторизации. |
S1 |
Сервис авторизации |
Авторизация по логину и паролю. Регистрация новых пользователей. Изменение профиля пользователя. |
B1 |
Пользователи |
Хранение профилей пользователей имеющих доступ к системе. |
S2 |
Сервис загрузки файлов |
Загрузка произвольных файлов в хранилище. Удаление файла из хранилища. |
S3 |
Сервис выгрузки файла |
Загрузка файла из хранилища по запросу пользователя. Ведение истории просмотра файла: кто и когда загружал файл. |
B2 |
Хранилище файлов |
Хранение содержимого файла. Хранение атрибутов файла. Хранение истории загрузок файла. |
S4 |
Сервис отчетов и поиска |
Поиск файлов по атрибутам с сортировкой, фильтрацией и пагинацией. Генерация отчетов о загруженных файлах. |
B4 |
Отчеты и списки |
Хранение отчетов в готовом виде. Хранение списков файлов со всеми атрибутами в готовом виде. |
D1 |
Шина |
Асинхронный обмен данными между сервисами (pub/sub). |
Таблица 3. Протоколы интеграции.
Код |
Протокол |
Описание |
U1G1 |
HTTP/JSON/REST |
Все пользовательские запросы. Имя сервиса указывается в URL запроса (например, http://localhost/files/1). |
G1S1 |
HTTP/JSON/REST |
Запрос на аутентификацию по логину и паролю. Запрос на авторизацию. Запрос на изменение профиля пользователя. |
G1S2 |
HTTP/JSON/REST |
Запрос на загрузку файла в хранилище (POST). Запрос на удаление файла (DELETE). |
G1S3 |
HTTP/JSON/REST |
Запрос на загрузку файла из хранилища (GET). |
G1S4 |
HTTP/JSON/REST |
Запрос списка (GET). Запрос отчета (GET). |
S1D1 |
AMQP/JSON |
Сообщение о регистрации нового пользователя. Сообщение об изменении профиля пользователя. |
S2D1 |
AMQP/JSON |
Сообщение о загрузке нового файла. Сообщение об удалении файла. |
S3D1 |
AMQP/JSON |
Сообщение о просмотре файла пользователем. |
D1S4 |
AMQP/JSON |
Все сообщения из S1D1, S2D1, S3D1. |
Далее мы разработали эталонную спецификацию OpenSpec и создали необходимые диаграммы взаимодействия по примеру тех, которые описаны в нашей предыдущей статье. Следующим шагом было создание структуры хранилища (Рис. 3).
Мы также апробировали инструментальные средства (mock-server, настройку ApiGateway) и проверили техническую готовность нашей инфраструктуры (CI/CD) поддержать итеративную разработку в рамках Design API First.
Учитывая доминирование Code First в практической разработке API среди наших команд и текущих проектов, а также издержки Design First, связанных с ошибками проектирования и необходимостью вносить изменения в контракт на этапах написания кода, было целесообразно сразу отработать два сценария:
Разработка проекта API идет в парадигме Code First, поэтому мы хотим прибегнуть к подходу Design API First, применяя его для проекта или его части.
При реализации спецификаций и сценариев на уровне программирования внести изменение в первоначальный контракт.
Не дожидаясь завершения этапа проектирования, команда Backend написала программное API, руководствуясь первоначальными требованиями в части работы с файлами по традиционному подходу Code First (сценарий 1). Далее взяли спецификацию API для авторизации (описанную в предыдущей статье) и реализовали эту часть программного API согласно спецификации.
После этого мы инициировали необходимость изменения спецификации API по работе с файлами (сценарий 2), провели еще одну итерацию проектирования и разработки, тем самым проверяя готовность команд (аналитики, разработчики, архитекторы) на взаимодействие в ключе подхода Design API First в условиях перехода с Code First или необходимости внести изменения в текущую версию API на поздних этапах (при написании кода согласно полученным артефактам проектирования).
Заключение
Таким образом мы получили систему, которая удовлетворяет всем требованиям, описанным выше, и позволяет продолжать разработку в режиме Design API First.
В наших следующих статьях мы расскажем, как указанные сценарии выше были отработаны командой проектирования API. А также какие изменения потребовалось внести в код проекта Backend для перехода с Code First на Design API First. Небольшой спойлер :) Часть Backend написана на .Net 6, а для генерации моделей мы использовали кодогенераторы Roslyn совместно с NSwag.