Потребитель иногда получает событие «ЗаказОплачен» раньше «ЗаказСоздан». Какой контракт события позволит тестом выявить нарушение порядка?
Контракт должен явно зафиксировать порядок событий в пределах одного заказа: идентификатор агрегата, монотонный номер последовательности и допустимую реакцию потребителя на пропуск номера. Тест передаёт события в неправильном порядке и проверяет, что потребитель не применяет «ЗаказОплачен» как обычное корректное состояние.
Одного времени создания события недостаточно: часы разных узлов могут расходиться, а временная метка не выражает причинную связь. Глобальный порядок всех событий обычно не требуется; важен порядок событий, относящихся к одному заказу.
Асинхронные интеграции появились, чтобы отделить отправителя события от потребителя по времени и снизить связанность систем. Брокер сообщений может доставлять данные через несколько узлов, повторять доставку после сбоя или обрабатывать разные потоки параллельно.
Поэтому факт публикации двух событий подряд не всегда означает, что потребитель получит их в том же порядке. Контракт порядка возник как способ явно описать это поведение вместо неявного предположения о последовательной доставке.
Если «ЗаказОплачен» обработан до «ЗаказСоздан», потребитель может создать неполное состояние, отклонить валидную оплату или сформировать противоречивые данные. Ошибка особенно опасна, когда тесты проверяют каждое событие отдельно и не моделируют последовательность доставки.
Нужно определить область гарантии: порядок всех событий системы, всех событий типа заказа или только событий одного конкретного заказа. Попытка требовать глобальный порядок обычно снижает параллелизм и усложняет масштабирование без пользы для бизнес-логики.
Контракт следует формулировать так: для каждого заказа события имеют общий идентификатор заказа и возрастающий номер последовательности. Потребитель применяет событие с номером n только после успешной обработки необходимых предыдущих номеров; при пропуске он откладывает событие, повторно запрашивает пропущенное или переводит ситуацию в контролируемую ошибку.
Тест должен проверить как минимум три случая: корректную последовательность, перестановку событий одного заказа и независимую обработку событий разных заказов. Проверяется не только порядок вызова обработчика, но и итоговое состояние потребителя, поскольку ошибка может проявиться лишь после последующего события.
Гарантию порядка обычно ограничивают ключом агрегата: события одного заказа направляются в один упорядоченный поток или логический раздел. События разных заказов могут обрабатываться параллельно. Это сохраняет масштабирование, но не защищает от ошибок самого потребителя, если он использует общий изменяемый ресурс без синхронизации.
Номер последовательности лучше считать частью схемы события и проверять его как контрактное поле. Временную метку можно сохранять для диагностики, но не следует использовать её как единственный механизм определения порядка.
Есть важное ограничение: порядок доставки не равен порядку завершения обработки. Даже если брокер доставляет события последовательно, параллельные обработчики могут завершить работу в обратном порядке. Поэтому контракт должен описывать также правила применения событий и поведение при повторной доставке.
В условной системе оплаты события одного заказа иногда обрабатывались разными экземплярами потребителя. Рассматривались три варианта: потребовать глобальную FIFO-доставку для всех заказов, включить в событие временную метку или маршрутизировать события по идентификатору заказа и добавить номер последовательности.
Глобальная FIFO-доставка упростила бы рассуждение, но ограничила бы параллелизм всей системы. Временная метка не гарантировала бы причинный порядок из-за рассинхронизации часов и не показывала бы, какое событие пропущено.
Выбран был третий вариант: события одного заказа направлялись в один поток, а потребитель проверял последовательность и откладывал событие при пропуске номера. Интеграционный тест с переставленной доставкой выявлял нарушение контракта, а события разных заказов продолжали обрабатываться параллельно.
Нет. Это обычно помогает сохранить порядок доставки внутри выбранного потока, но не заменяет контракт потребителя. Нужно подтвердить, что отправитель использует стабильный ключ маршрутизации, что события не публикуются в разные потоки и что потребитель не обрабатывает один поток конкурентно с нарушением последовательности.
Кроме того, порядок может нарушиться до публикации в брокер или после извлечения сообщения. Поэтому тест должен проверять наблюдаемое поведение интеграции, а не только настройку маршрутизации.
Он не должен безусловно применять такое событие. Возможные стратегии — временно отложить сообщение, запросить недостающие данные, повторить обработку после появления предыдущего события или отправить ситуацию в очередь ошибок.
Выбор зависит от требований к задержке и доступности. Немедленное отклонение проще, но может приводить к потере прогресса; бесконечное ожидание создаёт зависшие сообщения. В контракте следует зафиксировать допустимое поведение, тайм-аут и способ восстановления.
Да, но одного номера недостаточно. Потребитель должен хранить состояние последнего применённого номера и уметь безопасно повторно обработать уже известное событие, то есть обработка должна быть идемпотентной или защищённой уникальным идентификатором события.
Повтор события с уже применённым номером обычно пропускается либо распознаётся как повтор. Событие с будущим номером означает возможный пропуск и требует отдельной стратегии. Таким образом, порядок и повторная доставка — разные свойства, которые нужно проверять раздельно.