views:

861

answers:

2

I'm trying to get to grips with NHibernate, Fluent NHibernate and Spring.

Following domain-driven design principals, I'm writing a standard tiered web application composed of:

  • a presentation tier (ASP.Net)
  • a business tier, comprising:
    • an application tier (basically a set of methods made visible to UI tier)
    • repository interfaces and domain components (used by the application tier)
  • A persistence tier (basically the implementation of the repository interfaces defined in the business tier)

I would like help determining a way of instantiating an NHibernate ISession in such a way that it can be shared by multiple repositories over the lifetime of a single request to the business tier. Specifically, I would like to:

  • allow the ISession instance and any transaction to be controlled outwith the repository implementation (perhaps by some aspect of the IOC framework, an interceptor?)

  • allow the ISession instance to be available to the repositories in a test-friendly manner (perhaps via injection or trough some shared 'context' abstraction)

  • avoid any unnecessary transactions being created (i.e. when only read-only operations have been executed)

  • allow me to write tests that use SQLLite

  • allow me to use Fluent NHibernate

  • allow the repository implementation to remain ignorant of the host environment. I don't yet know if the businese tier will run in-process with the presentation tier or will be hosted separately under WCF (in IIS), so I don't want to bind my code too closely to a HTTP context (for example).

My first attempt to solve this problem had been using the Registry pattern; storing the ISession instance in a ThreadStatic property. However, subsequent reading has suggested that isn't the best solution (as ASP.Net can switch the thread within the page lifecycle, I believe).

Any thoughts, part solutions, pattern names, pointers to up-to-date samples (NHibernate 2) will be most gratefully received.

+1  A: 

I have not used Spring.NET so I can't comment on that. However, the rest sounds remarkably (or perhaps not so remarkably; we're hardly the first to implement these things ;) similar to my own experience. I too had trouble finding a One True Best Practice so I just read as much as I could and came up with my own interpretation.

In my situation I wanted transaction/session management to be external to the repository as well as keep repository concerns from bubbling up out of them (i.e. the code using the repository should not need to know that it's using NHibernate internally and shouldn't need to know anything about NHibernate session management). In my case it was decided that transactions would be created by default lest developers forget them, so I had to have a read-only escape mechanism. I went with the Unit of Work pattern with the NHibernate ISession instance store inside. Calling code (I also created a DSL interface for the UoW) might look something like:

using (var uow = UoW.Start().ReadOnly().WithHttpContext()
       .InNewScope().WithScopeContext(ScopeContextProvider.For<CRMModel>())
{
    // Repository access
}

In practice, that could be as short as UoW.Start() depending on how much context is already available. The HttpContext part refers to the storage location for the UoW which is, unsurprisingly, the HttpContext in this case. As you mentioned, for a ASP .NET application, HttpContext is the safest place to store things. ScopeContextProvider basically makes sure the right data context is provided for the UoW (ISession instance to the appropriate database/server, other settings). The "ScopeContext" concept also makes it easy to insert a "test" scope context.

Going this route makes the repositories explicitly dependent on the UoW interface. Actually, you might be able to abstract it some but I'm not sure I see the benefit. What I mean is, each repository method retrieves the current UoW instance and then pulls out the ISession object (or simply a SqlConnection for those methods that don't use NHibernate) to run the NHibernate query/operation. This works for me though because it also seems like the ideal time to make sure that the current UoW is not read-only for methods that might need to run CRUD.

Overall, I think this is one approach that solves all your points:

  • Allows session management to be external to the repository
  • ISession context can be mocked or pointed at a context provider for a test environment
  • Avoids unnecessary transactions (well, you'd have to invert what I did and have a .Transactional() call or something)
  • I can't see why you couldn't test with SQLite since that's more of an NHibernate concern
  • I use Fluent NHibernate myself
  • Allows the repository to be ignorant of the host environment (that is, the repository caller controls the UoW storage context)

As for the UoW implementation, I'm partially kicking myself for not looking around more before I started. There's a project called machine.uow which I understand is fairly popular and works well with NHibernate. I haven't played with it much so I can't say if it solves all my requirements as neatly as the one I wrote myself, but it might have saved development time as well.

Perhaps we'll get some comments as to where I went wrong or how to improve things, but I hope this is at least helpful in some way.

For reference, the software stack I'm using is:

  • ASP.NET MVC
  • Fluent NHibernate on top of NHibernate
  • Ninject for dependency injection
Stuart Childs
+1  A: 

What you are describing is supported by the Spring.NET framework almost out of the box. Only for FluentNHibernate you need to add a custom SessionFactory (not a lot of code, look here:Using Fluent NHibernate in Spring.NET) to Spring.NET.

Every repository can use the same ISession, just inject the SessionFactory in your repositories and use Spring.NET's transaction services.

Just try it out, they have pretty thorough documentation imho.

BennyM
Regarding the FluentNhibernate there is also an open jira: http://jira.springframework.org/browse/SPRNET-1232 Additional sources for sandy might be the Documentation: http://www.springframework.net/doc-latest/reference/html/nh-quickstart.htmlFor IntegrationTesting suppport have a look at http://www.springframework.net/doc-latest/reference/html/testing.html#unit-testing .
tobsen