AOP With Windsor: Adding Caching to IRepository based on T's attributes


Okay, this is a fairly wacky scenario, I admit.
I am using IRepository<T> for my data access, and I wanted to be able to specify attributes on the T (that is, on the entity, not the repository) and have the behavior of the class change. For instance, if I have IRepository<OrderStatus>, that is something that changes very rarely, so I can safely cache that. But I want to put [Cacheable] on OrderStatus, not on OrderStatusRepository.  To make matter worse, I never explicitly register IRepository<OrderStatus>, I only register IRepository<T> and let Windsor figure the rest of it out.
I thought that it would be a major hurdle, but it turn out to be fairly easy.

Windsor has the concept of Component Model, basically everything that Windsor knows about a component, and you can plug your own contributers, which can add additional knowledge to Windsor.

/// <summary>
///
Inspect the model and add a caching interceptor if appropriate
/// </summary>
public class CachingInterceptorContributer : IContributeComponentModelConstruction
{
    /// <summary>
    /// Inspect the model and add a caching interceptor if appropriate
    /// </summary>
    public void ProcessModel(IKernel kernel, ComponentModel model)
    {
        bool isRepository = model.Service.IsGenericType &&
            model.Service.GetGenericTypeDefinition() == typeof(IRepository<>);
        if (isRepository == false)
            return;
        Type entityType = model.Service.GetGenericArguments()[0];
        bool cacheable = entityType
              .GetCustomAttributes(typeof(CacheableAttribute),true).Length != 0;

        if(cacheable==false)
            return;
        model.Interceptors.Add(new InterceptorReference(typeof(CachingInterceptor)));
    }

}

This checks that the component is an IRepository<T>, and that the T has [Cacheable] attribute. The real nice thing here is that it will be called when Windsor decides that it needs to create an IRepository<OrderStatus>, thereby giving me the chance to add the interceptor to this (and only this) repository.
The interceptor is very simple, and builds on  already existing code:

/// <summary>
///
Add query caching capabilities
/// </summary>
public class CachingInterceptor : IInterceptor, IOnBehalfAware
{
    private string typeName;
 

    /// <summary>
    /// Intercepts the specified invocation and adds query caching capabilities
    /// </summary>
    /// <param name="invocation">The invocation.</param>
    public void Intercept(IInvocation invocation)
    {
        using(With.QueryCache(typeName))
        {
            invocation.Proceed();
        }
    }
 

    /// <summary>
    /// Sets the intercepted component model.
    /// </summary>
    /// <param name="target">The target.</param>
    public void SetInterceptedComponentModel(ComponentModel target)
    {
        typeName = target.Service.GetGenericArguments()[0].FullName;
    }

}


I am using this just to tell NHibernate that it should cache the query, since NHibernate would already be caching the entities in its second level cache (I might show later how I handled that). Now to register the contributer:


/// <summary>
///
Registers various add-ons to the container
/// </summary>
public class AdditionalFunctionalityFacility : AbstractFacility
{

      /// <summary>
      /// The actual addition of resolvers
      /// </summary>
      protected override void Init()
      {
        Kernel.AddComponent("caching.interceptor", typeof(CachingInterceptor));
        Kernel.ComponentModelBuilder.AddContributor(new CachingInterceptorContributer()); 
      }

}

And that is it. I register a facility, which takes care of configuring the interceptor and the contributer, and I am set. I can now simply put [Cacheable] on an entity, and it will cache that in all levels.
This technique is a powerful one, and I do believe that I will use it more in the future.

Print | posted on Sunday, March 11, 2007 8:21 PM

Feedback


Gravatar

# re: AOP With Windsor: Adding Caching to IRepository<T> based on T's attributes 3/11/2007 9:30 PM Mark Monster

I'm still new to Windsor. But this sounds interesting. Are you using some more custom things in this example beside the code you show? As I reed this, I get the idea that the Cacheble attribute is just a custom attribute with no additional functionality. It just describes and has meaning besides this. Am I correct?

Yours,

Mark Monster


Gravatar

# re: AOP With Windsor: Adding Caching to IRepository<T> based on T's attributes 3/11/2007 9:42 PM Ayende Rahien

Precisely!
I am not using anything else (except for the AR stuff, but that is for another post).


Gravatar

# re: AOP With Windsor: Adding Caching to IRepository<T> based on T's attributes 3/12/2007 8:32 PM carlos

do you think that i can implement historic entities with this? (some entities would save historic data and some not).


Gravatar

# re: AOP With Windsor: Adding Caching to IRepository<T> based on T's attributes 3/12/2007 8:54 PM Ayende Rahien

Carlos,
Can you explain what you mean in more details?


Gravatar

# re: AOP With Windsor: Adding Caching to IRepository<T> based on T's attributes 3/12/2007 9:05 PM carlos

uff I only speak a bit of english, but... what i mean is implement historic at repository level and decorate with an attribute that entities that we want that save historical data. Same way that you become an entity cacheable in this post, we could become an entity "historical". Will be this a better way that implemet historic with nhibertate interceptors?


Gravatar

# re: AOP With Windsor: Adding Caching to IRepository<T> based on T's attributes 3/12/2007 9:33 PM Ayende Rahien

Carlos,
The issue is that I fail to understand what you mean by saying historic entity?
Do you mean a database where you never UPDATE, just INSERT?


Gravatar

# re: AOP With Windsor: Adding Caching to IRepository<T> based on T's attributes 3/12/2007 10:02 PM carlos

"Historic entity" is an entity (a database table) that we need to save information about data changes.An object of a type that is historic can have versions. Martin Fowler call this Temporal Object pattern. For example, we have an entity Contract. If a contract changes, we need to know when, who and what was the changes. I want to be able to query entities in an old state (we can use Nhibernate filters for that). This is offen implemented with NHibernate Interceptors. If you still dont understand my comment or is inappropriate or inapplicable for this post, then forget it. Thanks for your attention.


Gravatar

# re: AOP With Windsor: Adding Caching to IRepository<T> based on T's attributes 3/12/2007 10:34 PM Ayende Rahien

I work a lot with temporal objects, and I certainly thinks that you question is appropriate.
In general, yes, you could use this approach to save the auditing data for the application.
I would recommend the Snapshot Pattern for this as well, if you will search my blog, you'll find several blog posts about the subject:

http://www.ayende.com/Blog/archive/7563.aspx
http://www.ayende.com/Blog/archive/2006/10/23/7153.aspx

I would recommend you to use an NHibernate IInterceptor for this, because NHibernate implements the Unit of Work pattern, and can save objects without you explicitly telling it to


Gravatar

# re: AOP With Windsor: Adding Caching to IRepository<T> based on T's attributes 3/12/2007 11:46 PM carlos

I thought something similar... but with version information in a separate table. thanks for your comments.

Comments have been closed on this topic.