DDD域模型复杂validation

我正在使用域驱动设计原则重写我的ASP.NET MVC应用程序。 我正在尝试validation我的用户实体。 到目前为止,我能够validation基本规则(例如用户名和密码是非null /空格字符串)。 但是其中一条规则,我需要确保用户名是唯一的。 但是我需要访问数据库才能执行此操作,这意味着我必须将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") } } 

然而,这似乎……错了。 让我的实体依赖于我的存储库似乎是一个坏主意(如果我错了,请纠正我)。 但是在实体中使用validation代码是有道理的。 放置复杂validation码的最佳位置在哪里?

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

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

但是在实体中使用validation代码是有道理的。 放置复杂validation码的最佳位置在哪里?

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

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

我在这些类型的情况下使用的模式是将这种类型的validation逻辑放在应用程序服务中。 在某种程度上,这是有道理的,因为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产生摩擦时,它是一个可以回归的好地方。

然而,这似乎……错了

不,这根本没有错。 让域模型依赖于存储库是完全没问题的。 除此之外,您已经在一个更好的界面后面抽象了您的存储库。

或者不要使用构造函数注入。 如果存储库是唯一需要它的人,则将存储库传递给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") } } 

我同意@oleksii, 使用规范模式是一种更好的方法。 validation在不同的情况下具有不同的含义,因此我有理由将这种关注分开。