Практическая ситуация: сервис аналитики строит отчёт по данным заказов и клиентов, но один источник иногда недоступен. Как организовать интеграцию, чтобы отчёт не зависел от синхронного fan-out, принимая временную задержку данных?
report:
sources: [orders, customers]
mode: synchronous-join
on_source_timeout: fail
Нужно заменить синхронное объединение данных при каждом запросе на локальную материализованную проекцию, которую сервис аналитики обновляет по событиям от сервисов-владельцев данных. Тогда чтение отчёта не требует доступности всех источников в данный момент, но данные могут быть временно неактуальными.
В монолитах отчёты обычно строились SQL-запросами по общей базе: соединения таблиц и транзакции обеспечивали единый взгляд на данные. При разделении системы на сервисы прямые межсервисные JOIN-операции исчезают, а синхронная агрегация начинает связывать доступность и задержки всех участников.
CQRS и локальные read-модели применяют, когда модель записи и модель чтения имеют разные требования. Сервис-владелец сохраняет бизнес-данные, а потребитель строит оптимизированное представление для своих запросов.
При синхронном fan-out запрос к отчёту вызывает несколько сервисов. Недоступность одного источника, его высокая задержка или несовместимое изменение API может сделать недоступным весь отчёт.
Попытка решить проблему кэшированием ответов недостаточна как общая архитектура: кэш может устареть, потеряться или не содержать полный набор данных. Кроме того, каждый запрос всё равно зависит от корректной координации нескольких удалённых вызовов.
Каждый сервис публикует доменные события после успешного изменения своих данных. Сервис аналитики подписывается на нужные события и в своей базе поддерживает проекцию, например customer_order_summary.
Чтение отчёта выполняется только из analytics_read_model. Если сервис заказов временно недоступен, уже обработанные данные остаются доступными, а новые события будут обработаны после восстановления источника или повторной доставки.
Проекция должна иметь понятную политику согласованности. Обычно это eventual consistency: между изменением в источнике и появлением результата в отчёте существует задержка. Интерфейс должен явно учитывать такой статус — например, показывать время последнего обновления или неполноту данных.
Обработчик событий должен быть идемпотентным, поскольку доставка может быть повторной. Для этого сохраняют идентификатор обработанного события, используют версию сущности или применяют операцию, безопасную при повторном выполнении.
Нужны также повторная обработка и восстановление проекции. События должны быть доступны из надёжного журнала либо должна существовать процедура периодической сверки с источником; иначе потерянное событие навсегда оставит read-модель неверной.
Главный компромисс — свежесть обменивается на автономность чтения. Такой подход не подходит для запросов, где требуется строго актуальное значение в момент ответа, например для проверки доступного остатка перед списанием. В этих случаях критическая проверка должна выполняться у сервиса-владельца, а аналитическая проекция не может считаться источником истины.
Отчёт руководителя показывает заказы, клиентов и региональные показатели. Сначала команда реализовала запрос, который синхронно обращался к сервисам заказов и клиентов. При деградации сервиса клиентов отчёт начинал отвечать с тайм-аутом, хотя данные по большинству заказов не менялись.
Рассматривались два варианта. API-композиция была проще и давала более свежие данные, но сохраняла зависимость от доступности всех сервисов и увеличивала задержку. Кэширование ответов уменьшало нагрузку, однако усложняло инвалидирование и не гарантировало полноту набора данных.
Выбрали локальную проекцию аналитики, обновляемую событиями OrderCreated, OrderCancelled и CustomerChanged. После этого отчёт продолжал работать во время кратковременных отказов источников, а команда добавила отображение времени последней синхронизации и метрику отставания проекции.
Нет. Она является производным представлением и может отставать, содержать ещё не обработанные события или временно быть неполной. Источником истины остаётся сервис, владеющий соответствующей бизнес-сущностью; проекция подходит для чтения и аналитики, но не для окончательной авторизации списания или подтверждения лимита.
Проекция останется в неправильном состоянии, даже если все последующие события будут обработаны успешно. Поэтому нужны гарантии сохранения и повторного чтения событий, мониторинг отставания, обработка ошибок и периодическая сверка с источником. Простая повторная попытка не помогает, если сообщение уже безвозвратно удалено.
Нужно измерять задержку между временем изменения в источнике и временем применения события в проекции. Если допустимая задержка превышена, система может пометить отчёт как устаревший, временно отказаться от ответа или предложить синхронную проверку у владельца данных — в зависимости от требований бизнеса. Важно не скрывать eventual consistency под видом гарантированно актуального результата.