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

Понимание событийной версии

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

Версию можно реализовать на различных уровнях:

  • Схема-версия — Само определение схемы несет идентификатор версии (например, ). Это наиболее явный подход и хорошо работает с реестрами схем.
  • Платежная нагрузка — Полезная нагрузка события включает в себя поле версии (например, ), которое сообщает потребителю, какую схему использовать для десериализации.
  • Метадата-версия — информация о версии хранится в заголовках сообщений или метаданных конверта, отдельно от полезной нагрузки. Это сохраняет полезную нагрузку чистой, но требует, чтобы потребитель разобрал заголовок перед чтением тела.

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

Стратегии эволюции схем

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

Проверка схемы

Используйте формальный язык определения схемы и инструмент проверки для обеспечения соблюдения правил структуры данных. Наиболее популярными вариантами являются JSON Schema, Apache Avro и протокольные буферы (Protobuf). Эти языки обеспечивают встроенные механизмы эволюции, такие как значения по умолчанию, дополнительные поля и режимы совместимости. Валидация гарантирует, что каждое произведенное событие соответствует ожидаемой структуре, снижая вероятность немого искажения данных ниже по потоку.

Обратная совместимость

Изменение схемы обратно совместимо, если потребитель, написанный для новой схемы, все еще может читать события, произведенные старой схемой.

  • Добавление дополнительных полей — Новые поля должны быть необязательными с разумными по умолчанию (например, или нулевым значением). Старые события просто опускают эти поля, и потребитель использует по умолчанию.
  • Добавление новых значений числа — Новые значения числа могут быть добавлены до тех пор, пока они не нарушают существующую логику. Потребители должны изящно обрабатывать неизвестные значения.
  • Создание полей, не подлежащих нулю — изменение требуемого поля на необязательное обратно совместимо; обратное — это изменение.
  • Использование типовых рекламных акций — расширение поля (например, → , → ) часто безопасно, но сужение может привести к потере данных.

Вперед Совместимость

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

  • Толерантные читатели — Потребители должны игнорировать неизвестные поля во время десериализации. Большинство форматов схем поддерживают этот вариант: опция Avro , опция Protobuf и опция сохранения неизвестного поля, а также JSON Schema .
  • Значения по умолчанию для новых полей — Производители могут заполнять новые поля значениями по умолчанию, когда потребитель не может их использовать, но это действительно проблема обратной совместимости.
  • Избегание структурных изменений — Переименование полей, изменение типов или реорганизация вложенных структур обычно нарушает совместимость спереди.

Версия в метаданных

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

Схемные реестры

Реестр схем представляет собой централизованную службу, которая хранит и проверяет схемы в нескольких версиях. Он обеспечивает соблюдение правил совместимости (например, обратная, передняя, полная или ни одна) и предоставляет потребителям возможность получить схему, необходимую для десериализации события. Смешанный реестр схем наиболее широко используется для систем на основе Kafka, но есть альтернативы с открытым исходным кодом, такие как реестр Apicurio и реестр Azure Schema. Использование реестра позволяет автоматизировать проверки совместимости в трубопроводах CI / CD и предотвращает развертывание несовместимых схем в производстве.

Реализация Версии на практике

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

Выбор формата сериализации

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

  • Apache Avro — Предназначен для эволюции схем. Поддерживает режимы обратной, прямой и полной совместимости. Использует компактный двоичный формат с сильной типизацией. Хорошо интегрирован с реестром сменных схем. Лучше всего подходит для Java-ориентированных экосистем Kafka.
  • Протокольные буферы (Protobuf) — Также поддерживает эволюцию с помощью номеров полей и дополнительных полей. Более эффективен, чем Avro для некоторых рабочих нагрузок. Хорошо работает в системах полиглотов с gRPC. Правила совместимости менее встроенны, но могут применяться со сторонними инструментами, такими как Buf.
  • JSON Schema — Человеческая читаемость, широко поддерживаемая и простая в отладке. Никакая встроенная двоичная сериализация; обычно используется с JSON. Эволюция управляется через спецификацию (например, , ). Лучше всего подходит для систем, которые отдают приоритет читаемости и гибкости инструмента по эффективности провода.

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

Поддерживать матрицы совместимости

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

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

Автоматизация тестирования совместимости

Ручные проверки совместимости схем быстро становятся неуправляемыми. Интеграция проверки схемы и проверки совместимости в ваш трубопровод CI/CD. Каждый раз, когда производитель меняет схему, трубопровод должен:

  1. Зарегистрируйте новую схему против реестра схем с определенным режимом совместимости.
  2. Если регистрация не удается, прекратите сборку и попросите команду исправить схему (или явно перевернуть версию события).
  3. Запустите интеграционные тесты с реальными потребителями, использующими новую схему, чтобы улавливать проблемы с временем выполнения.
  4. В случае успеха, опубликуйте новую версию схемы вместе с записью в журнале изменений.

Такие инструменты, как плагин Maven или , настраиваемые сценарии оболочки (и теперь GitHub Actions) могут автоматизировать это. Для Protobuf Buf CLI обеспечивает надежную команду , которая обеспечивает соблюдение правил совместимости.

Коммуникация и документация

Изменения схемы - это неявные контракты API. Они должны быть сообщены так же, как и любое другое изменение API. Поддерживайте журнал изменений для каждого типа событий, отмечая, что изменилось, почему и какие гарантии совместимости применяются. Используйте инструмент документации схемы (например, Backstage, созданный сайт из вашего реестра схем), чтобы команды могли просматривать доступные схемы и их историю. Когда прорывное изменение неизбежно, объявляйте об этом заранее, предоставляйте окно миграции и убедитесь, что все потребители обновлены до развертывания новой схемы.

Обработка прорывных изменений

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

  • Разновидности тем — Составлять мероприятия по новой теме (например, ), в то время как старые потребители продолжают читать по старой теме. Это чистая, но дублирующая инфраструктура и требует, чтобы потребители подписывались на обе темы во время миграции.
  • Версия событий набирает обороты — Сохраняйте одну и ту же тему, но увеличивайте количество версий. Потребители должны проверить версию и решить, как десериализировать. Это позволяет избежать распространения темы, но добавляет разветвленную логику в потребителях.
  • Дуал пишет — Для переходного периода производитель издаёт как старый, так и новый формат событий. Это часто используется в качестве ступеньки, пока потребители мигрируют. Это удваивает трафик и увеличивает сложность, поэтому оно должно быть временным.

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

Передовые соображения

Когда версия событий и эволюция схемы пересекаются с поиском событий, полиглотовыми средами или специализированными магазинами событий, возникают дополнительные нюансы.

Источник событий и эволюция схемы

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

Версия в среде Polyglot

Когда производители и потребители пишут на разных языках, вы должны убедиться, что формат сериализации и определение схемы согласуются между языками. Avro и Protobuf имеют надежную генерацию кода для многих языков, но каждый язык может обрабатывать неизвестные поля или значения по умолчанию немного по-разному. Тестировать кросс-языковую совместимость на ранней стадии разработки. Используйте реестр схем, который предоставляет агностические сериализаторы языка (например, REST-прокси Confluent для Avro), чтобы избежать дублирования логики разрешения схемы.

Версия с Event Stores

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

Заключение

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