Domain driven design DDD不变量业务规则和验证

Domain driven design DDD不变量业务规则和验证,domain-driven-design,dry,single-responsibility-principle,invariants,Domain Driven Design,Dry,Single Responsibility Principle,Invariants,我正在寻找关于在何处添加域实体的验证规则以及实现的最佳实践的建议。我做了搜索,没有找到我要找的东西,或者我错过了 我想知道验证属性不为null、在某个范围内或长度等的推荐方法是什么。。。我已经看到了几种使用IsValid()的方法,以及关于在构造函数中强制使实体永远处于无效状态的其他讨论,或者使用预处理和后处理,以及其他使用FluentValidation api的方法,不变量如何影响DRY和SRP 有人能给我一个很好的例子,当使用应用程序服务、有界上下文、域服务、聚合根、实体分层时,在哪里放置

我正在寻找关于在何处添加域实体的验证规则以及实现的最佳实践的建议。我做了搜索,没有找到我要找的东西,或者我错过了

我想知道验证属性不为null、在某个范围内或长度等的推荐方法是什么。。。我已经看到了几种使用IsValid()的方法,以及关于在构造函数中强制使实体永远处于无效状态的其他讨论,或者使用预处理和后处理,以及其他使用FluentValidation api的方法,不变量如何影响DRY和SRP

有人能给我一个很好的例子,当使用应用程序服务、有界上下文、域服务、聚合根、实体分层时,在哪里放置这些检查。这将走向何方?最好的方法是什么


谢谢。

< P>当建模你的域实体时,最好考虑真实世界的含义。假设您正在处理一个
员工
实体

员工需要一个名字

我们知道,在现实世界中,员工必须始终有自己的名字。员工不可能没有名字。换句话说,如果不指定员工的姓名,就不能“构造”员工。所以,使用参数化构造函数!我们也知道员工的名字不能更改,所以我们甚至通过创建一个私有setter来防止这种情况发生。使用.NET类型系统验证员工是一种非常有效的验证形式

public string Name { get; private set; }

public Employee(string name)
{
    Name = name;
}
有效名称有一些规则

现在它开始变得有趣起来。名字有一定的规则。让我们简单地假设一个有效的名称不是null或空的。在上面的代码示例中,未根据验证以下业务规则。此时,我们仍然可以创建无效员工!让我们通过修改setter来防止这种情况发生:

public string Name
{
    get
    {
        return name;
    }
    private set
    {
        if (String.IsNullOrWhiteSpace(value))
        {
            throw new ArgumentOutOfRangeException("value", "Employee name cannot be an empty value");
        }

        name = value;
    }
}
我个人更喜欢在私有setter中使用这种逻辑,而不是在构造函数中。setter并非完全不可见。实体本身仍然可以更改它,我们需要确保有效性。另外,总是抛出异常

如何公开某种形式的
IsValid()
方法?

以上述
员工
实体为例。
IsValid()方法在哪里以及如何工作

您是否允许创建一个无效的员工,然后期望开发人员使用
IsValid()
check检查其有效性?这是一个薄弱的设计——在你意识到之前,无名员工将在你的系统中四处游荡,造成严重破坏

但您可能想公开名称验证逻辑?

我们不想捕捉控制流的异常。例外情况是灾难性系统故障。我们也不希望在代码库中重复这些验证规则。因此,也许公开这种验证逻辑不是一个坏主意(但仍然不是最好的!)

您可以提供一个静态的
IsValidName(string)
方法:

public static bool IsValidName(string name)
{
    return (String.IsNullOrWhiteSpace(value))
}
我们的财产现在会发生一些变化:

public string Name
{
    get
    {
        return name;
    }
    private set
    {
        if (!Employee.IsValidName(value))
        {
            throw new ArgumentOutOfRangeException("value", "Employee name cannot be an empty value");
        }

        name = value;
    }
}
但是这个设计有点可疑…

我们现在开始为实体的各个属性生成验证方法。如果一个属性附加了各种各样的规则和行为,这可能是我们可以为它创建一个值对象的标志

public PersonName : IEquatable<PersonName>
{
    public string Name
    {
        get
        {
            return name;
        }
        private set
        {
            if (!PersonName.IsValid(value))
            {
                throw new ArgumentOutOfRangeException("value", "Person name cannot be an empty value");
            }

            name = value;
        }
    }

    private PersonName(string name)
    {
        Name = name;
    }

    public static PersonName From(string name)
    {
        return new PersonName(name);
    }

    public static bool IsValid(string name)
    {
        return !String.IsNullOrWhiteSpace(value);
    }

    // Don't forget to override .Equals
}
我们的客户端代码现在可以如下所示:

if(PersonName.IsValid(name))
{
    employee = new Employee(PersonName.From(name));
}
else
{
    // Send a validation message to the user or something
}
那么我们在这里做了什么?


我们已确保我们的领域模型始终保持一致。非常重要。无法创建无效的实体。此外,我们还使用了价值对象来提供进一步的“丰富性”
PersonName
为客户端代码提供了更多的控制和权力,还简化了
Employee

我构建了一个可以帮助您的库


举个例子:它们不应该影响SRP。您可以在设置域对象属性之前进行验证,例如,如果您使用CQR,则可以在命令中进行验证,以便确保它们有效。。。您可以对域对象进行注释,并使用该数据进行验证。+1这可能是如何封装我所读过的大多数验证需求的最佳示例。+1很高兴看到真正了解DDD的人给出答案,这在StackOverflow上是罕见的@AdrianThompsonPhillips如果你喜欢这些想法,还可以看看@MattDavey cheers,我是一个强大的防御性编程和价值对象粉丝。这两种静态方法的添加以及它们的使用方式是我最喜欢的。它结合了设置前的测试,但有引发异常的回退。完美。这是一个非常简单的例子。验证规则要求签入数据库,例如唯一名称或电子邮件,这会变得更加困难。然后,内部验证或静态方法没有这里那么有用,或者它们变得臃肿,因此需要分离关注点(例如,分离验证类)。我们不要忘记上下文相关的验证,当一名员工根据他来自何处(国家)或他/她所处的系统(如系统A或远程系统B)拥有姓名验证规则(例如,是否需要中间名)时。
if(PersonName.IsValid(name))
{
    employee = new Employee(PersonName.From(name));
}
else
{
    // Send a validation message to the user or something
}