【问题标题】:DDD Stateless Services and Constructor InjectionDDD 无状态服务和构造函数注入
【发布时间】:2014-03-29 01:10:59
【问题描述】:

在领域驱动设计文献中,经常说领域服务应该是无状态的。

我相信这是因为服务调用应该代表单个工作单元。不应该有多个服务方法会使用的任何服务状态。

我在我的服务架构中打破了这条规则,以便我可以构造函数注入服务中所需的所有相关存储库。示例:

public class UserService : IUserService
{
    public IUnitOfWork UnitOfWork { get; set; }

    public IUserRepository UserRepository { get; set; }

    public ICustomerRepository CustomerRepository { get; set; }

    public UserService(IUnitOfWork unitOfWork, IUserRepository userRepository, ICustomerRepository customerRepository)
    {
        UnitOfWork = unitOfWork;
        UserRepository = userRepository;
        CustomerRepository = customerRepository;
    }

    public User RegisterNewUser(...)
    {
        // Perform relevant domain logic
    }

    // ...
}

为了让我在UserService 上使用构造函数注入,我需要拥有状态(属性),以便服务方法可以访问相关的存储库等。

虽然我希望将各个服务方法设计为独立的工作单元,但我不一定能阻止这种情况发生。

我如何构建域服务以使其无状态?这甚至有必要吗?

编辑:

埃里克·埃文斯Domain-driven Design: Tackling Complexity in the Heart of Software

当域中的重要过程或转换不是 ENTITY 或 VALUE OBJECT 的自然责任,添加操作 将模型作为独立接口声明为服务。定义 根据模型的语言进行接口,并确保 操作名称是 UBIQUITOUS LANGUAGE 的一部分。做服务 无状态

Vaughn Vernon还在他的书中Implementing Domain Driven Design推荐了无状态服务。

【问题讨论】:

  • 如果你想让服务成为无状态的,你应该通过方法参数传入依赖,但这会非常可怕,并且会将这些依赖从实现细节提升到合同的一部分。
  • 是的,那不会很有趣。这也意味着我必须在应用层和/或 MVC 控制器中注入相关的存储库。
  • @Steven:检查我的编辑,谢谢。
  • 我相信 Evens 的含义与您想象的不同。我认为他将服务的状态与域对象的状态进行了比较。域对象显然会具有状态并在其生命周期内更改其状态。服务不会或不应该改变状态,我认为这就是他所说的“无状态”。我认为让服务完全无状态是不可能的,因为它总是在某种有状态的上下文中运行;例如一个 UnitOfWork,或者一个数据库事务。无论您注入依赖项还是使用服务位置,服务都将始终受此影响。
  • @Steven:感谢您的澄清。

标签: design-patterns dependency-injection domain-driven-design domainservices onion-architecture


【解决方案1】:

在我看来,您将拥有属性与拥有状态混淆了。

UserService 是一项服务。它只有无状态服务的只读(请删除设置器)属性,例如IUserRepository。这使得UserService 本身成为无状态服务。

是的,我们甚至可以在顶部添加一个更高级别的服务,它的组件之一是 IUserService。该服务也将是无状态的。

你问为什么?

无状态的目的(或至少一个目的)是允许控制反转:我们将对我们获得的实例的控制权交给外部。如果其他人正在控制我们获得的实例,那么这些实例最好是无状态的!如果我们得到与另一个类相同的实例,并且我们都开始改变它的状态怎么办?结果是不确定的。

没错,如果我们的服务根本没有属性,这个问题就可以避免——比如UserRepository,也许。另请注意,当更高级别的服务(例如UserService)独占此类服务的只读属性时,同样可以避免,因为没有要分配的内容。我们可以放心,注入的服务将始终正确运行,因为它的层次结构中没有状态可供它依赖。

【讨论】:

    【解决方案2】:

    接近您的目标的一种方法是将 IOC 容器注入您的服务类,然后覆盖您的属性 get 方法以解析必要类的实例。您的新课程将如下所示:

    public class UserService : IUserService
    {
      private IUnitOfWork UnitOfWork 
      { 
        get{return container.Resolve<IUnitOfWork>()}
      }
      private IUnityContainer container {get;set;}
    
      public UserService(IUnityContainer container )
      {
        this.container = container;
      }
    
      public User RegisterNewUser(User user)
      {
         //Domain logic
      }
    
    }
    

    您的服务类现在依赖于 IOC 容器,这不是一件好事,但如果您试图更接近无状态服务,则可以这样做。

    【讨论】:

    • 感谢您的回答。这似乎是最好的选择,但我绝对不想在我的域层中创建对 IOC 容器的依赖。甚至有必要拥有无状态服务吗?
    • 查看您的代码,我假设您正在制作一个 Web 应用程序数据访问层,因此主要关注的是处理多线程。如果您配置您的应用程序以便每个用户都获得她自己的 IUserService 实例,那么这并不重要。如果用户共享相同的服务,那么您可能会遇到并发问题(即,当另一个线程正在进行调用时,会调用 IUnitofWork.Save())。在这种情况下,我创建的解决方案可以帮助用户共享相同的 IUserService 实例,但通过 IOC 容器获取他们自己的 DbContexts。
    • 这真是个坏主意。这是Service Locator anti-pattern
    • 还要注意,这根本不能解决状态问题,因为从状态的角度来看,通过构造函数注入依赖项和从方法内部的容器中解析它们之间几乎没有区别。您的大多数服务将是无状态的,但在运行时取决于将保持所需状态的工作单元。使用相同的 UoW 并且状态量保持不变。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-02
    • 2017-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-19
    相关资源
    最近更新 更多