10 правил при построении
Event Driven Architecture
В чем проблема?
При построении систем основанных на событиях не всегда понятно как правильно формировать Domain Event и Integration Event. Если допустить ошибку, то это станет проблемой при развертывании и развитии вашей микросервисной системы.

Ниже показаны 10 правил, которыми я руководствуюсь при формировании событий.
Сообщение типа "Command"
Если у вас есть сообщение типа "Command", то все что описано ниже - не применимо.
Пример сообщения команды:
  • "SendSms"
  • "BlockUser"
  • Старайтесь меньше использовать сообщения типа "Command", это более директивный стиль, который делает ваши сервисы более связанными и зависиммыми друг от друга.
1. Сообщение типа "Event"
Событие (Event) - это констатация факта, которая в сочетании со всеми другими событиями в системе дает полную историю произошедшего. Событие должно как можно точнее описывать, что произошло и почему.
Поскольку событие - это констатация факта, то именовать события нужно в прошедшем времени.
Пример правильного именования события (оранжевые карточки):
2. Пишите в топик только из одного сервиса
Этот микросервис является владельцем каждого события, созданного в этом топике. Это позволяет всегда знать достоверный источник для любого данного события, позволяя отслеживать происхождение данных в системе.
3. Не пишите в один топик события, которые относятся к разным сущностям
Если писать в 1 топик разные события, то этот приведет к сложностям обработке на потребителе. Иногда мы готовы пойти на это не удобство, к примеру, ради обеспечения хронологии событий, которые произошли в следствие изменения сущности.
Но если писать в 1 топик события, которые произошли в следствие изменения разных сущностей - ничего, кроме сложности обработки на потребителе это не принесет.

Допустимо:
Недопустимо:
4. Явно определяйте контракт
Существует два компонента четко определенного контракта на передачу данных:
  • данные, или то, что будет создано (т.е. поля, типы и различные структуры данных)
  • логика запуска, или почему она создается (т.е.например, конкретная бизнес-логика, которая инициировала создание события)
  • Хорошее определение события - это не просто сообщение, указывающее на то, что что-то произошло, а скорее полное описание всего, что произошло во время этого события. С точки зрения бизнеса, это результирующие данные, которые создаются при обработке входных данных и применении бизнес-логики. Это выходное событие должно рассматриваться как единственный источник достоверности и должно быть зарегистрировано как непреложный факт для использования нижестоящими потребителями.
// The user has selected a movie
MovieClick {
  // Id of movie
  movieId: Long,
  
  // Does user watched preview?
  watchedPreview: Boolean
}
Если вы меняете контракт как с точки зрения полей так и логики запуска - убедитесь, что все потребители готовы в работе в новом формате.
5. Формируйте четкую схему с комментариями
Лучший способ обеспечить соблюдение контрактов на передачу данных и согласованность - это определить схему для каждого события. Комментарии к схеме помогают устранить двусмысленность в отношении значения данных и снизить вероятность неправильного толкования потребителями.
Комментарии должны быть:
  • К полям с примерами значений
  • К сообщению, с пояснением в следствие чего оно было сформировано
// The user has selected a movie
MovieClick {
  // Id of movie
  movieId: Long,
  
  // Does user watched preview?
  watchedPreview: Boolean
}
6. Делайте события одноцелевыми
Cобытия должны быть связаны с одним бизнес-действием, а не с общим событием, в ходе которого регистрируется большой объем данных. Если кажется, что вам нужно универсальное событие с различными параметрами типа, это обычно является сигнальным признаком того, что ваше проблемное пространство и ограниченный контекст недостаточно четко определены.

Как не надо:
TypeEnum: Book, Movie 
ActionEnum: Click, Bookmark 
ProductEngagement {
productId:  Long,
productType: TypeEnum,
actionType: ActionEnum,

  // Only applies to productType=Movie
  watchedPreview: {
    null, Boolean
  },
  // Only applies to productType=Book,actionType=Bookmark
  pageId: {
    null, Int
  }
}
Как надо:
MovieClick {
  movieId: Long,
  watchedPreview: Boolean
}

BookClick {
  bookId:Long
}

BookBookmark {
  bookId:Long,
  pageId: Int
}
7. Сведите к минимуму размер событий
События хорошо работают, когда они небольшие, четко определены, и их легко обрабатывать. В событии должна находиться только та информация, которая необходима. Не нужно в событие прописывать все данные сущности, если они никому не нужны. Это сделать размер события больше, а поддержку сложнее.
9. Не передавайте файлы, делайте на них ссылку
Некоторые обработчики событий создают очень большие выходные файлы, которые слишком велики, чтобы поместиться в одно сообщение топике. Пример такого события: в систему было загружено изображение. В таком случае нецелесообразно добавлять сам файл изображения в событие, достаточно передать только ссылку, по которой это изображение можно найти. В подобных сценариях вы можете использовать ссылку на фактические данные.
9. Избегайте использования событий в качестве семафоров или сигналов
Избегайте использования событий в качестве семафора или сигнала. Эти события просто указывают на то, что что-то произошло, но не являются единственным источником достоверности результатов.
Рассмотрим очень простой пример, когда система выдает событие, указывающее на завершение работы для произвольного задания. Хотя само событие указывает на выполнение работы, но фактический результат работы не включается в событие. Это означает, что для правильного использования этого события вы должны найти, где на самом деле находится завершенная работа.
К примеру, в сообщении есть информация только о том, что "задача завершилась", но нет информации о деталяъ. Потребителю придется обратиться в Producer и выяснять детали:
  • "просто завершилась или с ошибками, если да, то с какими"?
  • "в какое время?" (возможно потребитель на основе времени принимает решение о выплате бонуса)
10. Правильно работайте с перечислениями
Потребитель обязан учитывать значения Enum, которые он не распознает. Если пришел неизвестный токен, то варианта 2:
  • обрабатывать их, используя значение по умолчанию
  • cгенерировать фатальное исключение и останавливать обработку перативно разобраться, что нужно сделать. Сделать фикс.
  • Если вы выбрасываете фатальное исключение и останавливаете обработку - по сути это инцидент. Поэтому задача Producer не допускать таких ситуация и заблаговременно уведомить всех потребителей о появлении новых значений Enum.
Эта тема подробно рассматривается на курсе "DDD и Clean Architecture на практике"
Our Website is Almost Ready
Launch a targeted campaign.
Scale your infrastructure with our simple service.
Days
Hours
Minutes
Seconds
ИП Ветчинкин Кирилл Евгеньевич
ОГРН 321774600597342
ИНН 773376451099
Юр. адрес: Россия, Москва
Эл. адрес: info@microarch.ru