我一次又一次地回过头来想一想对需要访问某些上下文的POCO对象执行验证的最佳方法(例如NH中的ISession,IRepository)。
我仍然可以看到的唯一选择是使用服务定位器,因此我的验证将如下所示:
public User : ICanValidate {
public User() {} // We need this constructor (so no context known)
public virtual string Username { get; set; }
public IEnumerable<ValidationError> Validate() {
if (ServiceLocator.GetService<IUserRepository>().FindUserByUsername(Username) != null)
yield return new ValidationError("Username", "User already exists.")
}
}
我已经使用反转控制和依赖注入,并且由于事实的原因,实际上并不喜欢ServiceLocator:
但另一方面,对于普通的POCO对象,如果没有ServiceLocator并且仅使用IoC / DI,我没有看到任何其他方式执行上述验证。
目前,我在服务层中执行此类验证。因此,只要演员试图更改用户名(当然可能是不同的),服务就会执行此验证。一个明显的缺点是每个使用用户的服务都必须执行此检查(即使是一次通话)。
所以问题是:有没有办法在上述情况下使用DI / IoC ?
谢谢,
德米特里。
答案 0 :(得分:1)
存储库通常处于比它们获取/存储的域对象更高的抽象级别。如果根据存储库找到域对象,则表明上游存在设计问题。
你实际拥有的是循环依赖。 IUserRepository
取决于User
,User
取决于IUserRepository
。这个技术上是有效的,因为如果两个对象都在同一个程序集中它会编译,但它会让你在一般设计中遇到麻烦。可能有各种各样的对象想要处理User
,但对它来自的IUserRepository
一无所知。
我建议你不要将它作为User
的“验证”属性。验证应该由存储库本身执行,或者 - 更好的是 - 如果用户名在尝试保存时已存在,则只需让存储库引发异常。
这个建议有第二个原因。这个原因是并发性的。即使您验证了用户名并发现不已经存在,但是当您尝试保存该用户时,1秒后可能会出现这种情况。因此,您需要处理异常情况(尝试插入已存在的用户名)无论如何。鉴于此,您可能会推迟到最后一刻,因为您无法事先做出保证。
域对象应具有 no 依赖关系;如果他们自我验证,那么验证应该仅依赖于 对要验证的实际对象,而不是数据库中的其他数据。重复的用户名约束实际上是数据约束,而不是域约束。
摘要:将此特定验证移到User
类之外。它不属于那里;这就是为什么你发现自己正在使用这种特殊的反模式。
答案 1 :(得分:1)
加上Aaronaught所说的话。这种设计存在一个更大的问题,因为域模型验证应该只验证模型固有的属性 - 而不是在更大系统的上下文中。这些内在属性的一些示例是对用户名长度,可接受字符的要求,即提交名字和姓氏等。
您正在执行的验证是系统范围的验证,属于服务/存储库。如果使用域驱动设计设计,这就是这个系统的样子:
public class User : ICanValidate {
public User() {}
public virtual string Username { get; set; }
public IEnumerable<ValidationError> Validate() {
if (!string.IsNullOrEmpty(this.UserName))
yield return new ValidationError("Username must not be empty");
}
}
public class UserRepository : IUserRepository {
}
public static class UserService {
readonly IUserRepository Repository;
static UserService() {
this.Repository = ServiceLocator.GetService<IUserRepository>();
}
public static IEnumerable<ValidationError> Validate(User user) {
if (Repository.FindUserByUsername(user.Username) != null)
yield return new ValidationError("Username", "User already exists.")
}
}