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