不使用 ServiceLocator 进行验证

发布于 2024-09-01 04:55:36 字数 1056 浏览 2 评论 0原文

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

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

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

谢谢,
德米特里。

I am getting back again and again to it thinking about the best way to perform validation on POCO objects that need access to some context (ISession in NH, IRepository for example).

The only option I still can see is to use Service Locator, so my validation would look like:

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.")
    }
}

I already use Inversion Of control and Dependency Injection and really don't like the ServiceLocator due to number of facts:

  • Harder to maintain implicit dependencies.
  • Harder to test the code.
  • Potential threading issues.
  • Explicit dependency only on the ServiceLocator.
  • The code becomes harder to understand.
  • Need to register the ServiceLocator interfaces during the testing.

But on the other side, with plain POCO objects, I do not see any other way of performing the validation like above without ServiceLocator and only using IoC/DI.

Currently I perform such kind of validation in the service layer. So whenever an actor tries to change username (might be of course something different) the service performs this validation. One obvious disadvantage is that every service using the User must perform this check (even if it is one call).

So the question would be: is there any way to use DI/IoC for the situation described above?

Thanks,
Dmitriy.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

方觉久 2024-09-08 04:55:36

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

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

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

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

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

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

Repositories are generally at a higher level of abstraction than the domain objects they fetch/store. If you find your domain objects depending on repositories, then it's an indication of design problems upstream.

What you actually have is a circular dependency. The IUserRepository depends on the User and the User depends on the IUserRepository. This technically works, in the sense that it will compile if both objects are in the same assembly, but it will get you into trouble as a general design. There may be all sorts of objects that want to deal with a User but don't know anything about the IUserRepository it came from.

My suggestion to you is not to make this a "validation" property of the User. The validation should be performed by the repository itself, or - better yet - just have the repository raise an exception if the user name already exists when it tries to save.

There's a secondary reason for this suggestion. That reason is concurrency. Even if you validate the user name and find that it does not already exist, that may not be true 1 second later when you try to save that user. So you need to handle the exceptional case (tried to insert a user name that already exists) anyway. Given this, you might as well defer that until the last possible moment, since you have no way to make guarantees beforehand.

Domain objects should have no dependencies; if they self-validate, then the validation should depend only on the actual object being validated, and not other data in the database. The duplicate username constraint is actually a data constraint, not a domain constraint.

Summary: Move this particular validation outside the User class. It doesn't belong there; that's why you're finding yourself using this particular anti-pattern.

我很坚强 2024-09-08 04:55:36

只是补充一下阿罗诺特所说的内容。这种设计存在一个更大的问题,因为领域模型验证应该只验证模型固有的属性,而不是在更大系统的上下文中。此类固有属性的一些示例包括用户名长度、可接受的字符、名字和姓氏均已归档等要求。

您正在执行的验证是系统范围的验证,属于服务/存储库。如果使用领域驱动设计来设计,该系统将如下所示:

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.")
  }
}

Just to add what Aaronaught is saying. There is a larger problem with this design as the Domain Model validation should only validate attributes intrinsic to the model - not within the context of the larger system. Some examples of such intrinsic properties would be requirements for username length, acceptable characters, that both first and last name are filed etc.

The validation that you are performing is system-wide validation and belongs in a service/repository. This is how this system would look like if designed using Domain Driven Design:

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.")
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文