使用依赖注入在多个租户的控制台应用程序中动态范围内的依赖解析

Dynamic scoped dependency resolution in a console app for multiple tenants using dependency injection

在 Web API 的情况下,每个请求都是一个不同的范围,注册为范围的依赖项将根据请求得到解析。因此,解决每个租户每个请求的依赖关系很容易,因为租户信息(如 TenantId)可以在 HTTP 请求 headers 中传递,如下所示:

services.TryAddScoped<ITenantContext>(x =>
{
    var context = x.GetService<IHttpContextAccessor>().HttpContext;
    var tenantId = context.Request.Headers["TenantId"].ToString();
    var tenantContext = GetTenantContext(tenantId);
    return tenantContext;
}

其他注册先解析TenantContext,用它来解析其他依赖。例如,IDatabase 将注册如下。在解析期间,它将解析并连接到特定的租户数据库。

services.TryAddScoped<IDatabase>(x =>
{
    var tenantContext = x.GetService<ITenantContext>();
    return new Database(tenantContext.DatabaseConnectionString);
}

这在 Web API 服务中都很好,因为每个请求都是一个范围。我在 multi-tenant 控制台应用程序中使用依赖注入面临挑战。假设应用程序处理来自 multi-tenant queue 并且每条消息可以属于不同的租户。在处理每条消息时,它将数据提交给租户特定的数据库。所以在这种情况下,范围是 queue 中的每条消息,并且消息包含 tenantId。

所以当应用程序从 queue 读取消息时,它需要获取 TenantContext。然后根据这个TenantContext.

解决其他依赖

我看到如何实现此动态解析的一个直接选项是使用 TenantContext 手动创建依赖项 objects,但这样我就无法利用依赖项注入。所有 objects 都将在处理消息后超出范围后手动创建和处置。

var messgage = GetMessageFromQueue(queueName);
var tenantContext = GetTenantContext(message.TenantId);
var database = GetDatabaseObject(tenantContext);
// Do other processing now we got the database object connected to specific tenant DB

DI 中是否有一个选项,我可以在其中动态传递 TenantId,以便为此范围设置 TenantContext,然后此范围内的所有进一步解析都利用此 TenantContext

因为租赁的作用超出了实现(“这使用 X 数据库”)并且实际上与正在执行的操作相关(“这使用 X 数据库并且必须根据正在处理的上下文使用此连接字符串在操作中”),假设环境上下文存在于替代实现中存在一些风险,因为它没有以某种方式在您的界面中明确描述,这就是此处出现 DI 问题的地方。

您也许能够:

  1. 更新您的接口,使租赁信息成为您方法的预期参数。这确保了无论未来的实现如何,租户 ID 的存在在他们的签名中是明确的:

    public interface ITenantDatabase {
       public TResponse Get(string TenantId, int Id);
       //... other methods ...
    }
    
  2. 在现有接口周围添加一个工厂包装器,以处理对象创建时的上下文分配,并使该工厂 return 成为 IDatabase 实例。这基本上是您手动提出的建议,但围绕它有一个抽象,您可以注册和注入以防止利用它的代码负责逻辑:

    public interface ITenantDatabaseFactory {
       public IDatabase GetDatabaseForTenant(int TenantId);
    }
    
    // Add an implementation that manually generates and returns the scoped objects