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