0

我一次又一次地思考对需要访问某些上下文的 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 的情况下执行上述验证。

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

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

谢谢,
德米特里。

4

2 回答 2

1

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

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

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

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

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

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

于 2010-05-10T23:49:04.470 回答
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.")
  }
}
于 2010-05-11T00:18:14.980 回答