12

我正在使用域驱动设计原则重写我的 ASP.NET MVC 应用程序。我正在尝试验证我的用户实体。到目前为止,我能够验证基本规则(例如用户名和密码是非空/空白字符串)。但是其中一条规则,我需要确保用户名是唯一的。但是,我需要访问数据库才能执行此操作,这意味着我必须像这样将 IUserRepository 注入我的 User 实体中。

public class User
{
    private readonly IUserRepository _userRepository;
    public User(IUserRepository repo)
    {
        _userRepository = repo;
    }

    public override void Validate()
    {
        //Basic validation code
        if (string.IsNullOrEmpty(Username))
            throw new ValidationException("Username can not be a null or whitespace characters");
        if (string.IsNullOrEmpty(Password))
            throw new ValidationException("Password can not be a null or whitespace characters");

        //Complex validation code
        var user = _userRepository.GetUserByUsername(Username);
        if (user != null && user.id != id)
            throw new ValidationException("Username must be unique")
    }
}

然而,这似乎......错了。让我的实体依赖于我的存储库似乎是个坏主意(如果我错了,请纠正我)。但是在实体中包含验证代码是有意义的。放置复杂验证代码的最佳位置在哪里?

4

4 回答 4

10

我在这些类型的情况下使用的一种模式是将这种类型的验证逻辑放在应用程序服务中。在某种程度上,这是有道理的,因为User实体只对自己的有效性负责,而不是对用户集的有效性负责。创建用户的应用程序服务方法可以如下所示:

public User CreateUser(string userName)
{
  if (this.userRepository.Exists(userName))
    throw new Exception();
  var user = new User(userName);
  this.userRepository.Add(user);
  return user;
}

应用程序服务是一个抽象,无论您是否使用 DDD,它都存在,因此当 DDD 产生摩擦时,它是一个很好的回退位置。

于 2012-08-22T00:11:42.507 回答
9

然而,这似乎......错了。让我的实体依赖于我的存储库似乎是个坏主意(如果我错了,请纠正我)。

一般来说,对存储库的依赖并不是“错误的”,它有时是不可避免的。但是我认为这应该是一个例外,应该尽可能避免。在您的场景中,您可能会重新考虑拥有这种依赖关系。如果您考虑一下,“唯一性”不是实体本身的责任,因为实体不了解其他实体。那么为什么要让实体执行这条规则呢?

但是在实体中包含验证代码是有意义的。放置复杂验证代码的最佳位置在哪里?

我认为您可能过度概括了“验证”。我将摆脱“验证”方法,并首先确保对象不会进入“无效”状态。几个月前我回答了类似的问题。

现在回到唯一性规则。我认为这是 DDD 有点“泄漏”的例子之一,从某种意义上说,这个业务规则的执行不能纯粹用域代码来表达。我会这样处理它:

// repository
interface Users {
  // implementation executes SQL COUNT in case of relation DB
  bool IsNameUnique(String name);

  // implementation will call IsNameUnique and throw if it fails
  void Add(User user);
}

客户端代码会知道,在添加新用户之前,它应该明确检查唯一性,否则会崩溃。这种组合在域代码中强制执行业务规则,但这通常是不够的。作为附加的执行层,您可能希望在数据库中添加 UNIQUE 约束或使用显式锁定。

于 2012-08-14T18:12:15.220 回答
5

然而,这似乎......错了

不,这根本没有错。让域模型依赖于存储库是非常好的。除此之外,您还在一个更好的接口后面抽象了您的存储库。

或者不要使用构造函数注入。将存储库传递给 Validate 方法,如果它是唯一需要它的方法:

public class User
{
    public void Validate(IUserRepository repo)
    {
        //Basic validation code
        if (string.IsNullOrEmpty(Username))
            throw new ValidationException("Username can not be a null or whitespace characters");
        if (string.IsNullOrEmpty(Password))
            throw new ValidationException("Password can not be a null or whitespace characters");

        //Complex validation code
        var user = repo.GetUserByUsername(Username);
        if (user != null && user.id != id)
            throw new ValidationException("Username must be unique")
    }
}
于 2012-08-14T17:11:55.253 回答
1

我同意@oleksii,使用规范模式是一种更好的方法。验证在不同的上下文中具有不同的含义,因此对我来说分开这个问题是有意义的。

于 2012-08-14T17:59:01.947 回答