I'm using NHibernate, DI/IoC and the Unit of Work pattern.
Most UoW examples I've seen make sure that there can be only one active UoW/session at the same time, for example this one and this one.
Unfortunately, I don't quite understand yet how I should handle two services which both use UoW, but one calls the other.
Take this example:
A logger which uses an UoW:
public class LoggerService : ILoggerService
{
private ILoggerRepository repo;
public LoggerService(ILoggerRepository Repo)
{
this.repo = Repo;
}
public void WriteLog(string message)
{
using (UnitOfWork.Start())
{
// write log
}
}
}
...and another service which uses an UoW as well AND calls the logger:
public class PlaceOrderService : IPlaceOrderService
{
private IOrderRepository repo;
private ILoggerService service;
public PlaceOrderService(IOrderRepository Repo, ILoggerService Service)
{
this.repo = Repo;
this.service = Service;
}
public int PlaceOrder(int orderNumber)
{
using (UnitOfWork.Start())
{
// do stuff
this.service.WriteLog("Order placed!"); // will throw exception!!
// do more stuff
}
}
}
If my UoW implementation makes sure that there is only one active UoW at the same time (and throws an exception if you try to start another one, like in both linked examples), my code will crash in the this.service.WriteLog line in the PlaceOrder method:
There will already be an active UoW created by the PlaceOrder method, and the WriteLog method will attempt to open a second one, so the UoW implementation will throw an exception because of this.
So, what can I do about this?
I came up with two ideas, but both look somehow "hacky" to me.
Don't start a new UoW in the LoggerService, instead assume there is already an active one in the calling code.
That's what I'm doing at the moment. I just removed the using (UnitOfWork.Start()) stuff from the LoggerService and made sure that you can't directly call the LoggerService, only from other services.
This means that the code above will work, but the LoggerService will crash if the calling code doesn't start an UoW because the LoggerService assumes that one already exists.
Leave the example code as it is, but change the implementation of UoW.Start() like this:
a) if there is no active UoW, start a new one
b) if there already is an active UoW, return this one
This would enable me to call the LoggerService directly AND from other services, no matter if there is already an UoW or not.
But I've never seen anything like this in any example on the net.
(And in this example, there are only two services. It could get much more complicated, just think of a PlaceSpecialOrderService class which does some special stuff and then calls PlaceOrderService.PlaceOrder()...)
Any advices?
Thanks in advance!
EDIT:
Thank you for the answers so far.
Okay, maybe logging was not the best example.
I see your point concerning using a separate session for logging, and I will give this a look and try it.
Anyway, I still need to find a way to make nested service calls work.
Imagine some other example instead of logging, like the PlaceSpecialOrderService example I mentioned above.
To the answerers suggesting that I start my UoW somewhere in the infrastructure, and not directly in the services:
On one hand, that makes sense too, but on the other hand it would obviously mean that I can't do two different transactions in one service call.
I'll have to think about that, because I'm quite sure that I'll need this somewhere (like: save an order in one transaction, then do more stuff in a second transaction, and even if that fails, the order doesn't get rolled back).
Did you do it this way (one UoW per service call) in your apps?
Didn't you ever need the possibility to start a second UoW in the same service call?
I think you have found a very special scenario when you should never ever use the same Session for business service and logging service. UnitOfWork is "business transaction" but logging is obviously not part of the transaction. If your business logic throws exception it will rollback your logs!!! Use separate session for logging (with separate connection string which restricts enlisting in current transaction).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With