Based on some suggestions from Nick, I'm going to try a new direction
with multitenancy that will hopefully not require the core changes
that the previous attempt needed. Below is the design approach I'm
working out. I HAVE SOME QUESTIONS AT THE END. Please chime in with
answers; otherwise I'll take my best guess. Also, please let me know
if you have any questions/input.
MULTITENANCY FEATURE BRANCH
I'm going to tag the current feature branch in the
https://autofac.googlecode.com/svn/feature/multitenancy/tag folder and
create a new "trunk" for the branch based on the latest production
trunk. That way it starts out clean with no core changes.
MULTITENANCY REQUIREMENTS DOCUMENT
As a refresher, here's a link to that multitenancy requirements doc
we've got running:
https://docs.google.com/Doc?docid=0AcfBHeNRpGHIZGMzdHFicmZfNGh2N3ByNGNy&hl=en
TENANCYREGISTRY : ICONTAINER
I'll take the TenancyRegistry currently in the
Autofac.Integration.Web.MultiTenant namespace, combine it a little
with the MultiTenantContainerProvider, and implement IContainer on it.
From a pseudocode perspective, I anticipate the usage being something
like this:
var builder = new ContainerBuilder();
// Register application-level defaults.
builder.RegisterType<Foo>().As<IFoo>();
var appContainer = builder.Build();
// Pass a tenant identification strategy and
// the application container to the tenancy registry.
var tenancyRegistry = new TenancyRegistry(
new GetTenantFromEnvironmentStrategy(),
appContainer);
// Add tenant configurations as lambdas on
// ContainerBuilders. You can add any number
// of lambdas for a tenant and the ComponentRegistry
// on the tenant lifetime will either be created
// or updated for you as necessary.
tenancyRegistry.ConfigureTenant(
"tenant1",
b => b.RegisterType<Blah>().As<IBlah>());
tenancyRegistry.ConfigureTenant(
"tenant1",
b => b.RegisterModule<Tenant1Module>());
tenancyRegistry.ConfigureTenant(
"tenant2",
b => b.RegisterModule<Tenant2Module>());
// Resolving components from the TenancyRegistry will
// be done in a multitenant fashion. The current tenant
// will be retrieved from the tenant determination
// strategy and the resolution will happen from that
// tenant's component context - transparently.
tenancyRegistry.Resolve<IBlah>();
// Other operations are also multitenant - creating
// a new lifetime will create the lifetime for
// the current tenant.
var lifetime = tenancyRegistry.BeginLifetimeScope();
I think this will actually work really well and allow multitenancy to
be pretty transparent as far as your application and the rest of
integration is concerned. For example, doing it this way, we don't
need a special ContainerProvider for web integration - pass a
TenancyRegistry to the ContainerProvider as its IContainer and when
the ContainerProvider creates a new request lifetime, that request
lifetime will already be for the specific tenant.
MORE ABOUT THE ICONTAINER IMPLEMENTATION
If I implement IContainer on TenancyRegistry, it means implementing
the following methods:
ILifetimeScope BeginLifetimeScope();
ILifetimeScope BeginLifetimeScope(Action<ContainerBuilder>
configurationAction);
ILifetimeScope BeginLifetimeScope(object tag);
ILifetimeScope BeginLifetimeScope(object tag, Action<ContainerBuilder>
configurationAction);
object Resolve(IComponentRegistry registration, IEnumerable<Parameter>
parameters);
void Dispose();
IDisposer Disposer { get; }
object Tag { get; }
IComponentRegistry ComponentRegistry { get; }
I figure the BeginLifetimeScope methods can be tenant-specific easily
enough, as can Resolve.
Dispose can dispose of the application container and all of the tenant-
specific lifetimes.
I am not sure how exactly to handle Disposer, Tag, or
ComponentRegistry. My initial thought was to make Disposer and Tag
return the application container values, while ComponentRegistry will
be tenant-specific so it behaves consistently with Resolve... but then
that makes Tag and Disposer inconsistent. I ended up deciding that all
the standard properties would be tenant-specific, but it does mean one
of the FxCop design guidelines gets broken - calling a property (e.g.,
"ComponentRegistry") twice in succession could, technically, yield a
different value if the tenant context has changed between calls.
I will probably add a property to TenancyRegistry so you can still
access the application container directly if you need to.
So, now that you have the general plan... questions!
QUESTIONS
1) Is implementing IContainer on TenancyRegistry the way to go?
I think it's a pretty decent solution, but the whole approach here
makes the assumption that it's a good idea. If it's not... time to
start over. Assuming it's good...
2) Should the IContainer implementation be transparently multitenant?
I think it'd be nice and would mean a familiar syntax, but it might
also feel a little "magical" and inexplicit in practice. Assuming it
should be transparent...
3) How should Disposer, Tag, and ComponentRegistry be handled?
For consistency, I think the properties should be multitenant just
like the method calls.
4) What do we do about WCF hosting without Autofac core changes?
I will post a separate thread about this so we don't sidetrack this
one, but I'm pretty sure there are limitations inside WCF that won't
let us do multitenancy without the crazy dynamic proxy solution I came
up with the first time around, and that required some changes to the
AutofacHostFactory. We can technically create a whole separate
multitenant host factory, but then any issues that have to be fixed
would need to be fixed in two places. There's also a need to add
behaviors to hosted services to allow for propagation of tenant ID on
the wire, which you can't do programmatically using the current
factory implementation. Again, I'll start a separate thread on this,
but I will need HELP.