Модель предметной области определяет a.o. отношения между сущностями и мы определяем совокупные корни, чтобы обеспечить инкапсуляцию и границы транзакций. Хорошо известными отношениями являются отношения «один к одному» (объект или объект значения содержится в совокупном корне), отношения «один ко многим» (совокупный корень содержит коллекцию дочерних объектов) и отношения «многие ко многим». Последние являются сложными, потому что отношения «многие ко многим» между агрегатными корнями создают проблемы с границей транзакции. Таким образом, во многих случаях одно направление отношения «многие ко многим» рассматривается как более важное, и только это отношение моделируется как отношение «один ко многим».
Теперь сделайте еще один шаг вперед. Сети. Отношения многие ко многим между эквивалентными партнерами. Как вы можете смоделировать это, не нарушая границы транзакции в ваших совокупных корнях?
Посмотрите на этот широко применимый пример:
У меня есть сеть с узлами. Каждый узел имеет ограниченное количество портов. Один порт может быть подключен только к одному порту на другом узле. Я должен иметь возможность добавлять и удалять соединения между узлами, используя порты.
Интуитивным подходом к этому было бы моделирование узлов как совокупных корней, содержащих порты. Соединения кажутся объектами значений, и один порт может иметь одно соединение. Я мог бы реализовать метод Node.ConnectTo (nodeId, portId), который добавляет соединение (между портом X на узле A и портом Y на узле B) к совокупному корню, узлу A. Предпочтительно, я бы вызывал этот метод дважды, один раз на Узел A и один раз на узле B и заверните его в транзакцию. Однако это нарушило бы границу транзакции, поэтому я решил сохранить ее только на узле A.
Чтобы увидеть соединение на узле B на клиенте приложения, потребуется отдельная модель чтения. Но это не проблема, архитектура CQRS предоставляет нам эти возможности. Таким образом, добавление, удаление и просмотр подключений не является проблемой.
Проблема возникает, когда я хочу проверить, свободен ли порт, прежде чем добавить соединение к порту. Результатом соблюдения нашей границы транзакции является то, что (в модели записи) тот факт, что порт уже подключен, может быть неизвестен объединенному корню, но может храниться в любом другом объединенном корне.
Конечно, вы можете доверять проверке вашего клиента, продолжить и добавить соединение, если это нормально для узла, к которому вы добавляете его, и полагаться на процесс, выполняющий проверки согласованности для выполнения компенсирующих действий для недействительных соединений. Но это, кажется, имеет большое значение по сравнению с переносом транзакции вокруг двух вызовов ConnectTo ...
Это заставило меня подумать, что возможно, мои совокупные корни были выбраны неправильно . И я начал думать о Узлах и Сетях как об объединенных корнях, где Сеть - это коллекция Соединений. Хорошая вещь о сетевом агрегате состоит в том, что вы всегда можете проверить добавление или удаление соединений. За исключением случаев, когда новое соединение приведет к объединению двух существующих сетей ... И ваша совокупность может стать большой, возможно, в результате только в одной огромной сети. Также невозможно.
Итак, как вы думаете, это должно быть смоделировано? Видите ли вы решение, в котором корни агрегатов считаются границами транзакций, вы можете проверить сеть и не рискуете сохранить всю сеть как один агрегат? Или я запрашиваю все 3 CAP здесь и это просто невозможно?