Новый идентификатор объекта в домене

Я создаю приложение с моделью домена с использованием концепций CQRS и доменных событий (но без источника событий, просто старого SQL). Не было никаких проблем с событиями SomethingChanged. Затем я застрял в реализации событий SomethingCreated.

Когда я создаю какой-либо объект, который сопоставляется с таблицей с идентификационным первичным ключом, тогда я не знаю Id до тех пор, пока объект не будет сохранен. Сущность - это непротиворечивость, поэтому при публикации события изнутри объекта Id просто неизвестен - он волшебным образом задан после вызова context.SaveChanges(). Итак, как/где/когда я могу поместить Id в данные события?

Я думал:

  • Включает ссылку на объект в событии. Это будет работать внутри домена, но не обязательно в распределенной среде с несколькими автономными системами, сообщающими событиями/сообщениями.
  • Переопределить SaveChanges(), чтобы как-то обновлять события, помещенные в очередь для публикации. Но события должны быть неизменными, поэтому это кажется очень грязным.
  • Избавление от полей идентификации и использование GUID, сгенерированных в конструкторе сущности. Это может быть самым простым, но может поразить производительность и сделать другие вещи сложнее, например отладки или запросы (where id = 'B85E62C3-DC56-40C0-852A-49F759AC68FB', no MIN, MAX и т.д.). Это то, что я вижу во многих примерах приложений.
  • Гибридный подход - оставить личность и использовать ее главным образом для внешних ключей и более быстрого объединения, но использовать GUID как уникальный идентификатор, с помощью которого я вывожу сущности из репозитория в приложении.

Ответ 1

Лично мне нравятся GUID для уникальных идентификаторов, особенно в многопользовательских распределенных средах, где числовые идентификаторы вызывают проблемы. Таким образом, я никогда не использую базы данных с идентификационными столбцами/свойствами, и эта проблема исчезает.

В дополнение к этому, поскольку вы следите за CQRS, у вас, несомненно, есть CreateSomethingCommand и соответствующий CreateSomethingCommandHandler, который на самом деле выполняет шаги, необходимые для создания нового экземпляра, и сохраняет новый объект с помощью репозитория (через context.SaveChanges). Я буду поднимать событие SomethingCreated здесь, а не в самом объекте домена.

Во-первых, это решает вашу проблему, потому что обработчик команд может дождаться завершения операции с базой данных, вытащить значение идентификатора, обновить объект и передать идентификатор в событии. Но, что еще более важно, он также обращается к сложному вопросу о том, когда именно объект "создан"?

Поднятие события домена в конструкторе - это плохая практика, поскольку конструкторы должны быть тонкими и просто выполнять инициализацию. Кроме того, в вашей модели объект не создается до тех пор, пока он не присвоит идентификатор. Это означает, что после выполнения конструктора требуются дополнительные шаги инициализации. Если у вас более одного шага, вы выполняете порядок выполнения (другой анти-шаблон) или ставите чек в каждом, чтобы узнать, когда все это сделано (ох, вонючий)? Надеюсь, вы увидите, как это может быстро вырваться из-под контроля.

Итак, моя рекомендация - поднять событие из обработчика команд. (ПРИМЕЧАНИЕ. Даже если вы переключитесь на идентификаторы GUID, я буду следовать этому подходу, потому что вы никогда не должны поднимать события из конструкторов.)