Ayende @ Rahien

It's a girl

Rhino Commons, Repository and Unit Of Work

Rhino Commons is a great collection of stuff that I gathered along the way, but never documented. There is a sample application (https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/trunk/SampleApplications/Exesto), but not much more.  I want to spend a few minutes talking about the way the data access part of it works. This is post about how it works, not how to make it work (in other words, very little code here).

Before I start, I want to mentions that Rhino Commons is (highly) opinionated software, unlike Castle or NHibernate. It is a separate place where I take what Castle & NHibernate gives me, add a mix of my own best practices and let it run.

The data access part in Rhino Commons revolves around the Unit Of Work, Unit Of Work Factory and the Unit Of Work Application. The main abstraction that Rhino Commons provides in terms on data access is the IRepository<T> interface, which is accessible via the static Repository<T> accessor class. The Unit Of Work class and the IRepository<T> works together to simplify data access code in most cases.

It started as a set of wrapper methods and sort of grew from there. I find that this is very useful for intent revealing code when used in conjunction with the NHibernate Query Generator. Another useful tidbit is that it also serve to handle the differences between NHibernate & Active Record models fairly transparently this allows me to work against the NHibernate model (me likey) without having to define any XML (me likey more!) :-)

image

As you can see, the IRepository<T> is serving as a way to query NHibernate very easily. In a DDD environment I'll probably inherit from it and add additional methods to it, like CustomersThatTheUserIsAllowedToView(User user), etc.

Another thing that the use of the Repository gives me is the ability to do cross cutting concerns with queries, things like With.Caching, With.Transaction (although I prefer the Automatic Transaction Management Facility more), etc. It is important to note that the default Flush Mode for the session using this approach is Commit only, so Transactions play an important role here.

After the IRepository<T>, we have the Unit Or Work itself, which is basically responsible to manage the NHibernate session / Active Record Scope. Unit Of Work Factory is used to initialize NHibernate / Active Record and to create new Units Of Work.

image

Note that while you can create Unit Of Work using the IUnitOfWorkFactory, you query it using the Repository. The idea is that most of the time, you are only dealing with the Repository, and dealing with the Unit Of Work is left to a higher level code. I am a big believer in context being king, and this is one case of many where I am using this approach.

If the management of the Unit Of Work is relegated to a higher level code, who is responsible for managing it?

That is the job of the UnitOfWorkApplication, which handles the session per request pattern. This is an HttpApplication rather than the usual Http Module since HttpApplication.Application_start is guaranteed to run once and only once, while Http Modules can be created/disposed based on load.

image

Notice that the UnitOfWorkApplication is also responsible to create the container, after which it is available to the rest of the application.

Comments

Sheraz
06/08/2007 06:47 AM by
Sheraz

Rahien,

Seems like the application link is broken
Robert Mircea
06/08/2007 07:38 AM by
Robert Mircea

Is it possible to make initialization of IUnitOfWorkFactory more XML config friendly? At the moment, you are using boo which seems to have a natural syntax for this kind of stuff, but configuring IUnitOfWorkFactory from XML is almost impossible due to this kind of declaration (in C#):

Assembly[] assemblies = new Assembly[]

        {

            Assembly.Load("MyAssembly1"),

            Assembly.Load("MyAssembly2")

        };

Hashtable props = new Hashtable();

        props.Add("assemblies", assemblies);

        Container.AddComponent("active_record_unit_of_work", typeof(IUnitOfWorkFactory), typeof(ActiveRecordUnitOfWorkFactory));

        Container.Resolve<IUnitOfWorkFactory>(props);

Maybe creating an overloaded constructor in which you can specify assemblies names as an array of string will allow to use Castle's XML config initialization.

Regards,

Robert

Ayende Rahien
06/08/2007 08:19 AM by
Ayende Rahien

@Sheraz,

There is a refferer checks that blocks you, try copy/paste the link

Ayende Rahien
06/08/2007 08:20 AM by
Ayende Rahien

Robert,

I am accepting patches :-)

Christian KLAT
06/08/2007 08:34 AM by
Christian KLAT

I've been trying to migrate your code to make it work in a WCF Scenario instead of the HttpApplication one. I've implemented the DI for WCF.

I've been trying to Manage the UnitOfWork creation in a custom ServiceHostFactory implementation but it doesn't work.

Here are my questions :) :

1) Where do you think i should Initialize the Castle Container in a WCF Scenario.

2) What would be the best place to handle the WCF Service Lifecycle and be able to insure that the UnitOfWork and NHibernate initialization are handled properly.

I have the source code of what i've done so far but i would really mess up the post... If you're interested i can email it to you.

Regards,

Chrsitian

Ayende Rahien
06/08/2007 08:35 AM by
Ayende Rahien

I have Windsor WCF integration kit that I am preparing to release.

If you can wait a few days, I'll have it out.

It also handles the Unit Of Work stuff.

Quicoli
06/09/2007 09:16 PM by
Quicoli

Really thank you for explanation.

Chris Bilson
06/10/2007 07:42 AM by
Chris Bilson

Thanks for the explanation and the code Ayende. Awesome stuff.

One question: do have an example somewhere of making specialized repositories? Do you still use Repository? Do you have a really slick way you do this?

Please, if you have time and energy, post more like this - i.e. more about stuff in rhino-commons, how it works, etc. I've been surprised at how easy it is to figure out but I'd like to hear/read more, since the sample applications in rhino-commons don't even scratch the surface.

Thanks again!

Chris Bilson
06/10/2007 07:50 AM by
Chris Bilson

Sorry...I see you have more posts on Repository and DDD from the past few days I haven't caught up with yet. Sorry for asking a question you have already answered!

Tim Scott
06/10/2007 04:11 PM by
Tim Scott

Wow, this is awesome. Thanks for sharing this. I am just starting a project that is in progress, and I hope they're not too far along for me to introduce this.

After a quick look at the code I have a question. I notice a couple of that you pass both DetachedCriteria and Order[], for example:

ICollection FindAll(DetachedCriteria criteria, params Order[] orders)

Why have the Orders[] parameter? Detached criteria itself encapsulates orders. This could cause maintainability issues. For example, a developer sees this...

Repository.FindAdd(detCriteria, new Order(new Order(Order.Desc("FullName"))

...and assumes that the result would be sorted descending by full name. However, only after some painful sleuthing, he might discover that detCriteria has already has some orders. He would discover that the creator of the criteria and the repository are thus coupled.

I know, I know, you're accepting patches, but I guess this would be breaking change.

Ayende Rahien
06/10/2007 04:23 PM by
Ayende Rahien

Syntax:

Repository.FindAll(

Where.Person.EmployedSince >= new DateTime(2000,1,1),

OrderBy.Person.Name.EmployedSince.Desc

)

Darrell Young
06/12/2007 01:39 AM by
Darrell Young

I really like the Repository - I have question about extending IRepository and adding custom methods to it - how would you do this using Rhino.Commons?

Thanks

Rory Fitzpatrick
06/28/2007 10:52 AM by
Rory Fitzpatrick

I'm going to ask another beginner question here - where does...

Where.Person.EmployedSince >= new DateTime(2000,1,1),

OrderBy.Person.Name.EmployedSince.Desc

...come from?

Ayende Rahien
06/28/2007 10:56 AM by
Ayende Rahien

Rory, that is something that comes from NHibernate Query Generator

Comments have been closed on this topic.