Представим себе систему, в которой два сервиса общаются через отправку событий.

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

Всё шло хорошо, пока нам не понадобилось поменять формат события. Встал вопрос: как это сделать, не уронив систему?

Эволюция события

Пример. Сначала мы передавали поля “first_name” и “last_name”, а потом решили что вместо этих двух полей должно быть одно поле “full_name”.

Посмотрим, какие у нас варианты.

Вариант 1. Сначала обновляем консюмер, потом продюсер.

Если консюмер обновится раньше, чем закончатся старые события, то старые события не будут обработаны новым консюмером.

Вариант 2. Сначала обновляем продюсер, потом консюмер.

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

Вариант 3. Обновляем код двух сервисов одновременно.

В этом случае тоже остаётся риск, что в момент релиза мы потеряем часть событий.

Если между обновлением продюсера и консюмера будут созданы какие-то события, то эти события будут потеряны, см. варианты 1 и 2.

Если же консюмеров и продюсеров много, то “попасть” с обновлением в нужный момент станет ещё сложнее.

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

В остальных случаях, вариант сомнительный.

Вариант 4. Выключаем продюсер.

Если условия позволяют, можно сделать так.

  1. Выключаем продюсер. Консюмер заканчивает обрабатывать все события.

  2. Обновляем консюмер. Консюмер готов принимать события в новом формате.

  3. Обновляем и включаем продюсер. Продюсер отправляет новые события, они нормально обрабатываются консюмером.

Если система допускает что продюсер можно на короткое время отключать, то всё в порядке. Мы не потеряли никаких данных.

Вариант 5. Версионирование событий.

“Зашиваем” версию в событие. Тогда консюмер сможет сам определить, пришло старое или новое событие и обрабатывать его либо одним способом либо другим. Главное обновить консюмер раньше чем продюсер.

Хороший вариант, но наличие версий у событий на мой взгляд усложняет систему.

В остальном всё хорошо, так как мы не теряем данные и ничего не выключаем.


Поэтапная миграция

Есть ещё один метод который я называю поэтапной миграцией.

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

На каждом этапе получаем стабильно работающую систему не теряя данные и не останавливая работу.


Поэтапная миграция события. Шаг 0

Исходная ситуация. Оба сервиса используют старую версию события.


Поэтапная миграция события. Шаг 1

Обновляем код продюсера. Добавляем в событие новое поле, не удаляя старые поля.

В коде продюсера будет использоваться только новое поле.

Старый консюмер нормально будет обрабатывать такие “расширенные” события, так как необходимые ему поля есть в событии, а лишние он может игнорировать.


Поэтапная миграция события. Шаг 2

Обновляем код консюмера. Переключаем консюмер на использование нового поля.

Теперь и в коде консюмера будет использоваться только новое поле.

В событиях уже есть новое поле, поэтому можно спокойно переключить консюмер — события будут правильно обработаны как старым, так и новым консюмером.


Поэтапная миграция события. Шаг 3

Обновляем код продюсера. Удаляем старые поля.

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

Теперь обновлено всё что надо было обновить. Событие полностью изменило формат за три шага.