开发者

UnitOfWork (NHibernate), only one active UoW/session at a time? (need advice)

开发者 https://www.devze.com 2023-02-18 17:16 出处:网络
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.

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.

  1. 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.

  2. 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).


The construction and life time management for the UOW should not be the concern of the service that implements your business logic. Instead you should setup your infrastructure to do the UOW management. Depending on your application you can have a UOW per http request or per WCF operation call, or MessageModule ( think NserviceBus).

Most DI containers already have some kind of support for associating the lifetime of an instance with the above contexts.

Also regarding to logging - in most common cases it's an infrastructure concern and having a logging service next to an order processing service is a smell. Let log4net or nlog or whatever you prefer do what they are built to do.


Personally, I don't think that writing the status of the order is a logging concern. To me it is a business concern, so I would refactor your order service to something like this:

public int PlaceOrder(int orderNumber)
    {
        using (UnitOfWork.Start())
        {           
            repository.SaveOrder(order)

            repository.SaveOrderStatus(order,"Order placed")

        }
    }

And the logging service I would use for unhandled execptions, authentication issues etc.


I think I found a solution myself.

Actually, it was one of my proposed solutions in my question:

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.

I already came up with this idea before I asked my question here, but I was not sure if this is a good solution, because I found loads of different UoW implementations on the net, but nothing similar to my idea.

But I actually found an implementation of this - I read the complete post but I just somehow over-read the relevant part :-)
It was in the first link that I posted in my original question.
The UoW implementation there has a bool field "isRootUnitOfWork".
The constructor basically does this (simplified):

if (HasActiveSession)
{
    isRootUnitOfWork = false;
    session = GetActiveSession();
}
else
{
    isRootUnitOfWork = true;
    session = CreateSession();
}

I think that's the most flexible solution. I can call a single one of my services, or have one call the other...and it all works, without doing any special tricks with the UoW.


I recommend starting and stopping your UnitOfWork outside the Services. Don't know the apropriate tools for the .net world, but you should look for some Aspekt Oriented Programming Tools. Or manually create a wrapper class foreach service which only starts the unit of work then delegates to the realy service and afterwards closes the unit of work. If one service calls another it uses the real implementation not the unit of Work wrapper.

0

精彩评论

暂无评论...
验证码 换一张
取 消