5

我对 ddd 中的验证方法有疑问。我已经阅读了对此颇有争议的意见。有人说这应该存在于实体之外,另一些人说这应该放在实体中。我正在尝试找到一种我可以遵循的方法。

例如,假设我有带有电子邮件和密码的用户实体。用户有一个方法注册(电子邮件,密码)。电子邮件和密码验证应该放在哪里?我个人的看法是它应该在 Register() 方法中。但是这种方法可能会导致 User 类与验证内容混淆。一种方法可能是在单独的策略对象中提取电子邮件和密码规则,并仍然从 Register() 方法调用它们。

您对 DDD 中的验证方法有何看法?

4

4 回答 4

6

首先,我认为验证有点棘手,部分原因是需要考虑的各种背景。最终,将在各个应用层执行验证规则。至少,域对象中应该有标准的守卫。这些只是常规的前置条件和参数检查,它们应该是任何设计良好的对象的一部分,并且符合您对该Reigster方法的看法。正如lazyberezovsky 所说,这是为了防止对象进入无效状态。在这一点上,我支持始终有效的学校。我认为如果需要将实体保持在无效状态,应该为此创建一个新实体。

然而,单独使用这种方法的一个问题是,经常需要将这些验证规则导出到其他层,例如表示层。此外,在表示层,规则需要有不同的格式。它们需要同时呈现,并可能翻译成另一种语言,例如 JavaScript,以便立即获得客户端反馈。尝试从类引发的异常中提取验证规则可能很困难或不切实际。或者,可以在表示层重新创建验证规则。这要简单得多,尽管可能违反 DRY,但它允许规则依赖于上下文。特定工作流可能需要与实体本身强制执行的验证规则不同的验证规则。

所描述方法的另一个问题是,可能存在实体范围之外的验证规则,并且这些规则必须与其他规则合并在一起。例如,对于用户注册,另一个规则是确保电子邮件地址是唯一的。托管适用用例的应用程序服务通常会强制执行此规则。但是,它还必须能够将此规则导出到其他层,例如表示。

总的来说,我尝试将尽可能多的约束检查放入实体本身,因为我认为实体应该始终有效。有时可以设计规则框架,使其既可用于引发异常又可导出到外层。其他时候,简单地跨层复制规则会更容易。

于 2013-03-15T16:42:39.863 回答
3

实际上,用户的有效性取决于上下文。用户可以有效(姓名和电子邮件有效),但注册操作可能无法执行。为什么?因为可能已经存在同名用户。因此,在某些上下文中看起来有效的用户在注册上下文中可能是无效的。

因此,我将验证的一部分移到实体或值对象(例如 Mail 对象)中,以避免明显无效的实体状态(例如具有null名称的用户)。但是上下文相关的验证应该存在于那个上下文中。所以,如果我正在注册用户:

Mail mail = new Mail(blahblahblah); // validates if blah is valid email
User user = new User(name, mail); // validates if name is valid and mail not null
// check if there already exist user with such name or email
repository.Add(user);

另外我认为该方法Register应该是某些域服务(例如 MembershipService)的方法,因为它肯定应该使用某个存储库。

于 2013-03-15T15:56:53.427 回答
2

例如,假设我有带有电子邮件和密码的用户实体。用户有一个方法注册(电子邮件,密码)。电子邮件和密码验证应该放在哪里?

如果您关注 DDD,电子邮件地址和用户名是您域中的关键概念 - 因此它们应该表示为实体。在这种情况下,电子邮件地址的验证位于EmailAddress实体中,而用户名的验证应放置在Username实体中。

伪代码示例:

class EmailAddress
{
    Constructor(string email)
    {
        if (email.DoesNotMatchRegex("\w+@\w+.\w{2,3}"))
            throw error "email address is not valid"
    }
}

class Username
{
    Constructor(string username)
    {
        if (username.length < 6)
            throw error "username must be at least 6 characters in length"
    }
}

class User
{
    Register(Username username, EmailAddress email)
    {
        if (username == null)
            throw error "user must have a username"

        if (email == null)
            throw new error "user must provide email address"

        // At this point, we know for sure that the username and email address are valid...
    }
}
于 2013-03-15T17:58:06.177 回答
0

首先,我将两件事分开:验证和业务规则,后者通常进入服务或实体方法。对于有效电子邮件地址或有效密码等验证,我通常将它们粘贴到拥有它们的实体上,这样如果我将应用程序移植到不同的平台,我可以将它们与实体一起移动。现在对于您的注册方法示例,如果对象处于无效状态,我不明白为什么首先调用此方法,如果将调用它,我将使其成为另一个方法(如 SignUp)的一个步骤,可能会这样做像这样的东西:if (isValid()) register(username, password)

于 2013-03-16T00:37:24.943 回答