Я боюсь, что комбинация событий DDD и PropertyChanged / CollactionChanged теперь может быть лучшей идеей. Проблема заключается в том, что если вы основываете свою логику на этих событиях, управлять сложностью крайне сложно, поскольку один PropertyChanged ведет к другому, а другой - и вскоре вы теряете контроль.
Другая причина, по которой события ProportyChanged и DDD не совсем подходят, заключается в том, что в DDD каждая бизнес-операция должна быть максимально явной. Имейте в виду, что DDD должен внести технические вещи в мир бизнеса, а не наоборот. И на основании PropertyChanged / CollectionChanged, кажется, не очень явно.
В DDD главная цель состоит в том, чтобы поддерживать согласованность внутри агрегата, другими словами, вам необходимо смоделировать агрегат таким образом, чтобы любая операция, которую вы вызываете агрегат, была действительной и согласованной (если операция, конечно, завершается успешно).
Если вы строите свою модель правильно, вам не нужно беспокоиться о «построении» транзакции - операция над агрегатом должна быть самой транзакцией.
Я не знаю, как выглядит ваша модель, но вы могли бы рассмотреть вопрос о перемещении обязанностей на один уровень «вверх» в совокупном дереве, вполне возможно, добавив в процесс дополнительные логические объекты вместо того, чтобы полагаться на события PropertyChanged.
Пример:
Предположим, у вас есть набор платежей со статусами, и при каждом изменении платежа вы хотите пересчитать общий остаток заказов клиентов. Вместо того, чтобы подписывать изменения на сбор платежей и вызывать метод у клиента при изменении сбора, вы можете сделать что-то вроде этого:
public class CustomerOrder
{
public List<Payment> Payments { get; }
public Balance BalanceForOrder { get; }
public void SetPaymentAsReceived(Guid paymentId)
{
Payments.First(p => p.PaymentId == paymentId).Status = PaymentStatus.Received;
RecalculateBalance();
}
}
Возможно, вы заметили, что мы пересчитываем баланс одного заказа, а не баланс всего клиента - и в большинстве случаев это нормально, так как клиент принадлежит к другому агрегату, и его баланс можно просто запросить при необходимости. Это как раз та часть, которая демонстрирует эту «согласованность только внутри агрегата» - на данный момент нам нет дела до других агрегатов, мы имеем дело только с одним заказом. Если это не подходит для требований, то домен смоделирован неправильно.
Я хочу сказать, что в DDD нет единой хорошей модели для каждого сценария - вы должны понимать, как работает бизнес, чтобы быть успешным.
Если вы посмотрите на приведенный выше пример, вы увидите, что нет необходимости «строить» транзакцию - вся транзакция находится в методе SetPaymentAsReceived
. В большинстве случаев одно действие пользователя должно приводить к одному конкретному методу на объекте без агрегата - этот метод явно относится к бизнес-операции (конечно, этот метод может вызывать другие методы).
Что касается событий в DDD, существует концепция событий домена, однако они не связаны напрямую с техническими событиями PropertyChanged / CollectionChanged. События домена указывают на бизнес-операции (транзакции), которые были выполнены агрегатом.
В целом кажется, что нажатие всей логики в модели предметной области делает
модель предметной области довольно сложная
Конечно, он делает то, что предполагается использовать для сценариев со сложной бизнес-логикой. Однако, если домен смоделирован правильно, эту сложность легко контролировать и контролировать, и это одно из преимуществ DDD.
Добавлено после предоставления примера:
Хорошо, а как насчет создания агрегатного корня с именем Project - когда вы создаете агрегатный корень из репозитория, вы можете заполнить его NetworkData, и операция может выглядеть следующим образом:
public class Project
{
protected List<Network> networks;
protected List<NetworkData> networkDatas;
public void Mutate(string someKindOfNetworkId, object someParam)
{
var network = networks.First(n => n.Id == someKindOfNetworkId);
var someResult = network.DoSomething(someParam);
networkDatas.Where(d => d.NetworkId == someKindOfNetworkId)
.ToList()
.ForEach(d => d.DoSomething(someResult, someParam));
}
}
NetworkEditor не будет работать в сети напрямую, а через Project с использованием NetworkId.