Multitenancy, Take 2

101 views
Skip to first unread message

tillig

unread,
Jul 20, 2010, 2:21:17 PM7/20/10
to Autofac
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.

tillig

unread,
Jul 20, 2010, 5:53:51 PM7/20/10
to Autofac
For completeness, the thread on WCF multitenancy is here:
http://groups.google.com/group/autofac/browse_thread/thread/65430a14f895096c

On Jul 20, 11:21 am, tillig <travis.il...@gmail.com> wrote:
> 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 thehttps://autofac.googlecode.com/svn/feature/multitenancy/tagfolder 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=0AcfBHeNRpGHIZGMzdHFicmZfNGh2N3ByNG...

tillig

unread,
Jul 21, 2010, 2:18:56 PM7/21/10
to Autofac
Another question:

When a tenant lifetime scope is created, it's created from the
application scope. In a web scenario, the request scope comes from the
tenant scope, like this:

* Application/Root Scope
* Tenant 1 Scope
* Tenant 1 Request Scope
* Tenant 2 Scope
* Tenant 1 Request Scope

The request scopes are tagged so you can scope things to the request.

SHOULD TENANT SCOPES BE TAGGED ALSO?

I'm thinking we could tag them with the tenant ID (or a wrapped tenant
ID so there's no conflicts between "tenant1" the string and "tenant1"
the tenant ID) - it might prove helpful in lifetime management. I
don't think it would hurt anything, but it might help in some cases.

-T

On Jul 20, 2:53 pm, tillig <travis.il...@gmail.com> wrote:
> For completeness, the thread on WCF multitenancy is here:http://groups.google.com/group/autofac/browse_thread/thread/65430a14f...

tillig

unread,
Jul 21, 2010, 4:20:26 PM7/21/10
to Autofac
I ran into a bit of an odd behavior issue while implementing this,
maybe someone can offer a suggestion.

I am trying to figure out how to use standard syntax to register a
SingleInstance dependency for a tenant.

The usage is something like this:

var strategy = new TenantIdentificationStrategy();
var builder = new ContainerBuilder();
builder.RegisterType<Dependency1>().As<IDependency>().SingleInstance();
var mtc = new MultitenantContainer(strategy, builder.Build());
mtc.ConfigureTenant("tenant1", b =>
b.RegisterType<Dependency2>().As<IDependency>().SingleInstance());
mtc.ConfigureTenant("tenant2", b =>
b.RegisterType<Dependency2>().As<IDependency>().SingleInstance());

var appDependency = mtc.ApplicationContainer.Resolve<IDependency>();
var t1dependency =
mtc.GetTenantScope("tenant1").Resolve<IDependency>();
var t2dependency =
mtc.GetTenantScope("tenant2").Resolve<IDependency>();

// appDependency will be of type Dependency2.
// t1dependency and t2dependency will be the same object.

Unfortunately, SingleInstance appears to trickle down to the
application container, which isn't what I was expecting. If I register
the tenant-specific overrides as InstancePerLifetimeScope, I can get
the effect I'm looking for, but that's not very intuitive syntax-wise.

Is there a way to make SingleInstance obey LifetimeScopes? Do I need
to tag the scopes with a special tag or something?

-T

tillig

unread,
Jul 21, 2010, 4:48:50 PM7/21/10
to Autofac
I think I figured out the issue: In registering components for a
tenant, I was getting the tenant lifetime scope and then running

var builder = new ContainerBuilder();
configurationLambda(builder);
builder.Update(tenantScope.ComponentRegistry);

That doesn't work. Doing it where you register everything at once and
then do a BeginLifetimeScope(configLambda) for each tenant allows
things like SingleIsntance to work correctly...

...but it means you HAVE to register everything for a tenant up front
in a single call. And once you get a tenant lifetime scope, it's
totally immutable - no ContainerBuilder.Update available.

So, uh... looks like I'll have to make some sort of lambda aggregator
or something so you can do something like this:

var registry = new TenantRegistry();
registry.Register(b => b.RegisterType<Foo>().As<IFoo>());
// ...other logic, then...
register.Register(b => b.RegisterType<Bar>().As<IBar>());
multitenantContainer.ConfigureTenant("tenant1", registry);

Once a tenant has been configured, they can't be REconfigured, so if
you call it twice in a row, you'll get an exception:

multitenantContainer.ConfigureTenant("tenant1",
b=>b.RegisterType<Foo>().As<IFoo>());
// This will cause an exception:
multitenantContainer.ConfigureTenant("tenant1",
b=>b.RegisterType<Bar>().As<IBar>());

By the same token, if you resolve a value for a tenant and then try to
configure that tenant, you'll get the same exception because the
tenant's lifetime has already been built:
multitenantContainer.GetTenantScope("tenant1").Resolve<IFoo>();
// This will cause an exception:
multitenantContainer.ConfigureTenant("tenant1",
b=>b.RegisterType<Bar>().As<IBar>());

It's pretty static, but I think that's the limitation we will end up
requiring if we want to support common lifetime syntax.

Anyone see a better way?
-T

tillig

unread,
Jul 22, 2010, 10:36:58 AM7/22/10
to Autofac
I solved this problem by creating a class
ConfigurationActionBuilder : List<Action<ContainerBuilder>>

It has a Build() method on it that combines all of the registered
delegates into a single action.

You can then do things like:

var cab = new ConfigurationActionBuilder();
cab.Add(b => b.RegisterType<Foo>().As<IFoo>());
// ... do some other app logic, then ...
cab.Add(b => b.RegisterModule<MyModule>());
// ... and finally, configure the tenant all at once:
multitenantContainer.ConfigureTenant("tenant1", cab.Build());

There's a not-completely-formed idea in my head around configuration
where there may be a need for something like a
Dictionary<object, ConfigurationActionBuilder>
Where you can query a configuration system for the set of tenant
configurations, then add to them/modify them programmatically, and,
finally, finish tenant registration. I'm not there yet, though, so
I've not done anything with it.

Also, I've gotten things in the branch far enough along that there is
a sample console application showing use of the multitenancy feature.

I'm going to start working on web/MVC integration next, though I don't
think there will be much additional needed for that.

After that... I'm up to WCF and I'm going to need some help!
http://groups.google.com/group/autofac/browse_thread/thread/65430a14f895096c

-T
> ...
>
> read more »

tillig

unread,
Jul 22, 2010, 4:49:20 PM7/22/10
to Autofac
Got multitenancy integrated into an example MVC application with no
issue. Really nothing extra/special to support that model - not even a
custom ContainerProvider. Just set up the ContainerProvider with a
MultitenantContainer instead of a standard container and it all just
works. You can see this all in action in the feature branch:
https://autofac.googlecode.com/svn/feature/multitenancy/trunk

That brings me up to WCF, which I'll look at, but am sort of stuck on.
Would love any input folks can offer.

There's also configuration to look at, so if I get stuck on WCF, I'll
probably look at configuration.

-T

On Jul 22, 7:36 am, tillig <travis.il...@gmail.com> wrote:
> I solved this problem by creating a class
> ConfigurationActionBuilder : List<Action<ContainerBuilder>>
>
> It has a Build() method on it that combines all of the registered
> delegates into a single action.
>
> You can then do things like:
>
> var cab = new ConfigurationActionBuilder();
> cab.Add(b => b.RegisterType<Foo>().As<IFoo>());
> // ... do some other app logic, then ...
> cab.Add(b => b.RegisterModule<MyModule>());
> // ... and finally, configure the tenant all at once:
> multitenantContainer.ConfigureTenant("tenant1", cab.Build());
>
> There's a not-completely-formed idea in my head around configuration
> where there may be a need for something like a
> Dictionary<object, ConfigurationActionBuilder>
> Where you can query a configuration system for the set of tenant
> configurations, then add to them/modify them programmatically, and,
> finally, finish tenant registration. I'm not there yet, though, so
> I've not done anything with it.
>
> Also, I've gotten things in the branch far enough along that there is
> a sample console application showing use of the multitenancy feature.
>
> I'm going to start working on web/MVC integration next, though I don't
> think there will be much additional needed for that.
>
> After that... I'm up to WCF and I'm going to need some help!http://groups.google.com/group/autofac/browse_thread/thread/65430a14f...
> ...
>
> read more »

Nicholas Blumhardt

unread,
Jul 23, 2010, 8:43:49 PM7/23/10
to aut...@googlegroups.com
Thanks for the updates and all of your hard work Travis, it's looking good! :)

1) Seems like a good start to me.

2) I think your assumption is right - if it is going to support IContainer then transparency is a worthy goal

3) Disposer and Tag should definitely be per-tenant; particularly with Disposer, if we get to the stage where removing/releasing a per-tenant "root" container is supported then we'll need the singleton instances for that tenant to be attached to the appropriate disposer.

4) It has been a long time coming, I know, but I finally have some time this weekend to jump into this with you, so I'll follow up ASAP.

Know we still have bucketloads of issues to resolve but I think we're on the right track.

Cheers,
Nick


--
You received this message because you are subscribed to the Google Groups "Autofac" group.
To post to this group, send email to aut...@googlegroups.com.
To unsubscribe from this group, send email to autofac+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/autofac?hl=en.


tillig

unread,
Jul 27, 2010, 3:13:58 PM7/27/10
to Autofac
AutofacContrib.Multitenant is merged into the trunk now and has a
custom WCF host factory to address some of the challenges here. It's
not too far from the core version, but implementers should know that
you do have to use the custom host.

I'll add some wiki docs on usage, etc. next and put up a separate
announcement with the details.
-T

On Jul 23, 5:43 pm, Nicholas Blumhardt <nicholas.blumha...@gmail.com>
wrote:
> Thanks for the updates and all of your hard work Travis, it's looking good!
> :)
>
> 1) Seems like a good start to me.
>
> 2) I think your assumption is right - if it is going to support IContainer
> then transparency is a worthy goal
>
> 3) Disposer and Tag should definitely be per-tenant; particularly with
> Disposer, if we get to the stage where removing/releasing a per-tenant
> "root" container is supported then we'll need the singleton instances for
> that tenant to be attached to the appropriate disposer.
>
> 4) It has been a long time coming, I know, but I finally have some time this
> weekend to jump into this with you, so I'll follow up ASAP.
>
> Know we still have bucketloads of issues to resolve but I think we're on the
> right track.
>
> Cheers,
> Nick
>
> On 21 July 2010 04:21, tillig <travis.il...@gmail.com> wrote:
>
> > 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/tagfolder 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=0AcfBHeNRpGHIZGMzdHFicmZfNGh2N3ByNG...
> > autofac+u...@googlegroups.com<autofac%2Bunsu...@googlegroups.com>
> > .

Nicholas Blumhardt

unread,
Jul 28, 2010, 9:16:33 AM7/28/10
to aut...@googlegroups.com
Fantastic news!

Just a thought - if you were to use only PerCall services, with the existing WCF integration, then although the services themselves cannot vary in type, it is still fine for their dependencies to vary. This would give all of the multitenant functionality except around the edges where interfacing with WCF. Do you think that is a workable scenario? I can imagine that in many instances the per-tenant customisations are in policies and finer-grained services than the WCF contract implementations themselves.

So IMyWcfService might be implemented all the time by MyWcfService, but the IFooPolicy implementation that MyWcfService depends on could be chosen based on the tenant info gleaned from the OperationContext. What do you think?

NICE documentation too, by the way.

Cheers,
Nick

To unsubscribe from this group, send email to autofac+u...@googlegroups.com.

tillig

unread,
Jul 28, 2010, 12:28:57 PM7/28/10
to Autofac
I think that you are right in the PerCall service thing - if people
specifically designed their services in such a way that they only
allowed people to change the dependencies of services and not the
service implementations proper, the existing WCF hosting would
probably work. That said, I can see scenarios where really all you
want to share with a tenant developer is the service contract and not
any of the internals because if you change how things work or find a
different scenario requiring a change in dependency interface, you
don't really know what you'll break for the tenant. On the other hand,
the service contract is already public and [should have] strong
versioning around it, so it's a little safer and more controlled to
share.

But, again, you're right - if people chose to share interfaces at a
lower level, they could potentially use the existing WCF host factory
without issue. I've not personally tried it, but it sounds right.

And thanks on the docs - I'm not quite done, but I'm hoping they're
fairly robust. If I get a chance, I'll try and give a similar
treatment to some of the other docs in the spirit of the Great Wiki
Drive of '10.

-T

On Jul 28, 6:16 am, Nicholas Blumhardt <nicholas.blumha...@gmail.com>
wrote:
> Fantastic news!
>
> Just a thought - if you were to use only PerCall services, with the existing
> WCF integration, then although the services themselves cannot vary in type,
> it is still fine for their dependencies to vary. This would give all of the
> multitenant functionality except around the edges where interfacing with
> WCF. Do you think that is a workable scenario? I can imagine that in many
> instances the per-tenant customisations are in policies and finer-grained
> services than the WCF contract implementations themselves.
>
> So IMyWcfService might be implemented all the time by MyWcfService, but the
> IFooPolicy implementation that MyWcfService depends on could be chosen based
> on the tenant info gleaned from the OperationContext. What do you think?
>
> NICE documentation too, by the way.
>
> Cheers,
> Nick
>
> > <autofac%2Bunsu...@googlegroups.com<autofac%252Buns...@googlegroups.com>

tillig

unread,
Jul 28, 2010, 3:54:47 PM7/28/10
to Autofac
Just to see if I could, I tried using the standard AutofacHostFactory
stuff and, while it's still probably possible, I had trouble in
getting the tenant ID propagating across from client to server because
the standard host factory doesn't allow additional configuration of
generated hosts (the multitenant version does - specifically for this
reason). If you want to add a behavior to propagate the tenant ID,
it'll have to be done through XML config, which means implementation
of a new BehaviorExtensionElement that allows configuration of the
tenant ID type so it can be properly parsed... not insurmountable, but
not done, either. I tried a really quick 15-minute hack to get one
working and it didn't just fall together - I started getting contract
mismatch errors, meaning the behavior wasn't getting properly applied.

Again, not insurmountable and doesn't rule the possibility out, just
not currently fall-down easy or supported immediately out of the box.

I'm thinking the XML config element might be a good idea in any case,
so maybe I'll try working that out next if I get a chance. (I've sorta
eaten up all my time on getting the current stuff done, so that might
not happen immediately, but it's still a decent idea.)

-T
> ...
>
> read more »

Nicholas Blumhardt

unread,
Aug 1, 2010, 12:06:48 AM8/1/10
to aut...@googlegroups.com
Okay -thanks for doing the research!

It sounds like some degree of WCF extensibility is going to be needed. We can return to this anyway.

BTW, demo'ed AutofacContrib.Multitenant a couple of nights ago: it went very well - minimal effort to get going, got the job done, and no surprises in the process :)

Cheers,
Nick

--
You received this message because you are subscribed to the Google Groups "Autofac" group.
To post to this group, send email to aut...@googlegroups.com.
To unsubscribe from this group, send email to autofac+u...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages