WCF, Unity and NHibernate - First Findings
This blog post is to continue on the one I wrote a few days ago about a new architecture for a WCF project we're trying out.
My colleague Tomas and I sat down the whole day yesterday and dug into the topic and came out with a first rough code architecture that seems to work. Both of us knows WCF pretty well, but we're new to Unity and NHibernate so this baby has to be tested thoroughly :)
First, thanks to Ray Henry, who wrote a couple of nice posts about how to use Unity with WCF. That information was superb. Basically we're using a custom Service Host Factory which we point at from the .svc markup file:
<%@ ServiceHost Language="C#" Debug="true" Service="WcfUnity.TestService"
CodeBehind="TestService.svc.cs" Factory="WcfUnity.UnityService.UnityServiceHostFactory" %>
The UnityServiceHostFactory is where we currently registers the types that is to be injected into our code. Right now we're doing it in code, and we're only working with 2 classes; the NHibernate Repository and the NHibernate Session Manager (which is a singleton):
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var unity = new UnityContainer()
.RegisterType<IRepository, NHibernateRepository>()
.RegisterType<INHibernateSessionManager, NHibernateSessionManager>(new ContainerControlledLifetimeManager());
var host = new UnityServiceHost(serviceType, baseAddresses)
{
Container = unity
};
return host;
}
The Session Manager is responsible for setting up the NHibernate Session Factory which is expensive and you only want to do that once. In short - the factory creates a new WCF service host which creates a service behavior which is called each time before a requested service class is instantiated. To be able to resolve and inject our registered classes, the behavior uses a specific instance provider which does Resolve as part of its GetInstance() method. It's all described in detail by Ray in his post, so go check it out. I've not seen a better example of Unity + WCF out there yet and from what I can see in our, so far limited, testing it works well.
When that plumbing is done (some 4 small classes, that's all), we got a very clean service implementation class with the IRepository injected into it:
public class TestService : ITestService
{
private readonly IRepository _repository;
public TestService(IRepository repository)
{
_repository = repository;
}
// ITestService members goes here...
}
You don't see any trace if a container, just the way it should be. Currently we're looking at our Repository interface and a decent syntax for a Unit Of Work. I'm fond of the lambda style of coding, and I'm playing something like this now:
var response = new GetPersonResponse();
_repository.UnitOfWork(() =>
{
var person = _repository.Find<Person>(request.Id);
ret.Person = person;
});
return response;
Or for a transaction:
var response = new AddPersonResponse();
_repository.TransactionalUnitOfWork(() =>
{
_repository.Save(request.Person);
response.Person = _repository.Find<Person>(request.Person.Id);
});
return response;
The TransactionalUnitOfWork method would do things like:
public void TransactionalUnitOfWork(Action action)
{
_session = sessionManager.GetSession();
var transaction = _session.BeginTransaction();
try
{
action();
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
finally
{
transaction.Dispose();
_session.Close();
}
}
The "problem" is that the UoW shouldn't go into the repository implementation, it's wrong but I'm only trying things out here. I might as well wrap the opening and closing of a NH Session and transaction within an IDisposable and call it my UoW as many others have done before me, but I kind of like this. Have to think about not letting things that should stay inside the repository "leak" out... At this moment we're only playing around with things :)
We also need to encapsulate and inject business rules in a good way as we will host the services at more than one customer with different rules. MEF could be an option actually... more on that later :)