【问题标题】:DDD Domain Model Complex ValidationDDD 领域模型复杂验证
【发布时间】:2012-08-14 17:09:54
【问题描述】:

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

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")
    }
}

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

【问题讨论】:

标签: c# asp.net-mvc validation domain-driven-design


【解决方案1】:

我在这些类型的情况下使用的一种模式是将这种类型的验证逻辑放在应用程序服务中。在某种程度上,这是有道理的,因为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 出现摩擦时,它是一个很好的回退位置。

【讨论】:

    【解决方案2】:

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

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

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

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

    现在回到唯一性规则。我认为这是 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 约束或使用显式锁定。

    【讨论】:

      【解决方案3】:

      然而这似乎……大错特错

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

      或者不要使用构造函数注入。将存储库传递给 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")
          }
      }
      

      【讨论】:

      • 感谢您的回答。问题是现在我必须有一个 IUserRepository 的实现才能构造一个用户,这意味着我必须使用 IoC 来注入实体的依赖项。这似乎比它的价值更多的代码,特别是因为我在构造对象时利用了初始化属性 Example var user = new User { Username = "Admin" };
      • 在这种情况下不要使用构造函数注入。如果这是唯一需要的,则将存储库传递给 Validate 方法。
      【解决方案4】:

      我同意@oleksii,using the specification pattern 是更好的方法。验证在不同的上下文中具有不同的含义,因此对我来说分开这个问题是有意义的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-12-11
        • 2020-01-29
        • 1970-01-01
        • 2011-04-03
        • 2011-09-11
        • 2012-08-07
        • 2019-07-19
        • 2019-05-09
        相关资源
        最近更新 更多