Должна ли доменная модель поддерживать себя согласованной при использовании событий?

Я работаю над приложением, в котором мы пытаемся использовать модель предметной области. Идея состоит в том, чтобы сохранить бизнес-логику внутри объектов в доменной модели. Теперь многое делается для объектов, подписывающихся на связанные объекты, чтобы реагировать на изменения в них. Это делается через PropertyChanged и CollectionChanged. Эта работа хорошо, за исключением следующего:

Сложные действия: когда многие изменения должны обрабатываться как группа (а не отдельные изменения свойства / коллекции). Должен ли я / как я могу "строить" транзакции?

Постоянство: я использую NHibernate для постоянства, и это также использует установщики открытых свойств классов. Когда NHibernate попадает в свойство, выполняется много бизнес-логики (что кажется ненужным). Должен ли я использовать пользовательские сеттеры для NHibernate?

В целом кажется, что использование всей логики в модели предметной области делает модель предметной области довольно сложной. Есть идеи???

Вот примерная проблема (извините за дерьмовый инструмент, который я использую):

Вы можете увидеть, как Project my контейнер и объекты под ним реагируют друг на друга, подписавшись. Теперь изменения в сети выполняются через NetworkEditor, но этот редактор не знает о NetworkData. Эти данные могут даже иногда быть определены в другой сборке. Поток идет от пользователя->NetworkEditor->Network->NetworkData и всех других заинтересованных объектов. Это не похоже на масштаб.

1 ответ

Я боюсь, что комбинация событий 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.

Другие вопросы по тегам