Должны ли доменные объекты быть временно недействительными, и как это решение влияет на методы проверки? - PullRequest
7 голосов
/ 17 августа 2011

Я пишу проект VB.NET Winforms, основанный на MVVM (с использованием привязки Winforms). Мой инстинкт состоит в том, чтобы никогда не позволять объекту домена быть в недопустимом состоянии. Это требует, чтобы я делал проверки в конструкторе для новых сущностей и в каждом установщике для существующих сущностей:

Public Class Product


    Public Sub New(ProductID as Integer, Name as String)

        If ProductID > 0 AndAlso Name.Length > 5 Then

            _ProductID = ProductID
            _Name = Name

        Else
            Throw New InvalidProductException
        End If
    End Sub

    Private _ProductID as Integer
    Public Property ProductID as Integer

        Set(value as String)

            If value > 0 then
                _ProductID = value
            Else
                Throw New InvalidProductException
            End If

        End Set

    End Property

    'Same principle as above for Name setter.


End Class

Затем я наткнулся на аннотации данных, которые казались довольно изящными. Я заметил, что большинство людей, использующих аннотации данных, позволяют объекту домена временно становиться недействительным, а затем проверяют его в более поздний момент, вызывая Validate.ValidateObject. К этому моменту объект становится недействительным, и исходное состояние теряется, если у вас нет другого механизма для его отката.

Два вопроса:

1) Вы разрешаете доменным объектам временно становиться недействительными?

2) Какие методы вы используете для проверки сущности, основываясь на вашем ответе на вопрос № 1?

Ответы [ 3 ]

5 голосов
/ 17 августа 2011

Ваш инстинкт прав.В DDD объектам никогда нельзя разрешать входить в состояние, которое недопустимо с точки зрения домена.Даже временно.Объекты должны защищать свои внутренние инварианты, это довольно простой ООП.В противном случае это был бы не объект, а просто тупой контейнер данных.Часто люди смущаются структурами пользовательского интерфейса или чрезмерно обобщают термин «проверка».

Например, каждый продукт в вашей системе должен иметь SKU.Или у каждого Клиента должен быть номер социального страхования.Обеспечение соблюдения этих правил является прямой ответственностью организаций Продукта и Клиента.Старый добрый ArgumentNullException позволит клиентскому коду понять, что он нарушил некоторый инвариант.Применение этого правила не является обязанностью пользовательского интерфейса или какого-либо абстрактного «Валидатора».Если вы позволите применить это правило за пределами вашей организации, вы:

  • в конечном итоге получите недопустимое состояние, которое может привести к сбою или потребует от вас написания некоторого кода компенсации, чтобы избежать этого срыва

  • что более важно, вы не сможете рассуждать о Продукте, просто прочитав код продукта

Более того, бизнес-правила обычно более сложны, чемпоэтому было бы еще сложнее применить их за пределами сущности, не нарушая ее инкапсуляцию.Существует еще одна группа правил, которую легко реализовать с помощью DDD Value Object .В приведенном выше примере вы должны создать классы 'SKU' и 'SocialSecurityNumber'.Эти классы будут неизменными и будут применять все правила форматирования.Они также могут иметь статические методы, такие как:

SocialSecurityNumber.TryParse(String untrustedString, out SocialSecurityNumber)

или

SocialSecurityNumber.IsStringValid(String untrustedString)

Пользовательский интерфейс может использовать эти методы для проверки ввода пользователя.У пользовательского интерфейса нет причин «ломать» объекты даже временно.Если вы позволите этому случиться, вы получите Модель анемичного домена .К сожалению, многие примеры кода в Интернете способствуют такому подходу.Суть в том, что ваши правила валидации исходят от вашего домена и должны соблюдаться объектами домена.

5 голосов
/ 18 августа 2011

Во-первых, нет, доменные объекты никогда не должны существовать в недопустимом состоянии.

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

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

С точки зрения того, откуда берется объект модели представления: у меня есть отдельная служба запросов, которую вызывает пользовательский интерфейс.Это обеспечивает «плоские» (ненормализованные агрегированные данные) DTO, которые можно использовать для заполнения объектов модели представления или для работы самих моделей представления.Эта служба не знает о какой-либо модели домена и просто возвращает прогнозы из данных, по которым объекты домена проводят транзакции.

Я не могу рекомендовать достаточно чтения по CQRS , даже если вы толькорассмотреть некоторые аспекты этого.Мало того, что он помогает практический смысл DDD, но при правильной реализации он может действительно помочь с аспектом производительности при доставке данных на экраны вашего пользовательского интерфейса.

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

Нет, по моему мнению, доменные объекты никогда не должны быть недействительными, даже временно. Проблема в том, что если вы разрешите домену быть недействительным, как вы описали в своем вопросе, становится сложно вводить новые правила по мере роста сложности. Например, вы разрешаете объекту быть недействительным из-за какого-либо атрибута, предполагая, что он будет проверен позже. Но до того, как это произойдет, кто-то добавляет другое правило, которое изменяет свой результат в соответствии с тем же атрибутом - как узнать, правильно ли ведет себя правило? Вы не Поверьте мне, это происходит довольно часто в нетривиальных областях.

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

Техника, которую я склонен использовать, основывается на правилах валидации и результатах валидации. Короче говоря, большинство методов для сущностей реализовано следующим образом (C #, если вы не возражаете):

        public virtual void ChangeClaimEventDate(DateTimeOffset newDate)
        {
            var operationResult = ValidatorOf<Claim>
                .Validate()
                .WithCriticalRuleOf<EventDateFallsIntoPolicyCoverage>().WithParam(newDate)
                .WithCriticalRuleOf<EventDateFallsIntoInsuredCoverage>().WithParam(newDate)
                .WithCriticalRuleOf<PerformedServicesAreAvailableOnEventDate>().WithParam(newDate)
                .WithCriticalRuleOf<EventDateCannotBeChangedForBilledClaim>().WithParam(newDate)
                .ForOperation(this);

            if (operationResult.OperationFailed)
            {
                throw new InvalidBusinessOperation(operationResult);
            }

            SomeDate = newDate;
        }

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

OperationResultSet и ValidatorOf - довольно простые классы инфраструктуры, которые позволяют легко добавлять новые валидаторы с помощью свободного интерфейса. Валидаторы реализованы в виде классов, реализующих интерфейс IValidator, который позволяет реализовать довольно сложные правила проверки, а также проще тестировать их по отдельности.

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

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

...