【问题标题】:Best strategy for creating a child container (or isolated scope) with Microsoft.Extensions.DependencyInjection使用 Microsoft.Extensions.DependencyInjection 创建子容器(或隔离范围)的最佳策略
【发布时间】:2022-05-02 15:50:42
【问题描述】:

在我的 AspNetCore 应用程序中,我处理来自队列的消息。为了处理消息,我需要解析一些服务。其中一些服务依赖于ITenantId,我使用接收到的消息中的信息对其进行绑定。为了解决这个问题,消息的处理从创建一个子容器开始,然后我用它来创建一个IServiceScope,我从中解决所有需要的依赖关系。

消息可以并行处理,因此作用域必须相互隔离。

我可以找到创建子容器的方法,但我不确定哪种方法在性能、内存流失等方面最好:

选项A:每次消息到达时,将IServiceCollection克隆到一个新的ServiceCollection中,并在克隆的实例上重新绑定ITenantId

选项 B:当程序启动时,创建 IServiceCollection 的不可变副本(使用 ImmutableList<ServiceDescriptor>ImmutableArray<ServiceDescriptor>)。每次消息到达时,替换 ITenantId(导致 ImmutableList<ServiceDescriptor> 的新实例)并在新的不可变实例上调用 CreateScope()

我不喜欢选项 A 的一点是,每次消息到达时都需要克隆整个服务集合。我不确定选项 B 中的不可变集合是否以更智能的方式处理这个问题?

【问题讨论】:

  • 考虑不使用(任何)DI 容器作为分离特定于租户的状态的机制。因为这样做会导致“魔术”,其中一些服务将是状态感知的,而有些则不是(没有透明的说明为什么),以及如果你想对多个租户执行操作的低效率,你将不得不创建很多范围。相反,您可以使用内置“区域/租户”支持的缓存。但是,感谢您提出这个问题,我发现创建子容器的能力很有用,例如在测试自动化中,为每个并行线程创建一个克隆的子容器。

标签: asp.net-core .net-core dependency-injection


【解决方案1】:

这两个选项都会为每个传入消息创建一个新的容器实例。尽管这允许每条消息在完全隔离的气泡中运行,但这会对应用程序的性能和内存使用产生严重影响。创建容器实例的成本很高,并且第一次(每个容器)解析已注册的实例会导致生成表达式树、编译委托和 JIT 编译它们。这甚至会导致内存泄漏。

此外,这还意味着任何注册的单例类的生命周期都与任何作用域类的生命周期相同。无法再共享状态。

因此,我建议选择方案 3:

  • 只使用一个 Container 实例,不要多次调用BuildProvider
  • 创建一个ITenantId 实现,允许在实例化后设置Id
  • 将该实现注册为Scoped
  • 在每个新的 IServiceScope 开始时,解析该实现并设置其 ID。

这可能如下所示:

// Code
class TenentIdImpl : ITenantId
{
    public Guid Id { get; set; } // settable
}

// Startup:
services.AddScoped<TenentIdImpl>();
services.AddScoped<ITenantId>(c => c.GetRequiredService<TenantIdImpl>());

// In message pipeline
using (var scope = provider.CreateScope())
{
    var tenant = scope.ServiceProvider.GetRequiredService<TenantIdImpl>();
    tenant.Id = messageEnvelope.TenantId;

    var handler =
        scope.ServiceProvider.GetRequiredService<IMessageHandler<TMessage>>();

    handler.Handle(messageEnvelope.Message);
}

这个特殊的模型,我在我的博客中解释过,将状态存储在对象图中,称为Closure Composition Model

【讨论】:

  • 这是个好主意!由于 TenentIdImpl 对于每个范围都是唯一的,因此您仍然会被隔离。今天晚些时候我会试试这个。谢谢!
  • 只是想回复您并确认这一切正常。但是有一个问题:它为什么称为闭包组合模型?我看不出它依赖于通常意义上的闭包,运行时将关闭一组函数可用的字段/变量。据我所知,它更依赖于 ITenantId 和 ITenantIdImpl 被绑定为范围的事实。
  • 闭包组合模型描述了一个模型,其中变量被包装并存储在包含类中,很像功能闭包的工作方式,与环境组合模型相比,数据存储在类之外,但是而是从环境状态存储和检索。当运行时值通过构造函数注入时,您可能会更直观地使用术语“闭包”,但这实际上在使用 DI Cotainer 时很难实现。
猜你喜欢
  • 1970-01-01
  • 2016-09-29
  • 1970-01-01
  • 2015-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-29
  • 1970-01-01
相关资源
最近更新 更多