Latest guidance on multi-tenancy?

What is the latest guidance ServiceStack promotes (and supports) for registering and resolving dependencies that need to be reconfigured/instantiated differently for every HTTP request.

For many years using the ReuseScope.Request scope was frowned upon. I see this now but not sure if underneath this, things have progressed or not.

Is there any advancement on usage patterns for better creating/refreshing/updating dependencies for multi-tenanted apps? any recent examples of this to examine?

(Looking to learn something new, and hoping that things have moved on since the last time I had to do this (which was to make everything ReuseScope.None.)

The IOC should never be mutated after startup, if you need to pass anything through the request pipeline add it to IRequest.Items which everything down the request pipeline should have access to.

For clarity, are you saying that using ReuseScope.Request does not work the way we would expect it to work?

(Can you remind me again how using ReuseScope.Request would mutate the container?)

It sounded like you were after a solution that registers IOC deps per request, just wanted to make clear that IOCs should never be mutated after Startup.

The IOC docs are up to date, Request Scope still works or you can use scoped deps, but I personally have no reason for ever using it. Which of your deps require it? and where else is it being used outside of your Service.

In our case, (simplified, but close to actual) we have a service ServiceA : Service that takes another dependency AdapterB : IAdapterB that configures itself with settings (like an APIKEY) from another service IConfigurationSettings to talk over HTTP to an external service like SendGrid (lets say).

AdapterB is put in the container as Scoped:

  • container.AddScoped<IAdapterB>(c=> new AdapterB(c.Resolve<IConfigurationSettings>()))

Now, ConfigurationSettings : IConfigurationSettings gives access to both: static settings (read from appsettings.json (using IAppSettings) and settings per tenant (which are read from a permanent store like SQL/whatever through another adapter).

ConfigurationSettings is put in the container as Scoped:

  • container.AddScoped<IConfigurationSettings>(c=> new ConfigurationSettings(c.Resolve<IPermanentStorage>(), c.Resolve<ITenantService>()))

where ITenantService is also added to the container as a singleton this time:

  • container.AddSingleton<ITenantService, TenantService>()

A custom GlobalRequestFilter is added to the IAppHost that handles the request, and figures out the Id of the tenant of the inbound HTTP request (not important how at this stage, the result is that in the GlobalRequestFilter we resolve the ITenantService from the request, and call req.Resolve<ITenantService>().Set("thistenantid)

Now, later down the request pipeline, ServiceA uses AdapterB. The hope is that AdapterB is resolved at that point (for this request) and when it is resolved, it is instantiated and when it is instantiated, it reads the correct configuration settings (for this tenant), from the IConfigurationSettings which knows the current tenant by asking the singleton ITenantService, which should (for this request) reply with “thistenantid”.

Q1. Would you say any of this mutates the container?

Q2. Would you say using container.AddScoped() and container.AddSingleton() in these ways will just work in ServiceStack as is? and this setup should just work as described? (no extra worries)?

Q3. When you say “I personally have no reason for ever using it” (regarding Request Scope) are you saying that you would do the stuff above a different way, or are you saying that you’ve never had to do this kind of stuff? Either way I am curious because adding multi-tenancy (with physical infrastructure data partitioning - eg a separate database and sets of APIKEYs to access external services, per tenant) is not that uncommon in most SaaS products these days.

Calling any container .Add<Scoped/Singleton/Transient>/Register APIs modifies the IOC which should only happen on startup.

Using AddScoped() resolves dependencies using ASP .NET Core’s IOC, make sure you read the Scoped Dependencies docs as you’ll need to either register AddHttpContextAccessor() or use IRequest.TryResolve<T>() to resolve scoped dependencies.

Singletons should only be used if all its dependencies are singleton, otherwise it’s not a singleton.

I prefer a clean separation between dependencies & data, so I’d instead be constructing the Tenant data model at the start of the request and attaching it to IRequest.Items which everything else has access to. Any tenant-specific functionality would then just be passed the tenant data model as the first argument.

If preferred you could attach an entire tenant-specific constructed object graph wrapped around the tenant data model, but my preference is to always use the minimal abstraction necessary and would rather add tenant-specific extension methods for any DRY functionality on IRequest then abstracting away the data model.

Thansk @mythz,

Would you be interested in accepting a pull-request that adds verification to Funq? such that it can verify whether there are any “captured dependencies” registered in the container after configuration?