1

I have a multitenant application on a micro service architecture design.

I want to inject X number of services, depending on the number of tenants running.

 public void ConfigureServices(IServiceCollection services)
    {
        // ... OTHER DI

        services.AddHttpClient("TenantsService")
            .AddTypedClient<ITenantServiceClient>(c => new TenantServiceClient(new TenantServiceClientSettings()
            {
                AccessKey = Configuration["TenantsService:ApiKey"],
                BaseUrl = new Uri(Configuration["TenantsService:Url"])
            }, c)); 

        foreach (var tenant in TenantsToRegister)
        {
            services
                .AddGraphQLServer($"{tenant.Name}");
        }

        ...

    }

The above code would work if I had the list of tenants when the application starts. But I need to request that list from another microservice. Having this constraint, I need to build the service provider in order to get that list. At the same time, I need the list before the service provider's build to inject the services I need.

The only option that I see is adding the services at runtime, but I'm not sure if it's possible.

  • Microsoft's DI is completely runtime. You can do whatever you need to do to acquire `TenantsToRegister` inside your `ConfigureServices` method. – Kevin Krumwiede May 27 '21 at 16:43
  • @KevinKrumwiede When I said "runtime" what I really meant to say was "after the service provider is built". What I need to do it's an async HTTP call using `ITenantServiceClient` that I also inject in the service collection. I'm not finding any way to do this – joao-figueira May 27 '21 at 17:04
  • Sounds like a job for GetRequiredService? https://andrewlock.net/the-difference-between-getservice-and-getrquiredservice-in-asp-net-core/ – Nikki9696 May 27 '21 at 21:46

2 Answers2

0

You have a problem... You are trying to scale vertically in multitenant environment. That you will do with 10 tenants? 100? It is tons of ram for single process on single node (it might be not a case if you 100% sure you will have some of them) without any chance to scale horizontally

I think you can create service per tenant from same image, but different configuration and api gateway/loadbalancer depending on... Something... (Header, query param, user id, etc.). It might require some infrastructure investments, but will not be a pain in a future

If you really want to load tenants info from http client and then add HC gql servers per tenant i suggest you to write your own IConfiguration provider.

This is Consul integration, it has the same http roundtrip as you need https://github.com/wintoncode/Winton.Extensions.Configuration.Consul. It adds custom configuration source and loads it on startup

After that you just map your tenants info from configuration in startup

0

From a architectual point of view I would recommend to use "real" services for each tenant. For example start a docker or a process of your application per tenant. Like written in a former answer (thanks to sergey).

If you really want to start a new GraphQLServer for each tenant in the same process then I would do it like this: Full example on dotnet fiddle

services
    .AddHttpClient("TenantsService")
    .AddTypedClient<ITenantServiceClient>(c => 
                                    new TenantServiceClient(new TenantServiceClientSettings
                                    {
                                        AccessKey = "THE KEY", // Configuration["TenantsService:ApiKey"], 
                                        BaseUrl = new Uri("https://the-uri-you-need.com") // new Uri(Configuration["TenantsService:Url"])
                                    }, c));
        
// build a temporary service provider before, with all services added until now.
var tempServiceProvider = services.BuildServiceProvider();

// resolve the tenant service and query for tenants
var tenantsToRegister = tempServiceProvider.GetRequiredService<ITenantServiceClient>().GetTenants();

// register needed tenants
foreach (var tenant in tenantsToRegister)
{
    services.AddGraphQLServer($"{tenant}");
}

... of course the whole error handling around the call to the ITenantServiceClient must be added but its just for demo purpose. Here the special thing is the intermediate IServiceProvider which gives you the ability to use the full set of DI to query for your tenants but let you add the needed ones afterwards.

BUT: This is also on startup and no additional servers can be added while runtime.

Update Here is the updated dotnet fiddle (thanks to joao-figueira)

Martin
  • 3,096
  • 1
  • 26
  • 46
  • 1
    thanks for your reply. The full example on the dotnet fiddle was very helpful and resolved my problem for now. For the ones that may be looking for the same solution, the only part on Martin's code that was missing was how to pass the collection built on the Main method back to the ConfigureServices on startup (to continue adding other DIs). I'm now posting the solution that I've used on another comment. – joao-figueira May 28 '21 at 10:09
  • [link] https://dotnetfiddle.net/hi05JN Martin's solution with the mentioned detail (see previous comment) – joao-figueira May 28 '21 at 10:29
  • @joao-figueira Thanks for the update :) . I will add this to the answer – Martin May 28 '21 at 12:04