Инструментарий техлида
Прошедший выпуск Podlodka Techlead Crew был посвящен межсервисному взаимодействию. Как и всегда, были знакомые вещи и не очень. В конечном счете, невозможно знать и помнить обо всём. Вот и я выписал себе очередной набор инструментов, которые показались мне крайне интересными. Чем и спешу поделиться.
Contract First
Продолжаю наблюдать популяризацию идеологии Contract First. С ростом компании растет количество сервисов и продуктов, что неминуемо приводит к необходимости их интеграции и согласования работ между командами разработки. Идеология Contract First — один из инструментов для решения этих проблем, масштабирования компании и процессов. Тема абсолютно не новая, но с популяризацией микросервисов и изобилием протоколов и технологий межсервисного взаимодействия вновь начала набирать популярность.
Взяв курс на Contract First, очень важен вопрос выбора инструментов, поскольку они должны унифицировать процесс, контролировать его и ускорять. Иначе говоря, хотелось бы иметь какой-то стандарт или общий подход к описанию API (хотя бы на уровне компании), возможность верифицировать спецификации (делать ревью, архитектурный контроль, тестирование контрактов) и создавать их максимально быстро и удобно.
Инструмент от Microsoft для ускорения описания API-спецификаций. Может быть установлен в виде плагина к VSCode (требует установки NodeJS). Спецификация определяется в текстовом файле с расширением `.tsp` и записывается на интуитивно-понятном языке TypeSpec (подмножество TypeScript). Спецификацию можно экспортировать в целевой формат (OpenAPI, JSON Schema, Protobuf), клиентский код или заглушку для реализации сервера (JavaScript, Java, C#). Есть возможность создать свой собственный конвертер. Проект с открытым исходным кодом и активно развивается под лицензией MIT.
Файлы TypeSpec можно положить в Git и получить все преимущества версионирования, контроля изменений, тестирования и т.п. Всё точно так же, как и при написании кода.
Асинхронное взаимодействие продолжает замещать синхронное во многих сценариях и областях, и этот вид взаимодействия также требует унификации и стандартизации. Тут безоговорочным лидером является AsyncAPI. Пока TypeSpec не спешит добавлять поддержку AsyncAPI (но вы можете сделать это самостоятельно), им можно воспользоваться напрямую.
Спецификация для описания асинхронного API и набор инструментов для генерации документации и кода. Спецификация определяется в yaml-формате (есть плагин для VSCode) с достаточно интуитивным DSL. Проект с открытым исходным кодом, который развивается под лицензией Apache-2.0 не менее активно, чем TypeSpec.
Разработка и развитие API требует контроля обратной совместимости. Любые изменения в API должны подвергаться жесточайшему контролю. Помочь в этом может проект OpenAPI-diff.
Утилита для сравнения двух спецификаций OpenAPI (v3.x), подпроект OpenAPITools. Результат сравнения может быть представлен в формате HTML, Markdown, Asciidoc или JSON. Имеет Java-библиотеку, используя которую можно написать автоматический тест для проверки несовместимых изменений. Проект с открытым исходным кодом и активно развивается под лицензией Apache-2.0.
Я затронул тему тестирования контрактов, которую подробно описывает Крис Ричардсон (см. Consumer-driven contract test, Consumer-side contract test, Consumer-Driven Contracts). Насколько применим этот подход в реальной практике в сочетании с вышеописанным инструментарием. В частности, можно ли интегрировать эти инструменты и Spring Cloud Contract.
Спецификация API — это формальное описание структур данных, списка операций и т.п. Контракт — это набор примеров взаимодействия между потребителем API (consumer) и его поставщиком (provider). Таким образом, контракт может использоваться как тестовый набор для независимой проверки как потребителя, так и поставщика. Успешность прохождения таких тестов означает отсутствие проблем с интеграцией.
Если вкратце, то подобную практику использует меньшинство, поскольку формат спецификаций и контрактов очень разный, что обязывает разработчиков спецификаций дополнительно описывать контракты их использования. Честно говоря, аргументация больше похожа на отговорку, но соглашусь, что подобную практику внедрить очень непросто. Однако, если кого-то интересует тема тестирования контрактов, то рекомендую обратить внимание и на проект Pact (спасибо ребятам из чата за эту ссылку).
Спецификация контрактов (DSL), хранилище контрактов и набор инструментов для тестирования контрактов. Имеет поддержку нескольких платформ и языков (JS, JVM, .NET, Go, Python и т.п.). Проект с открытым исходным кодом и активно развивается под лицензией MIT.
Несмотря на существование достойной инструментальной поддержки, ряд коллег высказывают озабоченность. Во-первых, остается вопрос, кто именно должен создавать и развивать спецификации (архитектор, системный аналитик, техлид, разработчик). Во-вторых, спецификация сильно оторвана от кода (реальной жизни), что может вызвать проблемы на уровне её реализации. Отсюда вытекает третье сомнение: при большом числе интеграций создание спецификаций вручную выглядит ресурсоемким и опять же может разъехаться с реализацией. Иначе говоря, было сделано предположение, что в идеале спецификация должна определяться на языке, на котором ведется разработка, тогда это сократит "разрыв" до нуля. В конечном счете сошлись на том, что универсального решения не существует и каждый ведёт дела так, как ему удобно.
Implementation
В современной практике очень часто можно встретить необходимость гетерогенного хранения данных или необходимость в транзакционной логике при выполнении бизнес-процессов с участием нескольких (микро)сервисов. В этом случае на помощь приходит шаблон Saga и два классических подхода к его реализации: оркестрация и хореография. Реализация данного шаблона без использования какого-то фреймворка крайне затруднительна, но даже с фреймворком всё не так просто. Внедрить Saga в существующий проект — ещё то приключение. Существенно упростить жизнь помогает проект Temporal.
Сервер исполнения бизнес-процессов. Бизнес-процесс задается декларативно на интуитивно понятном DSL с использованием клиентской библиотеки (Java, C#, Go и др.). Бизнес-процессы исполняются распределённо, гарантируется согласованность состояния, масштабируемость и отказоустойчивость. В качестве хранилища состояний используется Apache Cassandra, но можно использовать и другие базы, в частности PostgreSQL и MySQL. Проект с открытым исходным кодом и активно развивается под лицензией MIT.
Всё выглядит так, что Temporal позволяет достаточно быстро добавить Saga в существующий проект, особенно если он написан без использования таких структурообразующих фреймворков, как Spring.
Еще одна интересная, но очень сложная задача — это сервис push-уведомлений. Если вы не можете или не готовы использовать какой-нибудь облачный сервис, то писать свою реализацию, пожалуй, не самый лучший вариант. В такой ситуации на помощь приходит Centrifugo.
Сервер для организации push-уведомлений. Берет на себя множество проблем, которые приходится решать при реализации сервиса уведомлений. Centrifugo встраивается в архитектуру проекта как простой и самодостаточный PUB/SUB сервис. Уведомляемая сторона может взаимодействовать с Centrifugo через наиболее предпочтительный протокол (WebSocket, SSE, gRPC и т.п.). Бэк взаимодействует с Centrifugo через HTTP API или клиентскую библиотеку. Проект с открытым исходным кодом и активно развивается под лицензией Apache-2.0.
Observability
Было очень много сказано про техники обеспечения прочности и устойчивости. Два ярких момента, на которые я обратил внимание.
- Крупные компании стараются перекладывать всё больше ответственности с кода на инфраструктуру. Например, выполнение некоторых повторов (Retry) можно возложить на инфраструктуру. Однако на этом уровне имеет смысл повторять только HTTP 5xx.
- Распределенная трассировка на базе OpenTelemetry — это неотъемлемая часть современного приложения.
При этом было отмечено, что очень важно не маскировать бизнес-ошибки за кодами HTTP 2xx (например, "HTTP 200 Недостаточно средств"). Это создает серьезные проблемы при сборе статистики о работе системы на уровне инфраструктуры.
Что касается распределённой трассировки, то справедливо подмечено, что её хранение может обойтись очень дорого, причём большая часть событий будет неинтересна. По этой причине следует рассматривать данный подход в сочетании с сэмплированием — выдергивать из потока и сохранять только те события, которые потенциально могут представлять интерес.
P.s. Если вам интересна данная тематика, присоединяйтесь к моей новостной ленте в Telegram или здесь. Буду рад поделиться опытом. ;-)