Чтобы проиллюстрировать проблему, мы используем простой случай: есть два агрегата - Lamp
и Socket
. Следующее бизнес-правило должно выполняться всегда: ни Lamp
, ни Socket
нельзя подключать более одного раза одновременно. Чтобы предоставить соответствующую команду, мы задумали Connector
-службу с Connect(Lamp, Socket)
-методом для их подключения.
Поскольку мы хотим соответствовать правилу, согласно которому одна транзакция должна включать только один агрегат, не рекомендуется устанавливать связь для обоих агрегатов в Connect
-транзакции. Итак, нам нужен промежуточный агрегат, который символизирует сам Connection
. Таким образом, Connect
-транзакция просто создаст новый Connection
с указанными компонентами. К сожалению, здесь начинаются неприятности; как мы можем обеспечить согласованность состояния подключения? Может случиться так, что многие одновременные пользователи захотят подключить одни и те же компоненты в одно и то же время, поэтому наша «проверка согласованности» не отклонит запрос. Новые Connection
-агрегаты будут сохранены, потому что мы блокируем только агрегатный уровень. Система была бы непоследовательной, даже не зная об этом.
Но как мы должны установить границу наших агрегатов, чтобы обеспечить соблюдение нашего бизнес-правила? Мы могли бы представить Connections
-aggregate, который собирает все активные соединения (как Connection
-entity), тем самым активируя наш алгоритм блокировки, который должным образом отклонял бы повторяющиеся Connect
-запросы. С другой стороны, этот подход неэффективен и не масштабируется, а также противоречит интуиции с точки зрения языка предметной области.
Вы знаете, что мне не хватает?
Изменить: Чтобы подвести итог проблемы, представьте себе совокупность User
. Поскольку определение агрегата - это единица на основе транзакции, мы можем обеспечить соблюдение инвариантов, заблокировав эту единицу для каждой транзакции. Все в порядке. Но теперь возникает бизнес-правило: имя пользователя должно быть уникальным. Следовательно, мы должны каким-то образом согласовать наши совокупные границы с этим новым требованием. Если предположить, что одновременно регистрируются миллионы пользователей, это становится проблемой. Мы стараемся обеспечить этот инвариант в незаблокированном состоянии, поскольку несколько пользователей означают несколько агрегатов.
Согласно книге Эрика Эванса «Дизайн, управляемый предметной областью», следует применять конечную согласованность, как только несколько агрегатов задействованы в одной транзакции. Но так ли это на самом деле и имеет ли смысл?
Применение возможной согласованности здесь повлечет за собой регистрацию User
с последующей проверкой инварианта с именем пользователя. Если два User
s фактически установят одно и то же имя пользователя, система отменит вторую регистрацию и уведомит User
. Обдумывание этого сценария приводит меня в замешательство, потому что он нарушает весь процесс регистрации. Например, отправку электронного письма с подтверждением пришлось отложить и так далее.
Думаю, я вообще о чем-то забываю, но не знаю что. Мне кажется, что мне нужно что-то вроде инвариантов на Repository
-уровне.