ddd中的验证方法

时间:2013-03-15 15:48:06

标签: oop domain-driven-design

我对ddd中的验证方法有疑问。我已经阅读了相当有争议的意见。有人说这应该是实体生活,有人说这应该放在实体中。我正试图找到一种我可以遵循的方法。

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

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

4 个答案:

答案 0 :(得分:5)

首先,我认为验证在某种程度上是一个滑的主题,部分原因在于需要考虑的各种contexts。最终,将在各种应用层执行验证规则。至少,域对象中应该有标准的防护。这些只是常规的前置条件和参数检查,应该是任何精心设计的对象的一部分,并且符合您对Reigster方法的看法。正如lazyberezovsky所说,这是为了防止物体进入无效状态。我支持永远有效的学校。我认为如果需要将实体持久化为无效状态,则应为此创建新实体。

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

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

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

答案 1 :(得分: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),因为它肯定应该使用某些存储库。

答案 2 :(得分: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...
    }
}

答案 3 :(得分:0)

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