.net 是否应该允许域对象暂时无效,以及该决定如何影响验证技术?

.net 是否应该允许域对象暂时无效,以及该决定如何影响验证技术?,.net,validation,domain-driven-design,.net,Validation,Domain Driven Design,我正在基于MVVM(使用Winforms绑定)编写一个VB.NET Winforms项目。我的直觉是永远不允许域实体处于无效状态。这要求我在构造函数中对新实体进行验证检查,并在每个setter中对现有实体进行验证检查: Public Class Product Public Sub New(ProductID as Integer, Name as String) If ProductID > 0 AndAlso Name.Length > 5 Then

我正在基于MVVM(使用Winforms绑定)编写一个VB.NET Winforms项目。我的直觉是永远不允许域实体处于无效状态。这要求我在构造函数中对新实体进行验证检查,并在每个setter中对现有实体进行验证检查:

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的回答,您使用什么技术来验证实体?

不,在我看来,域实体永远不应该被允许无效,即使是暂时的。问题是,如果您允许域无效,就像您在问题中描述的那样,随着复杂性的增加,引入新规则变得越来越困难。例如,您允许实体由于某些属性而无效,假设稍后将对其进行验证。但在此之前,有人添加了另一条规则,该规则根据同一属性改变其结果-您如何知道该规则的行为是否正确?你没有。相信我,它经常发生在非平凡领域

不允许状态无效的另一个原因是,在某些情况下,它可能会给ORM带来问题-我个人看到过一个问题,涉及NHibernate缓存和子实体,这些子实体无效,但不知何故仍保留在缓存中,但我记不起任何具体细节

我倾向于使用基于验证规则和验证结果的技术。简而言之,实体上的大多数方法都是通过以下方式实现的(如果您不介意的话,可以使用C#):

公共虚拟无效更改claimeventdate(DateTimeOffset newDate)
{
var operationResult=ValidatorOf
.Validate()
.WithCriticalRuleOf().WithParam(新日期)
.WithCriticalRuleOf().WithParam(新日期)
.WithCriticalRuleOf().WithParam(新日期)
.WithCriticalRuleOf().WithParam(新日期)
.用于操作(本);
if(操作结果。操作失败)
{
抛出新的无效业务操作(operationResult);
}
SomeDate=newDate;
}
这段代码最重要的一点是,即使在更改实体之前,也会检查某些验证规则。这个例子展示了结果集的使用,因为即使验证成功,我也经常需要提供有关验证的信息(换句话说,我有失败的验证,必须向用户显示有关验证的信息;但是域实体仍然有效)

OperationResultSet
ValidatorOf
是非常简单的基础结构类,允许使用fluent接口轻松添加新的验证器。验证器实现为实现
ivalidater
接口的类,允许实现非常复杂的验证规则,并且更易于测试他们也各自独立

我的观点是,验证应该在对域实体进行更改之前执行——使用正确的约定和一些基础设施,它甚至可以简化代码结构


编辑说明:由于对这个答案有一些批评的声音,我决定将示例代码更改为引发异常而不是返回结果的代码。尽管我仍然认为这是我的场景的一种方式,但我同意,如果不指定完整的上下文,这可能会产生误导-异常应该是第一个选项和其他因素应该存在以选择备选方案。

你的直觉是正确的。在DDD中,永远不应该允许对象进入从域角度看无效的状态。即使是暂时的。对象应该保护其内部不变量,这是非常基本的OOP。否则它将不是一个对象,而只是一个哑数据通常,人们会被UI框架弄糊涂,或者过度概括“验证”一词

例如,您系统中的每个产品都应该有SKU。或者每个客户都应该有社会保险号。强制执行这些规则是产品和客户实体的直接责任。好的异常会让客户代码意识到它打破了一些不变。强制执行此规则不是UI的责任,也不是bstract“Validator”。如果允许在实体外部强制执行此规则,您将:

  • 最终以无效状态结束,这可能导致崩溃,或者要求您编写一些补偿代码以避免崩溃

  • 更重要的是,您不能仅仅通过阅读产品代码来对产品进行推理

此外,业务规则通常比这更复杂,因此在实体之外强制执行它们而不破坏其封装将更加困难。 还有一组规则可以通过使用DDD轻松实施。在上面的示例中,您将创建类“SKU”和“SocialSecurityNumber”。这些类将是不可变的,并将实施所有格式规则。它们还可以具有静态方法,如:

SocialSecurityNumber.TryParse(String untrustedString, out SocialSecurityNumber)

UI可以使用这些方法来验证用户输入。UI没有理由临时“破坏”对象。如果你让这种情况发生,你最终会得到。不幸的是,internet上的许多示例代码都促进了这种方法。底线是,你的验证规则来自你的域,应该由域o强制执行对象。

首先,
SocialSecurityNumber.TryParse(String untrustedString, out SocialSecurityNumber)
SocialSecurityNumber.IsStringValid(String untrustedString)