Supporting WCF Multitenancy Without Core Autofac Changes

213 views
Skip to first unread message

tillig

unread,
Jul 20, 2010, 5:52:55 PM7/20/10
to Autofac
I started a thread over here about a new take on multitenancy that
won't require core Autofac changes:
http://groups.google.com/group/autofac/browse_thread/thread/c21616ef0a029ddb

And, as a recap, the requirements for multitenancy:
https://docs.google.com/Doc?docid=0AcfBHeNRpGHIZGMzdHFicmZfNGh2N3ByNGNy&hl=en

I have been trying to work out a way to get past WCF hosting without
making core changes or creating an all new service host implementation
specfically for multitenancy and I'm stumped. I'm hoping people can
help me brainstorm some answers.

Let me walk you through my thought process so you can see why I'm
thinking what I'm thinking.

(And pardon some of my ALL CAPS in places - Google Groups doesn't
support bold and there are some points I wanted to draw attention to.)


HOW WCF HOSTING WORKS (IN GENERAL)

When you're hosting WCF services outside of Autofac, you generally
have a .svc file that has the name of the service in it, like this:

<%@ServiceHost
Service="Microsoft.ServiceModel.Samples.CalculatorService"%>

When IIS picks that up, it looks at the value in the "Service"
parameter like a string, not like a type. Since there's no factory
specified, through a reasonably complex process the string ends up at
System.ServiceModel.Activation.ServiceHostFactory.CreateServiceHost(string,
Uri[]). Inside that CreateServiceHost(string, Uri[]) method, the type
is resolved from the string by iterating through all of the referenced
assemblies and finding the type by name. Once the type is resolved, it
gets passed to
System.ServiceModel.Activation.ServiceHostFactory.CreateServiceHost(Type,
Uri[]).

Nothing really happens in CreateServiceHost(Type, Uri[]) - the one
line of code in that factory method is:

return new ServiceHost(serviceType, baseAddresses);

It's just passing the type and the list of Uris to the constructor of
System.ServiceModel.ServiceHost. This is where you'll see that we have
limitations.

ServiceHost has two public constructors: one takes a service TYPE and
a list of endpoints to listen on; one takes a singleton service
INSTANCE and a list of endpoints to listen on. Both behave roughly
identically. In both constructors, the method
System.ServiceModel.ServiceHostBase.InitializeDescription gets called,
the purpose of which is to figure out what the contract is for the
service implementation. Since a given implementation can technically
implement multiple service contract interfaces, this goes through and
"sanity checks" the whole thing, creating the overall service
contract.

The sticky bit of InitializeDescirption is that, down the stack it
calls
System.ServiceModel.Description.ServiceDescription.GetService(Type)
and passes in the service type (if you didn't provide a singleton
instance to host). It specifically checks and fails if the type it
gets passed is not a class.

Due to this check, SERVICEHOST ONLY SUPPORTS CLASS SERVICE TYPES. You
absolutely can't start a service host without a concrete service
implementation type.

Assuming you get past that check, the ServiceHost starts up and it
listens on the addresses you originally passed in for incoming
requests.

Once you have a ServiceHost running and some incoming requests start
hitting the service, the channel dispatchers start playing in the
picture. Each endpoint the service listens to has a set of various
dispatchers associated with it. (There is a good diagram of this here:
http://msdn.microsoft.com/en-us/library/ms734665.aspx ) The service
host looks at the incoming request, picks a channel dispatcher to
handle it, and the channel dispatcher asks an instance provider for
the instance of the service implementation that should be used to
handle the request.

You know how you can mark your services with a
ServiceBehaviorAttribute that takes an InstanceContextMode value that
says whether the service should have an instance per-call, be a
singleton, or be per-session? It's up to the instance provider to
manage that. The thing is, the service host is expecting the instance
provider to always return the same concrete service type that it was
started with. While technically it should only need a type that
implements the same service contract, I've had poor luck in getting
that to consistently behave properly.

The safest, most consistent way to make this work is to ONLY HOST
CONCRETE INSTANCES THAT SHARE A DERIVATION CHAIN. That is, if you
start the service with the class ServiceImpA, you can swap in
ServiceImpB later... if it derives from ServiceImpA. If it doesn't, it
MIGHT work, but like I said, I've had poor luck getting that to behave
right.

(Nitpicker's corner: I know you can do some fancy stuff if you
manually create your own service host and skip IIS/WAS. The Autofac
integration bits are specifically around IIS/WAS integration, so
that's what we're focusing on here.)


HOW AUTOFAC HOOKS IN

Remember earlier when I mentioned that an instance provider was used
to get the service implementation instance that should be used to
service requests? That's the hook Autofac uses.

The custom AutofacHostFactory overrides
System.ServiceModel.Activation.ServiceHostFactory.CreateServiceHost(string,
Uri[]) method and uses the passed in string to attempt resolution of a
component from the application container. First it tries to get the
component as a named service, assuming that the string is the service
name. This allows you to do something like this:

<%@ServiceHost Service="SomeNamedService"
Factory="Autofac.Integration.Wcf.AutofacServiceHostFactory,
Autofac.Integration.Wcf" %>

If there is no named service registered with that name, it tries to
get a type from that string and then resolve the service from that.

Once it figures out the type of component that the service host should
use as the service implementation, the AutofacHostFactory creates a
ServiceHost and applies a behavior to it that ends up attaching a
special instance provider to the host. When the host asks the instance
provider for an instance of the service implementation, Autofac takes
the exact component registration that was determined by
AutofacHostFactory and resolves the component from the application
container.

It's up to the application container, then, to manage the lifetime of
the service implementation rather than the usual
ServiceBehaviorAttribute and InstanceContextMode settings.


CHALLENGES WITH MULTITENANCY

Now that you know how WCF usually works and what it expects, you may
see a few challenges in getting multitenancy to work:

* HOW DO YOU DETERMINE THE TENANT?
If we use the TenancyRegistry idea that I posted about in the other
thread ( http://groups.google.com/group/autofac/browse_thread/thread/c21616ef0a029ddb
) then it'll be easy enough for the tenant identification strategy to
use something like OperationContext to get the current tenant ID from.
That said, you have to push the tenant ID from the client to the
service somehow... and usually that's done with some sort of service
behavior. The way the AutofacHostFactory works, there's no opportunity
to programmatically add any behaviors to the servcie host. We may be
able to provide some sort of simple behavior people can use to
transport tenant ID in message headers or something, but they couldn't
apply it programmatically. It'd have to be done through configuration.
That's not pretty if you're trying to thin down your config files.


* WHAT CONCRETE SERVICE IMPLEMENTATION CLASS DO YOU USE TO INSTANTIATE
THE SERVICE HOST?
When you first instantiate the service host in the service host
factory, you need to pass it a concrete type so it can determine the
service contract. At the time this is happening, you don't have a
request, so there's no tenant ID or tenant context. How do you
determine what type to use? If you use a type registered in the
application container it means you HAVE to have a default registered
at that level. That's not good because you may have a service that
only one or two tenants have implemented; other tenants don't even
have placeholder code for it. Even if you somehow defer it to the
first request, you probably don't want to instantiate the host with a
tenant-specific implementation type.


* HOW DO WE GET THE TENANT-SPECIFIC COMPONENT REGISTRATION FOR THE
SERVICE IMPLEMENTATION IF IT'S RESOLVED WAY BACK AT THE SERVICE HOST?
If tenants register different implementations for a service in tenant
specific containers but the exact component registration associated
with the service implementation is done at the service host factory
level, the wrong component is going to get resolved. The component
registration resolution needs to happen on a per-call basis once we've
established the tenant context. (Unless I'm misunderstanding how
resolution works when you have a specific component registration
instance.)


* WHAT IF YOU WANT A DIFFERENT STRATEGY FOR RESOLVING SERVICE
COMPONENT TYPES?
Slightly peripheral to multitenancy, but slightly not... right now you
get the option of resolving the service component type as a named
service or as a typed service. What if you want to resolve it using
some other mechanism? At this moment, it means you need to implement a
custom service host. From a multitenant standpoint, given some of the
above challenges, it would potentially be nice to "plug in" a
different component resolution strategy. (I suppose that's just one
possible solution for the problem, but the flexibility would certainly
be nice.)


* HOW DO WE GET SERVICE IMPLEMENTATION TYPES TO SHARE A DERIVATION
CHAIN?
As I mentioned, I've had mixed (mostly poor) luck swapping out service
implementation types from under a service host if the implementations
don't share a derivation chain. Which is to say, if I start the
service host with
public class ServiceImpA : IService
and I use an instance provider to swap the implementation in the
service host to
public class ServiceImpB : IService
then sometimes it works and sometimes it doesn't. In simple cases, it
works. Usually. However, something I found always works is if they
share a derivation chain, like:
public class ServiceImpB : ServiceImpA
That always works.

Since you probably won't be sharing your service implementations with
your tenants - just your contracts - you can't really enforce a
derivation chain.

We may be able to punt on dealing with this until it can be better
quantified. Maybe I just had a bad set of test projects when I was
researching it. It's something to keep in the back of your mind,
though, as something we will potentially have to address.


WHAT I DID BEFORE
I answered most of the above questions by making changes to the
AutofacHostFactory. I added the ability to programmatically add
behaviors to the service host (which allowed you to add your tenant ID
propagation behavior). I added the ability to change component
registration resolution strategies so I could remove the ability to
use named services - you had to use the name of the service interface
type. The reason that was important was so I could create a dynamic
proxy that implemented that service interface type - that answered the
question of what concrete type to start the host with. Finally, I
skipped resolution of the exact component registration at the service
host level and pushed that down to the instance provider so you would
have tenant ID context available.

Unfortunately, that was pretty much a total overhaul of the
AutofacHostFactory. If we want to try doing this without core changes,
it means either:
* We need to implement a whole new multitenant-specific host factory,
OR
* I need you smart folks out there to help me figure out a different
way to address the challenges.


OTHER SOLUTIONS
There are obviously other ways you can do multitenancy without mucking
about with the instance provider. You could...

* Set up a load balancer that inspects the incoming message headers
and passes the incoming messages to different service endpoints based
on the tenant ID in the header. This would imply that each tenant has
their own service endpoint rather than a single endpoint for all
tenants.

* Use multitenant DI on the client to inject a service proxy that's
pointed at a different service endpoint on a per-tenant basis. This,
again, implies that there is a separate endpoint for every tenant.

... and so on. The problem I'm trying to solve here is specifically
with a single endpoint that hosts a multitenant service
implementation.


SO... ACK! HELP!
You have the background, you have the challenges, you have what I did
before... how do we get multitenancy to work in WCF?

Nicholas Blumhardt

unread,
Jul 24, 2010, 9:25:19 AM7/24/10
to aut...@googlegroups.com
Hi Travis,

I've scratched my head over this for a while, no bright ideas unfortunately. Here's the best plan I can come up with, let me know what you think:
  1. Pull the "new" AutofacContrib.Multitenant implementation back into /trunk
  2. Get rid of the MultiTenant stuff under the ASP.NET implementation, so we're working with one code base (grabbing anything useful in the process)
  3. Merge the "old" OperationContext-based multitenant WCF changes into AutofacContrib.Multitenant.Wcf, porting to the IContainer-based tenancy model
A that point the AutofacContrib.Multitenant bits will be in good shape to see if we can get some feedback on the setup, ideally by finding some more early adopters.

The results of this should guide us in how we get the whole thing from contrib -> core (if that turns out to be desirable) as well as the features that are popular, missing, etc. It will also give the feature time to bake while we figure out the best option for WCF hosting.

Will that work for you? I can do any/all of the items listed above as required.

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 25, 2010, 1:13:33 PM7/25/10
to Autofac
This sounds like a decent plan and I'll be happy to do it. One
question, though:

When you say "merge the old OperationContext-based" stuff into the
AutofacContrib.Multitenant library, that implies we have a separate
multitenant-specific AutofacHostFactory. I'm OK with that, I just want
to make sure I understand the recommendation. Can you confirm/clarify?

Thanks!
-T

On Jul 24, 6:25 am, Nicholas Blumhardt <nicholas.blumha...@gmail.com>
wrote:
> Hi Travis,
>
> I've scratched my head over this for a while, no bright ideas unfortunately.
> Here's the best plan I can come up with, let me know what you think:
>
>    1. Pull the "new" AutofacContrib.Multitenant implementation back into
>    /trunk
>    2. Get rid of the MultiTenant stuff under the ASP.NET implementation, so
>    we're working with one code base (grabbing anything useful in the process)
>    3. Merge the "old" OperationContext-based multitenant WCF changes into
>    AutofacContrib.Multitenant.Wcf, porting to the IContainer-based tenancy
>    model
>
> A that point the AutofacContrib.Multitenant bits will be in good shape to
> see if we can get some feedback on the setup, ideally by finding some more
> early adopters.
>
> The results of this should guide us in how we get the whole thing from
> contrib -> core (if that turns out to be desirable) as well as the features
> that are popular, missing, etc. It will also give the feature time to bake
> while we figure out the best option for WCF hosting.
>
> Will that work for you? I can do any/all of the items listed above as
> required.
>
> Cheers,
> Nick
>
> On 21 July 2010 07:52, tillig <travis.il...@gmail.com> wrote:
>
>
>
> > I started a thread over here about a new take on multitenancy that
> > won't require core Autofac changes:
>
> >http://groups.google.com/group/autofac/browse_thread/thread/c21616ef0...
>
> > And, as a recap, the requirements for multitenancy:
>
> >https://docs.google.com/Doc?docid=0AcfBHeNRpGHIZGMzdHFicmZfNGh2N3ByNG...
> >http://msdn.microsoft.com/en-us/library/ms734665.aspx) The service
> >http://groups.google.com/group/autofac/browse_thread/thread/c21616ef0...
> ...
>
> read more »- Hide quoted text -
>
> - Show quoted text -

Nicholas Blumhardt

unread,
Jul 26, 2010, 3:07:31 AM7/26/10
to aut...@googlegroups.com
That's what I was thinking - whether it be a temporary or a permanent measure I'm not sure. On the downside there's some duplication, on the up-side, there aren't often code changes in that area.

Cheers,
Nick


--

tillig

unread,
Jul 26, 2010, 11:36:06 AM7/26/10
to Autofac
Sounds like a plan. I'll work on this today. Thanks for getting back
to me!

On Jul 26, 12:07 am, Nicholas Blumhardt <nicholas.blumha...@gmail.com>
wrote:
> That's what I was thinking - whether it be a temporary or a permanent
> measure I'm not sure. On the downside there's some duplication, on the
> up-side, there aren't often code changes in that area.
>
> Cheers,
> Nick
>
> ...
>
> read more »

tillig

unread,
Jul 26, 2010, 8:13:30 PM7/26/10
to Autofac
I got multitenant WCF integration working with a custom host factory
that is a hybrid of the one from the first multitenant attempt and the
stock version. The hybrid version...
* Uses IContainer rather than IContainerProvider (just like the core
version).
* Uses the same instance context/instance provider/DI behavior
hierarchy as the core version.
* Has a strategy pattern for resolving the service implementation type
(like the first multitenant attempt) - needed for adding dynamic proxy
support.
* Hosts dynamic proxies (like the first multitenant attempt) so we
won't run into trouble when tenant-specific service imps don't share
an inheritance hierarchy.
* Defaults to the strategy that multitenancy requires - you must
specify the service interface type in the .svc file and can't use
named services.
* Has an optional strategy for single-tenant usage (as an example)
that behaves the same as the core host.

The example applications have been updated and everything appears to
be working. With that, I think the first run at multitenancy is ready
to go to trunk.

Tomorrow I'll pull the new contrib library into the trunk (along with
the example apps) and add a wiki page explaining how to use it all.
It's very similar to the existing mechanism so it shouldn't be hard
for people to pick up and run with.

Also, in the feature branch I've got the TestDriven.NET integration
with NUnit running as discussed in the NUnit/Tool folder thread here:
http://groups.google.com/group/autofac/browse_thread/thread/b334831c712d14e0

I can include that in the merge if it sounds like something folks are
interested in. (Right now it's only done in the Contrib section.)

-T
> ...
>
> read more »

tillig

unread,
Jul 27, 2010, 3:12:27 PM7/27/10
to Autofac
AutofacContrib.Multitenant is now merged into the trunk. This includes
the custom host factory as discussed here as well as an example
application showing it in action.

-T
> with NUnit running as discussed in the NUnit/Tool folder thread here:http://groups.google.com/group/autofac/browse_thread/thread/b334831c7...
> ...
>
> read more »
Reply all
Reply to author
Forward
0 new messages