AOP With Windsor: Adding Caching to IRepository<T> 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)));
}
}
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());
}
}
This technique is a powerful one, and I do believe that I will use it more in the future.
Comments
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
Precisely!
I am not using anything else (except for the AR stuff, but that is for another post).
do you think that i can implement historic entities with this? (some entities would save historic data and some not).
Carlos,
Can you explain what you mean in more details?
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?
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?
"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.
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
I thought something similar... but with version information in a separate table. thanks for your comments.
Comment preview