Sunday, March 30, 2008

Castle Dynamic Proxy

This blog has been silent for a while, and for good reason. Besides experiencing symptoms of writer's block,  I've also been playing around with a lot of new stuff (at least new to me), and no, it's not LINQ or any other stuff from Redmond this time.

One of the things I've been playing with is Dynamic Proxy and it's successor Dynamic Proxy 2 from the magnificent Castle stack.

Now, what are the problems that this library tries to solve? It tries to overcome the CLR's heavyweight proxy capabilities (extending MarshalByRefObject or ContextBoundObject) by providing lightweight proxies that can be generated on the fly for interfaces and classes (virtual properties and methods only). If this last sentence blows you away, don't worry. It's not that hard to understand.

I'm a big proponent of Persistence Ignorant (PI) domain models. Practically, this means that the assembly that contains my domain entities (also called POCO's) does not have any reference to other assemblies, except for the System assembly of the .NET Framework. The domain layer is the core of your application and it should be treated as such. You can compare it with the kernel of an operating system. It is the beating heart of ..., you get the picture.

This of course is easier said than done. You have to take some actions in order to accomplish PI domain models. Castle Dynamic Proxy can be used to simplify some of these actions and overcome some of the issues you come across while trying to achieve Persistence Ignorance. It's time for an example.

Let's say you have an entity in your domain model called Artist. An Artist can have a number of Records in his Repertoire.

public class Artist { private String _name; private Repertoire _repertoire; public String Name { get { return _name; } set { _name = value; } } public Repertoire Repertoire { get { return _repertoire; } set { _repertoire = value; } } } public class Repertoire { private readonly Artist _artist; private readonly List<Record> _records = new List<Record>(); public Repertoire(Artist artist) { _artist = artist; } public Artist Artist { get { return _artist; } } public virtual IEnumerable<Record> Records { get { return _records; } } } public class Record { private readonly String _title; public Record(String title) { _title = title; } public String Title { get { return _title; } } }

This is the simplest domain I could come up with. In some scenarios, I want to use Artist information without retrieving the records from it's Repertoire. Other scenario's require that the records of the Repertoire are available.

One solution would be to change the Records property of the Repertoire class to directly retrieve the records from a database gateway when this property is accessed. This violates the PI principle explained earlier by polluting the domain with infrastructure concerns.

Let's see how Castle Dynamic Proxy can help us out here. First we implement an interceptor (implements the IInterceptor interface from the Castle.Core.Interceptor namespace).

public class LazyLoadInterceptor : IInterceptor { private List<Record> _loadedRecords; public void Intercept(IInvocation invocation) { Repertoire repertoire = (Repertoire)invocation.Proxy; if(null == _loadedRecords) { // Some data access _loadedRecords = new List<Record>(); _loadedRecords.Add(new Record("Die Sonne")); _loadedRecords.Add(new Record("Mutter")); _loadedRecords.Add(new Record("Mein Teil")); Console.WriteLine("Repertoire of {0}", repertoire.Artist.Name); } invocation.ReturnValue = _loadedRecords; } }

We need to make sure that the Records property of the Repertoire class is marked as virtual. Calls to the Records property are intercepted. If the records are not yet loaded, then they are retrieved from the database. This interceptor lives in the infrastructure or data access part of your application. 

Next, we use this interceptor to intercept calls to the Records property.

public class ArtistRepository : IArtistRepository { public Artist FindBy(String name) { // Some data access Artist artist = new Artist(); artist.Name = name; ProxyGenerator proxyGenerator = new ProxyGenerator(); LazyLoadInterceptor lazyLoadInterceptor = new LazyLoadInterceptor(); IInterceptor[] interceptors = new IInterceptor[] { lazyLoadInterceptor }; Repertoire repertoire = (Repertoire)proxyGenerator .CreateClassProxy(typeof(Repertoire), interceptors, artist); artist.Repertoire = repertoire; return artist; } }

Notice that the Repertoire class doesn't need to have a default constructor. It is possible to specify constructor arguments.  You typically want to move this proxy setup code to some kind of a mapper class, but for the simplicity of this example this will do.

The following code results in the records to be retrieved only once, although the collection is accessed twice:

Artist artist = artistRepository.FindBy("Rammstein"); Console.WriteLine("Name: {0}", artist.Name); foreach(Record record in artist.Repertoire.Records) { Console.WriteLine("Record: {0}", record.Title); } foreach(Record record in artist.Repertoire.Records) { Console.WriteLine("Record: {0}", record.Title); }

Lazy loading is just one example where using proxies can be helpful. When you have a decent ORM at your disposal, you don't need to write this kind of code for lazy loading. In fact, NHibernate makes extensive use of Castle Dynamic Proxy to support lazy loading for you.

Other scenarios include cross-cutting concerns like dirty tracking, logging, tracing, etc. Interceptors also integrate nicely with Castle Windsor (what would you expect :-) ). This nicely written blog post explains how easy this is.

This example makes use of Dynamic Proxy 2. The first version of Castle Dynamic Proxy also supports mixins, which is not yet available for Dynamic Proxy 2 included with the Castle RC3 release. According to Hamilton Verissimo, Dynamic Proxy 2 is significantly more performant than it's first version.

If you, my dear reader want to know more about the use of dynamic proxies in your applications, than these articles and blog posts will provide you with some more information:

Till next time.

2 comments:

Rajiv Mathew said...

very well explained...
Nice links provided...
Besides blogging kindly do post such articles at codeproject.com ....
There are many companies where all sites except codeproject.com is blocked...
I am no marketing man but this will really help your article to reach a wider audience

Jan Van Ryswyck said...

I'll try to put it up there as soon as I find the time. Anyway, many thanks for the kind words.