域驱动设计:服务和验证

时间:2012-10-01 16:36:59

标签: domain-driven-design

我遵循域驱动的设计规则来设计系统。我对服务,外部系统和验证都有疑问。

聚合必须与其他系统的Web服务交互以进行验证并提供信息。我不确定允许聚合访问外部单词是个好主意。如果我创建一个服务来访问外部服务,我很难强制执行不变量和验证。如果我把所有逻辑都放在汇总中,这听起来不是一个好主意,但这些问题似乎消失了。

为了使问题更容易理解,假设有一个用户聚合,并且必须发送电子邮件以确保电子邮件是正确的(在我真正的问题中,我必须与外部Web服务通信)

public class User {
    public User (Long id, String name, String email) {...}
    public changeEmail(String newEmail) {...}
    ...
}

public interface EmailValidatorService {
    /**
    * Sends a test email
    */
    public verifyEmail(String email) throws EmailException;
}

我不确定这是一个好主意还是电子邮件验证逻辑应该是User汇总的一部分。也许它可能是一个服务,用户聚合可以使用它......但它听起来也不是一个好主意。

如果它是User聚合的一部分,它将承担额外的责任,如果它是一项服务,我看不到一种简单的方法来强制执行域规则。 ¿如果开发人员使用changeEmail而未使用该服务验证它会怎样?

1 个答案:

答案 0 :(得分:3)

您有几个选项可以实现此方案。一种是使用处理此特定用例的应用程序服务来调用验证功能:

class UserService
{
  EmailValidatorService emailValidatorService;
  UserRepository userRepository;

  public void changeUserEmail(string currentEmailAddress, string newEmailAddress)
  {
    var user = this.userRepository.GetByEmail(currentEmailAddress);
    if (user == null)
       throw ...;


    this.emailValidatorService.verifyEmail(newEmailAddress);

    user.changeEmail(newEmailAddress);

    // commit, etc...
  }
}

应用程序服务是注入此类验证规则的便利位置 - 需要调用外部服务的排序,或者聚合无法轻松访问的服务。更一般地说,应用程序服务可以是一种“后备”机制,用于处理纯DDD方法不太适合的情况。此外,无论您使用的是域模型还是transaction script,都可以使用应用程序服务。

另一个选择是在User类的changeEmail方法中为聚合提供验证器:

class User
{
  string emailAddress;

  public void changeEmail(string newEmailAddress, EmailValidatorService validator)
  {
    validator.verifyEmail(newEmailAddress);
    this.emailAddress = newEmailAddress;
  }
}

这里的好处是聚合封装了与电子邮件地址更改相关的所有逻辑。因此,客户端代码无法在未提供验证程序的情况下更改电子邮件地址 - 这是应用程序服务方法无法实施的。此外,这与通过依赖注入引用验证器服务的用户聚合不同,这通常是不赞成的。相反,这是一种即时依赖注入。

在此特定方案中需要考虑的另一件事是您希望执行的验证的性质。一个特征是,如果电子邮件地址在某一时刻有效,则将来可能不再有效。这意味着您已经需要有工作流来处理具有无效电子邮件地址的现有用户。如果这个逻辑已经存在,那么为什么还要确保在域中确认?毕竟,即使验证器服务确保电子邮件地址有效且有效,也无法保证用户可以访问它。