Как применить ограничение, например, любое поле (или конкретное поле) не должно меняться, если сущность находится в каком-то состоянии? - PullRequest
3 голосов
/ 20 августа 2011

Я пытаюсь использовать DDD в моем текущем проекте (c #, mvc, nhibernate, castle), и я думаю о лучшем способе проверить ограничение, которое говорит, что любое поле (или конкретное поле) не должно изменяться, если объектв каком-то состоянии, т.е.Счет-фактура, который зарезервирован (состояние = забронировано), не должен иметь измененного поля суммы.В слое сервиса я получаю некоторый объект DTO (из графического интерфейса или веб-сервиса и т. Д.), Который мне нужно сопоставить с объектом домена.После завершения сопоставления я хочу проверить свой объект - в частности, я хочу проверить конкретное ограничение в моем вопросе.

В настоящее время я думаю о:

  • отслеживании изменений на объектеуровень, т. е. на каждом установщике добавить поле в коллекцию измененных полей и переключить NHibernate для использования стратегии доступа к полю.Вариация этого шаблона будет состоять в том, чтобы генерировать исключение в установщике, если изменение значения не разрешено
  • создание копии объекта перед отображением и сравнение исходных и сопоставленных значений
  • с использованием nhibernate и получение этой информациииз сеанса nhibernate - однако правило не будет применено на уровне сущности (imho оно нарушает DDD)

Что вы думаете об этом?Вы знаете какие-нибудь хорошие шаблоны для этого?Или я что-то упустил, и мне нужно изменить свое представление об этом ограничении?

Заранее спасибо за помощь.

Ответы [ 3 ]

3 голосов
/ 20 августа 2011

Доменные объекты в DDD являются «самопроверяющимися».Другими словами, клиентский код не может нарушать правила домена, поскольку объекты применяют свои внутренние инварианты.Например:

public class Invoice {
    private Money _amount;
    private InvoiceState _state;

    public void ChangeAmount(Money newAmount) {
        if(_state == State.Booked) {
            throw new InvalidOperationException(
                      "Unable to change amount for booked invoice.");
        }
        _amount = newAmount;
    }

    // Methods like this can be used by external code (UI) to check business
    // rules upfront, to avoid InvalidOperationException.
    public Boolean CanChangeAmount() {
        if(_state == State.Booked) {
            return false;
        }
        return true;
    }
}

Еще один пример из Образец DDD :

  public HandlingEvent(final Cargo cargo,
                       final Date completionTime,
                       final Date registrationTime,
                       final Type type,
                       final Location location,
                       final Voyage voyage) {

    ...

    if (type.prohibitsVoyage()) {
      throw new IllegalArgumentException(
                       "Voyage is not allowed with event type " + type);
    }

Никогда не позволяйте своей структуре пользовательского интерфейса обрабатывать объект домена как контейнер данных.К сожалению, это поощряется множеством примеров в Интернете и акцентом C # на геттеры и сеттеры.Если вы измените состояние объекта без применения бизнес-правил, вы в конечном итоге получите «поврежденные» объекты.Это особенно верно для NHibernate, потому что его Сессия «запоминает» все объекты и с радостью сбросит их в базу данных при следующем коммите или сбросе.Но это всего лишь техническая составляющая, главная причина в том, что вам нужно иметь возможность рассуждать о бизнес-правилах, связанных со счетами, просто взглянув на класс Invoice.Также обратите внимание, что код должен быть основан на Ubiquitous Language .Вы должны видеть такие слова, как «Счет-фактура», «Забронировано», «Сумма», вместо общих «Поле», «Свойство», «Валидатор».

ОБНОВЛЕНИЕ: empi, спасибо за решение вашей проблемы.Возможно, вы захотите открыть новый вопрос.Это цитата с моим акцентом

Как я сказал в одном из моих комментариев - этот вопрос является частью более серьезной проблемы, с которой я сталкиваюсь.Я ищу стандартный способ определения логики и ограничений домена только в домене, а затем перевести его на графический интерфейс и другие уровни.Я не вижу какой-либо общей модели для следующего очень распространенного требования: домен заявляет, что поле не может быть отредактировано, и это правило автоматически преобразуется в графическую логику , которая отключает поле и делает его доступным только для чтения.Я ищу образец решения в стеке MVC.Мне кажется, что я заново изобретаю колесо, и большинство разработчиков просто сдаются и дублируют логику в графическом интерфейсе

Я думаю, что вы ищете способ указать все в домене, а затем «сгенерировать» пользовательский интерфейс.Что-то вроде Голые объекты для MVC ?Я никогда не использовал этот подход, но сомневаюсь, что сгенерированный пользовательский интерфейс когда-нибудь выиграет конкурс красоты или удобства использования.На мой взгляд, в пользовательском интерфейсе всегда будет некоторая «перестройка» бизнес-логики.Некоторые доменные инварианты слишком сложны, включают в себя несколько полей, требуют репозитория и, возможно, даже внешних сервисов.Я не уверен, что можно автоматически генерировать высококачественный пользовательский интерфейс.Я думаю, что попытки сделать это могут начать сгибать вашу модель для соответствия инфраструктуре пользовательского интерфейса.

1 голос
/ 21 августа 2011

При проектировании своих доменных объектов я стараюсь не думать о них просто как о наборе данных, а как об объекте, с которым можно работать. Вместо предоставления прямого доступа к данным (даже через методы получения и установки) предоставьте методы, которые соответствуют действиям, которые можно выполнить с объектом. Иногда действие изменит несколько полей данных. Иногда он может изменить только один и функционально не отличаться от установщика, но называется так, что представляет действие, а не просто изменение данных.

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

Наряду с этим подходом я также использовал разные сущности для представления одних и тех же данных в разных точках жизненного цикла. Пока счет активен, он должен быть объектом, на который можно воздействовать. Однако, как только он достигает конечного состояния, он используется только для просмотра и составления отчетов, и ни одно из данных не изменяется. Используя разные объекты (например, ActiveInvoice и CompletedInvoice), становится ясно в приложении, где оно используется как часть процесса и где оно используется только для просмотра. Это также облегчает работу с архивными данными, которые могут поступать из другой таблицы или только для чтения.

Если объект имеет только два состояния, представляющих изменчивое и неизменяемое состояние без особой логики для разрешения различных методов для различных состояний, вы можете использовать шаблон Эрика Липперта 'Popsicle Immutability' . Он допускает более прямую модификацию объекта, чем, но затем обеспечивает его неизменность после замораживания.

0 голосов
/ 20 августа 2011

Хотя я не могу найти хорошую ссылку (могу поклясться, что слышал это от Мартина Фаулера несколько лет назад, но поиск по его сайту закончился), я привык слышать эту концепцию, называемую «замораживание» "или" заморозить ". Обычно используется в сочетании с бухгалтерскими операциями на двух ногах.

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

Как ни странно, Microsoft реализовала это в совершенно ином контексте с WPF. Они используют «freezable» прежде всего, чтобы указать, что уведомления об изменениях больше не нужны. Если вы на самом деле используете WPF, вы можете рассмотреть класс Freezable .

В противном случае, если вам нужен действительно универсальный шаблон, я настоятельно рекомендую вам прочесть Учебное пособие по динамическому прокси-серверу Kozmic . Хотя это в основном оправдание для демонстрации возможностей Castle Proxy, концепция «freezable» - это точно , которую он выберет для реализации, и он показывает способ сделать это с помощью универсальной библиотеки многократного использования без необходимости писать много дополнительного кода после факта.

Несмотря на то, что для обработки всех изломов достаточно много кода, основная идея состоит в том, чтобы просто написать перехватчик и затем создать прокси с ним:

internal class FreezableInterceptor : IInterceptor, IFreezable
{
    private bool _isFrozen;

    public void Freeze()
    {
        _isFrozen = true;
    }

    public bool IsFrozen
    {
        get { return _isFrozen; }
    }

    public void Intercept(IInvocation invocation)
    {
        if (_isFrozen && invocation.Method.Name.StartsWith("set_",
            StringComparison.OrdinalIgnoreCase))
        {
            throw new ObjectFrozenException();
        }

        invocation.Proceed();
    }
}

public static TFreezable MakeFreezable<TFreezable>() 
  where TFreezable : class, new()
{
    return _generator.CreateClassProxy<TFreezable>(new FreezableInterceptor());
}

Обратите внимание, что выше не код качества производства , это просто вступление. Вы должны прочитать больше связанных сайтов для получения дополнительной информации.

Насколько я знаю, проксирование классов / интерфейсов - действительно единственный способ сделать это независимым от домена способом. В противном случае вам придется заново реализовать логику freezable для каждого класса freezable, то есть поместить множество операторов if-then в установщики свойств и выдать FrozenException, если статус установлен.

...