Wednesday, March 14, 2012

Castle Windsor - component override: the problem

I am a big fun of the Inversion of Control pattern and of the Dependency Injection one, I love them, I have to admit that I use their impersonation (an Inversion of Control toolkit) even for talk samples…I am really addicted Smile

The power of the “IoC approach” is much more increased when we deal with “products” (emphasis on the word product vs. project) especially when we deal with product extensibility or much more when we deal with a product that can be customized replacing some default/built-in behaviors to accomplish a specific customer need.

A scenario, a common scenario

We sell a product and as every product out there we offers to the end user a bunch of features. We can imagine (for simplicity) that every single feature is represented by a component that the application depends on using the classical dependency injection pattern. Let’s think about a real product.

Informer

Recently I’ve worked on a really interesting product (codenamed: Informer) that basically is a mail server.

Side note: if you want to deeply understand the underlying architecture and all the choices done come to the first Guisa event.

Informer has a really wide extensible architecture (the driver for that at the Guisa event Winking smile) and one of the most important extension point is the security engine that guarantees that every request to the server is authenticated and authorized.

The following is an excerpt of the code where you can see how the security service is used:

class MailboxService : IMailboxService
{
	readonly ISecurityService securityService;
		
	public MailboxService( ISecurityService securityService /* other dependencies */ )
	{
		this.securityService = securityService;
	}
 
	[PrincipalPermission( SecurityAction.Demand, Authenticated = true, Role = InformerBuiltinRoles.Users )]
	public dto.PagedResults<dto.Conversations.Conversation> GetConversations( dto.ConversationsRequest conversationsRequest )
	{
		var mailboxUniqueIdentifier = this.parser.GetMailboxIdentifier( conversationsRequest.Mailbox );
		this.securityService.EnsureIsAuthorized( SecurityOperations.Mailbox.Read, mailboxUniqueIdentifier );
                /* other parts of the system */ 
	}
}

Since we are using, at the WCF level, the membership API we can leverage all the benefits of the CAS declarative security (the principal permission attribute) and instead of using the CAS imperative security we introduced a much more fine grained security engine based on policies, roles, actions and resources.

The problem

Informer comes with a default built-in implementation of the ISecurityService, implementation based on RavenDB Authorization Bundle and custom Membership and Role provider (the whole product is based on RavenDB and NServiceBus); one of the customers has a requirement to integrate the whole security infrastructure with its own on premise Active Directory.

Nitpickers corner 1: why not using by default the built-in CAS security? simple, too simple…the role based security does not cover the product requirements.

Nitpickers corner 2: why not using by default a claim based approach? good point, something new to learn and not enough time compared to the date of the first release. Now we have a modular security engine that let us introduce WIF and oAuth without any problem.

Now…

If you use an IoC container (in our case Castle Windsor) and if you use an xml based configuration it is really easy to achieve the goal, just change the configuration.

But…

In a huge product, managing the configuration of hundreds of components (mostly using .net generics) using strings and a xml configuration is a real pain, and is a real pain having a mixed configuration system too.

As you may know in my campaign to sustainability I love to find frictionless solutions, in the last few years I realized that the best compromise is to use MEF (Microsoft Extensibility Framework) to glue together the container configuration just writing 3 lines of  code, summarizing what happens is:

  • at application bootstrap an instance of MEF is created;
  • the application bootstrapper requests to import a set of IWindsorInstaller (that are the configuration unit of work of Castle Windsor);
  • MEF resolves all the found installers and the container can be configured;

In order to participate in this play a module/assembly just need to expose to MEF (using an attribute, nothing more) all the installers that the module wants be installed.

Event if this approach drastically simplifies the whole configuration and setup process we still need to face a couple of side effects:

  • we have no control over the MEF composition process so we basically have no control over the order in which the various installers are loaded;
  • the previous effect can be ignored until the requirement is to override a component behavior…

Overriding: the real problem.

When building something extensible one of the key point is to provide extensibility entry points, quite obvious isn’t it? but you do not need to provide any extensions out of the box.

When building something that must provide a replaceable behavior a default implementation of that behavior must be provided as built-in (remember we do not want any configuration, we want a natural and quite transparent conventions based behavior).

Now, typically in order to achieve the goal we introduce an interface to depend on  (the ISecurityService interface) and somewhere we provide a default implementation of that interface (say DefaultSecurityService), we cannot simply remove the assembly that contains the DefaultSecurityService replacing it with a new implementation because doing so we will end up removing all the Default* implementations provided by the system.

So…?

Here is the solution, clean and neat:

container.Register
(
	Component.For<ISecurityService>()
		.ImplementedBy<DefaultSecurityService>()
		.Overridable()
);

This is the registration made with the container in order to register a component that can be override by someone else. In order to override the component registration the only requirement is to register a new component that implements the same contract.

In the next post we’ll see how it works under the hood.

.m

1 comment:

  1. Giusto un update per chi si ritrovasse su questo post, sul castle hanno introdotto (a distanza di qualche mese da questo post) .IsFallback() che facilita tantissimo questo task:

    http://docs.castleproject.org/Default.aspx?Page=Whats-New-In-Windsor-31&NS=Windsor&AspxAutoDetectCookieSupport=1#Fallback_components_4

    Adesso basta dichiarare la registrazione di default come fallback:

    Container.Register(Component.For().ImplementedBy().IsFallback(),
    Component.For().ImplementedBy());
    // ...
    var foo = Container.Resolve(); // will resolve MyBetterFoo

    ReplyDelete