没有ServiceLocator的验证

时间:2010-05-10 23:41:26

标签: c# .net design-patterns dependency-injection service-locator

我一次又一次地回过头来想一想对需要访问某些上下文的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:

  • 更难维护隐式依赖。
  • 更难以测试代码。
  • 潜在的线程问题。
  • 仅对ServiceLocator显式依赖。
  • 代码变得难以理解。
  • 需要在测试期间注册ServiceLocator接口。

但另一方面,对于普通的POCO对象,如果没有ServiceLocator并且仅使用IoC / DI,我没有看到任何其他方式执行上述验证。

目前,我在服务层中执行此类验证。因此,只要演员试图更改用户名(当然可能是不同的),服务就会执行此验证。一个明显的缺点是每个使用用户的服务都必须执行此检查(即使是一次通话)。

所以问题是:有没有办法在上述情况下使用DI / IoC

谢谢,
德米特里。

2 个答案:

答案 0 :(得分:1)

存储库通常处于比它们获取/存储的域对象更高的抽象级别。如果根据存储库找到域对象,则表明上游存在设计问题。

你实际拥有的是循环依赖。 IUserRepository取决于UserUser取决于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.")
  }
}