Сервисы дружитес. Как платформа упрощает создание интеграций без ошибок

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Всем привет! Кратко обо мне: меня зовут Никита и я уже третий год работаю над тем, чтобы платформа СберМаркета становилась лучше день ото дня. Мой основной язык программирования — Go, но, учитывая специфику платформенной разработки, еще и bash.

В этой статье на примерах разберу, что мешает строить разработчикам надежные интеграции, попутно заглядывая в детали реализации нашей утилиты sbm-cli, шаблона микросервиса и CI/CD. Этот материал я написал в соавторстве с моим коллегой Эмилем Шарифуллиным.

В ходе статьи я еще не раз буду упоминать утилиту sbm-cli. Под спойлером оставляю ее краткое описание. Для большего погружения рекомендую ознакомиться с предыдущими статьями о PaaS и sbm-cli в частности. Раз. Два. Три.

Коротко о sbm-cli

sbm-cli — это утилита, разработанная командой платформы СберМаркета для автоматизации рутиных действий разработчиков.

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

Что вы узнаете из статьи?

  • Разберётесь, что мешает разработчикам строить надежные интеграции.

  • Вспомните, что такое интеграции и контракты.

  • Узнаете, как PaaS оптимизирует процесс разработки и релиза фичей через инструменты для работы с контрактами.

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

Какую проблему мы решаем?

Мы в своей работе ориентируемся на метрику Time to Market: совокупное время, затраченное на разработку фичи от идеи до продакшена. За счет оптимизации процесса разработки и тестирования интеграций, мы помогаем снижать количество инцидентов. А это, в свою очередь, позволяет предоставлять более качественный сервис нашим пользователям и экономить деньги компании.

Мысленный эксперимент

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

Давайте договоримся называть сервис, предоставляющий свой API другим сервисам — провайдером. Сервис, который использует API — консумером.

Ситуация #1 Поломка обратной совместимости

Команда сервиса-консумера зарелизила новую фичу. С командой разработчиков сервиса-провайдера почему-то обсудить новую интеграцию «забыли». В какой-то момент разработчики сервиса-провайдера, думая, что у них еще нет клиентов в проде, решают оптимизировать работу эндпоинта и удаляют поле, которое слишком затратно расчитывать по времени. Результат очевиден, обратная совместимость нарушена, сервис отдает 500.

Как предотвратить инцидент?

Нужно четко понимать отношения между сервисами. Не давать провайдеру ломать обратную совместимость, если хотя бы один сервис уже использует их API. Лучшее решение — проверка обратной совместимости на этапе CI/CD. А еще лучше — узнать это до этапа CI/CD, локально запустив команду.

Ситуация #2 Несогласованные контракты

Разработчик должен написать сервис-консумер. Требуется запросить персональную скидку для покупателя по идентификатору из внешнего сервиса и применить ее на этапе чекаута. Внешний сервис разрабатывает другая команда, поэтому наш герой, собрав в кулак свои софт скиллы, отправляется к разработчикам сервиса-провайдера за подробностями.

Они договариваются заранее о контракте и разрабатывают консумера и провайдера одновременно. И все вроде бы хорошо, тестирование прошло успешно. Но в какой-то момент выясняется, что сервис выдает скидки более 100%.

В процессе отладки выяснили, что сервисы использовали разные версии генераторов кода. Одна из версий генератора содержала баг и не обновлялась два года.

Как предотвратить инцидент?

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

Выше описаны примеры, приближенные к действительности. Из личного опыта знаю, иногда в компаниях без платформы нет даже формальной спецификации. А знания о работе API хранятся только в головах разработчиков или в переписке.

Не басня, но притча
Не басня, но притча

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

Способ разработки API курильщика

В среднем, разработка интеграции происходит следующим образом:

  1. Разработчики сервиса-консумера и сервиса-провайдера согласовывают план взаимодействия. Утверждают схему и протоколы.

  2. Разработчик сервиса-провайдера описывает спецификацию API.

  3. Разработчик сервиса-провайдера пересылает спецификацию.

  4. Разработчики пишут и/или генерируют код сервера и клиента согласно спецификации.

  5. Сервисы начинают взаимодействовать на стейджинге или в проде.

  6. Спецификация изменяется разработчиком сервиса-провайдера.

  7. Тут идем на шаг 3 и так по кругу.

Не сложно заметить, что в текущей схеме есть сразу несколько возможностей для создания инцидента «на ровном месте»:

  • Отсутствует механизм «устаревания» спецификации. Нет гарантий, что разработчик поделился корректной и актуальной спецификацией.

  • Написание кода руками по спецификации мало того, что затратно, так еще и чревато появлением багов. Человеческий фактор никто не отменял.

  • Нет шага валидации контрактов при деплое приложения в стейджеое и продовое окружение.

Способ разработки API здорового человека

Вот так должен выглядеть оптимизированный процесс разработки интеграции.

Разработчик сервиса-провайдера:

  1. Описывает спецификацию.

  2. Запускает валидацию локально (проверятся синтаксис и обратная совместимость).

  3. Отправляет спецификацию в репозиторий.

  4. В CI/CD валидация выполняется идентично локальному запуску.

  5. Запускается генерацию сервера локально.

  6. Сервис наполняется логикой.

  7. Запускает деплой на стейдж.

Разработчик сервиса-консумера:

  1. Регистрирует в конфиге своего сервиса зависимость от стороннего сервиса и указывает актуальную ветку.

  2. С помощью одной команды скачивает актуальную (!) спецификацию и генерирует клиента.

  3. Реализует бизнес логику.

  4. Запускает валидацию локально (проверятся синтаксис и обратная совместимость).

  5. Отправляет спецификацию в репозиторий.

  6. В CI/CD валидация выполняется идентично локальному запуску.

  7. Происходит регистрация клиента.

  8. Запускается деплой на стейдж.

Какие плюсы можно увидеть, сравнивая эти два подхода?

Как минимум, коммуникация разработчиков значительно упрощается. Все, что им нужно обсудить это сроки реализации и сам контракт. Спецификация является отправной точкой, технические детали скрываются за интерфейс sbm-cli и CI/CD. Разработчики могут сконцентрироваться на логике приложения.

На данный момент у нас есть картина «идеального» процесса разработки API. Мы почти готовы перейти к реализации, но сперва убедимся, что говорим на одном языке. В этом нам поможет краткий обзор существующих способов описать и реализовать интеграцию.

Что такое интеграция?

Разрабатывая приложения в клиент-серверной архитектуре, необходимо обеспечить надежный канал связи (интеграцию) между клиентом и сервером. Cap!

Для построения интеграции между частями распределенного приложения используют интерфейс программирования приложения (или API). API предоставляет доступ к «ручкам» приложения, абстрагируя сложность каждого отдельного компонента.**

Главная задача API — обеспечение работоспособности интеграции компонентов системы, удовлетворяющих описанному контракту или спецификации (это не одно и то же).

☝️ PaaS оперирует абстракциями высокого уровня, будь то Контракт или Спецификация. О них мы поговорим более подробно ниже, а пока, убедимся, что у нас нет разночтений в плане терминологии.

Интеграции вашего приложения почти наверняка организованы одним из следующих способов: REST, gRPC или Kafka (Nats, MQ, etc).

Выглядит привычно и большинство из нас обсуждает их как альтернативы друг другу, но более корректным является следующее сопоставление:

  • JSON-over-HTTP;

  • Protobuf-over-RPC;

  • JSON-over-Messaging-System.

На самом деле каждый метод являет собой «слоеный пирог» из нескольких протоколов, технологий, стандартов или фреймворков:

  • REST (или RESTful) это набор соглашений (или архитектурный стиль) проектирования API поверх протокола HTTP, в котором данные передаются в текстовом виде (JSON). Взаимодействие синхронно.

  • gRPC это фреймворк для вызова удаленных процедур или RPC (архитектурный стиль противопостовляемый RESTful), поверх протокола HTTP/2, данные передаются в бинарном формате. Взаимодействие может выполняться синхронно, или, в случае gRPC Streaming, асинхронно.

  • Messaging System (e. g. Kafka, RabbitMQ, etc) это шина данных, основанная на протоколе TCP. Шина данных не обязывает вас использовать какой-то конкретный формат данных. Вызовы выполняются асинхронно.

А теперь, чтобы стало понятнее, представим все это в виде таблицы.

Название

Протокол

Архитектурный стиль

Фреймворк

Формат данных

Асинхронность

REST

HTTP

REST

-

XML, JSON, etc

Нет

gRPC

HTTP/2

RPC

gRPC

Protobuf

Да (для gRPC Streaming)

SOAP

SOAP

SOAP

-

XML

Да

Messaging System

TCP

Messaging

-

XML, JSON, Protobuf

Да

Разумеется, перечень описанных способов не является исчерпывающим. Для большей осведомленности рекомендую обратить внимание на эту статью.

Когда использовать синхронный метод, а когда асинхронный?

Ситуации, в которых следует выбрать синхронные интеграции:

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

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

  3. Простота в отладке. Синхронные вызовы могут быть проще в отладке, поскольку результаты операций мгновенно возвращаются, и процесс может быть более прозрачным для разработчика.

Ситуации, в которых следует отдать предпочтение асинхронным интеграциям:

  1. Обработка долгих операций. Если ваши операции требуют значительного времени для выполнения, асинхронные интеграции могут быть более предпочтительными. Например, обработка больших объемов данных или выполнение длительных вычислений.

  2. Событийно-ориентированные системы. Когда ваши системы работают в реакции на события, такие как изменения состояния или появление новых данных. Асинхронные интеграции могут обеспечивать более естественный способ реагирования на события.

  3. Очереди сообщений. Использование асинхронных сообщений в виде очередей (например, RabbitMQ, Apache Kafka) может быть полезным для разделения компонентов системы и обеспечения более гибкой и масштабируемой архитектуры.

  4. Повышенная масштабируемость. Асинхронные интеграции могут облегчить масштабирование системы, так как они позволяют более гибко управлять нагрузкой.

Когда использовать gRPC, а когда REST?

Для ответа на этот вопрос разберем основные плюсы каждого.

Основные плюсы REST:

  1. Простота и универсальность.

  2. Легкость в понимании и использовании. REST API легко понимать и использовать, особенно с использованием стандартных инструментов, таких как браузеры.

  3. Совместимость с ограниченными сетевыми условиями. REST легко адаптируется к различным условиям сети и обычно лучше себя ведет в случае ограниченной пропускной способности или ненадежной сети.

Основные плюсы gRPC:

  1. Производительность. gRPC обеспечивает высокую производительность за счет использования протокола HTTP/2 и бинарного формата данных Protocol Buffers.

  2. Сильная типизация. Позволяет строить более надежные интеграции.

В общем, если потребителями вашего бэкенда являются фронтенды или сервисы сторонних разработчиков, рекомендую использовать более простой, понятный и распространенный REST.

Для внутренних коммуникаций следует выбрать gRPC как более экономичный и производительный метод синхронных интеграций.

Что такое контракт?

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

Можно, конечно, описать контракт с помощью натурального языка, но чаще используется специализированная нотация (или Schema Definition Language, SDL), позволяющая свести количество вариантов трактовки к минимуму.

Примерами языков для описания контрактов могут служить Protobuf и OpenAPI.

Каждый из них имеет свои плюсы и минусы. Каждый имеет личный «зоопарк» в виде генераторов, валидаторов и прочих инструментов автоматизации.

☝️ Это обсуждение выходит далеко за рамки текущей статьи, думаю, мы вернемся к нему в рамках рассказа про контрактное тестирование в PaaS.

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

Для большего погружения советую прочитать статью Яна Робинсона, в которой сопоставляются два этих понятия в контексте контрактных тестов в микросервисной архитектуре.

Когда все точки над i расставлены, договоримся в рамках сегодняшнего рассказа читать «контракт», подразумевать «спецификация». Главное — контракт позволяет описать интеграцию. На основе этого описания можно построить целую экосистему инструментов, дополняющих друг друга. Облегчающих разработку надежных интеграций как минимум. Позволяющих избегать инцидентов как максимум.

Code First vs API First

Подход Code First предполагает, что разработка API начинается с написания кода и реализации бизнес-логики, а затем автоматически генерируется спецификация API на основе этого кода.

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

Что первично? Контракт или код?

Вопрос, конечно, не из простых. С одной стороны, баг в интеграции между двумя сервисами (при сохранении работоспособности) тоже является частью контракта в широком смысле этого понятия

Источник: https://habr.com/ru/companies/sbermarket/articles/776022/


Интересные статьи

Интересные статьи

В процессе разработки разработчик часто реализовывает функционал, который кажется ему логичным даже не задумываясь как этот архитектурный стиль называется. При этом читая про паттерны и шаблоны проект...
Рассказываем о том, как внедряли новомодные AI-инструменты в проект. Как это повлияло на его популярность и что за этим последовало — читайте в статье.Технические особенности проекта: фреймворк — Lara...
Привет, Хабр! Меня зовут Юрий Никулин, и я руководитель направления документирования Cloud. Сегодня расскажу, как мы перешли с документирования в Word на подход docs as code и почему в качестве языка ...
Переехать в микросервисы можно двумя способами. Можно построить платформу — это надежно, но очень сложно. Или можно поднять Kubernetes и начать в него коммитить новые сервисы. Переезд проходит быстро ...
Ошибки допускают все, это нормально. Но их нужно разбирать и принимать решения по их недопущению. Это и называется учится на ошибках. А обеспечение качества - это учиться на чужих ошибках.В этом посте...