АрхитектураМикросервисы и интеграцииИнтеграционный архитектор

Сервис потребитель считает поле обязательным, а команда владелец события выпускает новую схему. Разберите н...

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

{"type":"UserRenamed","userId":"42","displayName":"Анна"}
{"type":"UserRenamed","userId":"42","fullName":"Анна Петрова"}

Какой принцип совместимости должен определить порядок изменения продюсера и потребителя?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Новая версия события нарушает обратную совместимость: старый потребитель ожидает обязательное поле displayName, а продюсер его удалил и заменил на fullName. Для независимого выпуска сервисов продюсер должен сначала сохранять старый контракт, а новые поля добавлять как необязательные; потребитель обновляется до поддержки нового формата до удаления старого поля.

Исторический контекст

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

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

Постановка проблемы

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

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

Подробное решение

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

  1. Продюсер продолжает публиковать displayName.
  2. При необходимости он добавляет fullName как новое поле.
  3. Потребители постепенно начинают использовать fullName, сохраняя обработку старого формата.
  4. После подтверждения, что старые потребители больше не зависят от displayName, поле выводят из эксплуатации по согласованной политике хранения событий.
{ "type": "UserRenamed", "userId": "42", "displayName": "Анна", "fullName": "Анна Петрова" }

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

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

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

Ситуация из практики

Сервис профилей публиковал событие CustomerChanged. Сервис уведомлений использовал displayName, аналитика — fullName, а старый экспорт данных воспроизводил события за последние тридцать дней. Команда профилей хотела заменить displayName на более точное поле fullName.

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

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

Что кандидаты часто упускают

  1. Достаточно ли просто добавить новое поле, чтобы гарантировать совместимость?

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

  1. Как безопасно удалить старое обязательное поле?

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

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

  1. Когда следует выбрать новую версию события вместо расширения существующего?

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

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