АналитикаСистемный анализСистемный аналитик

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

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

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

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

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

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

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

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

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

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

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

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

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

Интеграционное событие должно:

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

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

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

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

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

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

Сервис заказов публиковал наружу внутреннее событие изменения заказа. Служба доставки использовала поле внутреннего статуса, а после реорганизации модели статусы были разделены на несколько признаков. Изменение сломало обработку доставки, хотя бизнес-смысл заказа для неё не изменился.

Рассматривались два варианта. Первый — разрешить потребителям читать внутренние события: это почти не требует разработки, но сохраняет сильную связанность и переносит ответственность за внутреннюю модель на клиентов. Второй — создать отдельное событие «заказ готов к отгрузке» с бизнес-ориентированными полями: это требует адаптера и контракта, зато скрывает внутренние изменения.

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

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

  1. Всегда ли одно доменное событие должно превращаться в одно интеграционное событие?

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

  1. Можно ли считать интеграционное событие просто копией строки из таблицы?

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

  1. Как изменение интеграционного события влияет на совместимость потребителей?

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