【问题标题】:Validation without ServiceLocator没有 ServiceLocator 的验证
【发布时间】:2010-05-10 23:41:25
【问题描述】:

我一次又一次地思考对需要访问某些上下文的 POCO 对象执行验证的最佳方法(例如 NH 中的 ISession,IRepository)。

我仍然可以看到的唯一选项是使用 Service Locator,所以我的验证如下所示:

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 的情况下执行上述验证的方法。

目前我在服务层中执行这种验证。因此,每当参与者尝试更改用户名(当然可能是不同的用户名)时,服务都会执行此验证。一个明显的缺点是每个使用 User 的服务都必须执行此检查(即使是一次调用)。

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

谢谢,
德米特里。

【问题讨论】:

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


    【解决方案1】:

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

    您实际上拥有的是循环依赖。 IUserRepository 取决于 UserUser 取决于 IUserRepository。这技术上是有效的,如果两个对象在同一个程序集中它会编译,但作为一个通用设计它会给你带来麻烦。可能有各种各样的对象想要处理User,但对它来自的IUserRepository 一无所知。

    我对您的建议是不要将此作为User 的“验证”属性。验证应该由存储库本身执行,或者 - 更好的是 - 如果在尝试保存时用户名已经存在,则让存储库引发异常。

    这个建议还有一个次要原因。那个原因就是并发。即使您验证了用户名并发现它确实存在,但 1 秒后当您尝试保存该用户时,这可能不是真的。所以你需要处理例外情况(试图插入一个已经存在的用户名)无论如何。鉴于此,您最好将其推迟到最后一刻,因为您无法事先做出保证。

    域对象应该有no依赖;如果他们自我验证,那么验证应该依赖于被验证的实际对象,而不是数据库中的其他数据。重复用户名约束实际上是数据约束,而不是域约束。

    总结:将此特定验证移到User 类之外。它不属于那里;这就是为什么你会发现自己在使用这种特殊的反模式。

    【讨论】:

    • 我完全同意这一点。目前我在我的服务层执行Unique 验证,但我必须复制/继承每个服务中与用户一起使用的逻辑。
    • @Dmitriy:很好,可以理解;然后,我会将这个逻辑放在客户端/用户与之交互的 service 中,而不是特定项目。如果您需要保持服务整洁,请委托给一个特殊的验证者。
    • 我所有的services 目前都是特定用例/故事的实现(因此它们可以被视为用户与之交互的那些)。我要做的可能是添加另一个依赖项IUserValidation 并将其注入到用例服务中。我认为这将是目前最好的方式。
    【解决方案2】:

    只是为了补充 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.")
      }
    }
    

    【讨论】:

    • 伊戈尔,谢谢。我刚刚添加了一个用户验证服务,所以我可以重用它。稍后我会考虑将其命名为“UserService”:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-02
    • 1970-01-01
    • 2013-12-18
    • 1970-01-01
    相关资源
    最近更新 更多