Проверка уникальности при использовании CQRS и источников событий
Я пытаюсь реализовать свою собственную инфраструктуру CQRS с помощью Event Sourcing, чтобы лучше ее изучить. В качестве примера проекта я реализую движок блога, я знаю, что он может не подходить идеально, но я просто хочу поработать над чем-то реальным.
Проблема, к которой я пришел, - это проверка. Каждый пост имеет shortUrl
и shortUrl
должен быть уникальным, но где я должен поставить эту проверку в домене? Я знаю, что у меня будет эта проверка, прежде чем я даже отправлю команду, читая из моего хранилища для чтения, чтобы проверить, является ли она действительной при создании команды create post или update post.
Я могу думать о двух "решениях".
- Есть
Blog
агрегат, который отслеживает все настройки, связанные с блогом, а также ссылки на все сообщения. Но проблема с этим в моих глазах заключается в том, что мне приходится обрабатывать связи между агрегатами в этом сценарии, а также каждый раз, когда мне нужно проверить уникальностьshortUrl
Мне нужно прочитать все события из хранилища событий, чтобы создать все сообщения, и это кажется сложным. - Вторая альтернатива, которую я имею, - это когда событие запускается, и мой обработчик событий, который создает модель чтения, запускает двойное короткое событие URL, когда замечает, что у него будет два коротких URL, указывающих на разные сообщения. Допустимо ли иметь модель чтения для запуска событий при обнаружении ошибок?
Есть ли еще альтернативы. Обратите внимание, что я знаю, что мой домен может не подходить для cqrs и DDD, но я делаю это для обучения в небольшом домене.
3 ответа
Я бы пошел на доменную службу, которая просто отвечает за генерацию уникальных ShortURL. Вы можете использовать транзакционную БД для реализации этого поведения. Обычно этот сервис будет использоваться частью обработки команд агрегата BlogPost. Если есть дубликат ShortURL, вы можете запустить DuplicateUrlErrorEvent. Вы можете предварительно отловить это в пользовательском интерфейсе (но не на 100%), создав тонкую модель запроса с использованием того же источника данных, чтобы вы могли запросить, является ли сокращенный URL-адрес уникальным, перед отправкой сообщения (как описано в ответе @RyanR).).
Я прочитал различные ответы на этот и связанный с этим вопрос.
Решение сводится к правильности. Если вы можете прощать и соглашаться с несовершенным поведением для некоторой степени операции, ваша проблема будет гораздо проще решить, особенно при слабых гарантиях согласованности.
Однако, если вы хотите согласованности, вы должны использовать постоянный сервис, который имеет строгие гарантии согласованности.
Например, команда, которая создает короткий URL, проверит, что хранилище чтения уже не содержит такого короткого URL, и мы будем фиксировать наше событие, только если мы сможем зафиксировать изменения в нашем хранилище чтения.
Если мы сможем зафиксировать наши изменения в нашем хранилище чтения, мы не нарушили какое-либо ограничение уникальности (при условии, что ваше хранилище чтения применяет такое ограничение), и мы можем продолжить.
Однако, поскольку у нас есть две транзакции, которые необязательно находятся в одной базе данных, мы можем потерпеть неудачу после первой фиксации. Это нормально, потому что операция в целом также не будет выполнена. Хранилище чтения будет отражать несогласованное состояние в течение некоторого времени, но как только мы восстановим агрегат, хранилище чтения вернется в согласованное состояние.
В качестве процедуры технического обслуживания мы могли бы периодически ремонтировать агрегаты, которые были подвержены потенциальным ошибкам. И вы можете сделать это, введя флаг ошибки, который сбрасывается только в случае успешной фиксации обеих транзакций.
Был пример, в котором банк позволил бы пользователю перезаписать свою учетную запись, потому что у него есть доплаты, чтобы это компенсировать. Это вызывает вопросы, потому что кажется, что решение такой проблемы кажется неряшливым, даже ленивым. Некоторые называют это умным. Я не знаю, что думать. У банка, вероятно, достаточно денег, чтобы покрыть его, поэтому они могли бы его игнорировать, но мир так не работает. Во всяком случае, я отвлекся.
С точки зрения правильности, наше хранилище чтения имеет строгую гарантию согласованности, и мы бы написали нашу проекцию таким образом, чтобы мы не могли зафиксировать транзакцию в хранилище чтения, если баланс будет положен в минус. Таким образом, самое худшее, что может случиться, это то, что из хранилища чтения вычитается заряд, но операция никогда не выполнялась полностью в хранилище событий. Пользователь будет видеть, что деньги отсутствуют на его счете, пока процедура обслуживания не заметит флаг ошибки и не вылечит учетную запись. Я думаю, что это рабочий компромисс.
Это зависит от того, что хочет "бизнес". Если вы хотите, чтобы клиент (создатель команд) отвечал за выбор короткого URL-адреса, у него должно быть хранилище для чтения, из которого он проверяет его уникальность. Когда пользователь вводит короткий URL-адрес, представление должно проверить, что короткий URL-адрес является уникальным, и представить ошибку проверки, если это не так. Каждый раз, когда сообщение сохраняется, событие публикует обновленную информацию (включая короткий URL), которая синхронизирует хранилище для чтения.