UnitOfWork (NHibernate),一次只有一个活动的 UoW/会话? (需要建议)

发布于 2024-10-24 09:56:43 字数 2728 浏览 4 评论 0原文

我正在使用 NHibernate、DI/IoC 和工作单元模式。

我见过的大多数 UoW 示例都确保同时只能有一个活动的 UoW/会话,例如 这个这个

不幸的是,我还不太明白应该如何处理两个都使用 UoW 的服务,但一个服务调用另一个服务。
举个例子:

一个使用 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
        }
    }
}

...以及另一个使用 UoW 并调用记录器的服务:

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
        }
    }
}

如果我的 UoW 实现确保同时只有一个活动的 UoW(并抛出异常)如果您尝试启动另一个,就像在两个链接的示例中一样),我的代码将在 PlaceOrder 方法的 this.service.WriteLog 行中崩溃:
PlaceOrder 方法已经创建了一个活动的 UoW,并且 WriteLog 方法将尝试打开第二个,因此 UoW 实现将引发异常。

那么,我该怎么办呢?
我提出了两个想法,但在我看来,这两个想法都显得有些“老套”。

  1. 不要在 LoggerService 中启动新的 UoW,而是假设调用代码中已经存在一个活动的 UoW。
    这就是我现在正在做的事情。我刚刚从 LoggerService 中删除了 using (UnitOfWork.Start()) 内容,并确保您无法直接调用 LoggerService,只能从其他服务调用。
    这意味着上面的代码可以工作,但是如果调用代码没有启动 UoW,LoggerService 将崩溃,因为 LoggerService 假定已经存在一个 UoW。

  2. 保留示例代码不变,但更改 UoW.Start() 的实现,如下所示:
    a) 如果没有活动的 UoW,则开始一个新的
    b) 如果已经一个活跃的UoW,则返回这个
    这将使我能够直接从其他服务调用 LoggerService,无论是否已经存在 UoW。
    但我从未在网上的任何示例中看到过这样的内容。

(在这个例子中,只有两个服务。它可能会变得更加复杂,只需考虑一个 PlaceSpecialOrderService 类,它会执行一些特殊的操作,然后调用 PlaceOrderService.PlaceOrder()...)

有什么建议吗?
提前致谢!


编辑:

感谢您迄今为止的回答。

好吧,也许日志记录不是最好的例子。
我明白您关于使用单独的会话进行日志记录的观点,我将看一下并尝试一下。

不管怎样,我仍然需要找到一种方法来使嵌套服务调用工作。
想象一些其他示例而不是日志记录,例如我上面提到的 PlaceSpecialOrderService 示例。

对于建议我在基础设施中的某个地方启动 UoW 的回答者,而不是直接在服务中:
一方面,这也有道理,但另一方面,这显然意味着我无法在一个服务调用中执行两个不同的事务。
我必须考虑这一点,因为我很确定我会在某个地方需要这个(例如:在一个交易中保存订单,然后在第二个交易中做更多的事情,即使失败,订单也不会不会被回滚)。

您是否在应用程序中这样做了(每个服务调用一个 UoW)?
您是否曾经需要在同一个服务呼叫中启动第二个 UoW 的可能性?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

今天小雨转甜 2024-10-31 09:56:44

我认为您已经发现了一个非常特殊的场景,您永远不应该将同一个会话用于业务服务和日志记录服务。 UnitOfWork 是“业务事务”,但日志记录显然不是事务的一部分。如果您的业务逻辑抛出异常,它将回滚您的日志!使用单独的会话进行日志记录(使用单独的连接字符串来限制当前事务中的登记)。

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

二货你真萌 2024-10-31 09:56:44

UOW 的构建和生命周期管理不应该是实现业务逻辑的服务所关心的。相反,您应该设置基础设施来进行 UOW 管理。根据您的应用程序,每个 http 请求或每个 WCF 操作调用或 MessageModule 可以有一个 UOW(想想 NserviceBus)。

大多数 DI 容器已经提供某种支持将实例的生命周期与上述上下文关联起来。

还有关于日志记录 - 在大多数常见情况下,这是一个基础设施问题,并且在订单处理服务旁边提供日志记录服务是一种味道。让 log4net 或 nlog 或任何您喜欢的东西做它们的用途。

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.

你是我的挚爱i 2024-10-31 09:56:44

就我个人而言,我不认为写入订单状态是一个日志问题。对我来说,这是一个业务问题,所以我会将您的订单服务重构为如下所示:

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

            repository.SaveOrderStatus(order,"Order placed")

        }
    }

以及我将用于未处理的执行、身份验证问题等的日志记录服务。

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.

嘿咻 2024-10-31 09:56:44

我想我自己找到了解决方案。

实际上,这是我在问题中提出的解决方案之一:

保留示例代码不变,但更改 UoW.Start() 的实现,如下所示:
a) 如果没有活动的 UoW,则开始一个新的
b) 如果已经有一个活动的 UoW,则返回这个
这将使我能够直接从其他服务调用 LoggerService,无论是否已经存在 UoW。
但我从未在网上的任何示例中看到过这样的事情。

在我在这里提出问题之前,我已经想到了这个想法,但我不确定这是否是一个好的解决方案,因为我在网上发现了很多不同的 UoW 实现,但与我的想法没有任何相似之处。

但我实际上找到了一个实现 - 我阅读了完整的帖子,但我只是以某种方式过度阅读了相关部分:-)
它位于 我在原始版本中发布的第一个链接问题
UoW 实现有一个布尔字段“isRootUnitOfWork”。
构造函数基本上是这样做的(简化的):

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

我认为这是最灵活的解决方案。我可以调用我的一项服务,或者让其中一项服务调用另一项……这一切都有效,无需对 UoW 进行任何特殊操作。

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.

夜无邪 2024-10-31 09:56:44

我建议在服务之外启动和停止您的工作单元。不知道适合 .net 世界的工具,但您应该寻找一些面向 Aspekt 的编程工具。或者手动为每个服务创建一个包装类,该类仅启动工作单元,然后委托给真正的服务,然后关闭工作单元。如果一个服务调用另一个服务,它会使用真正的实现而不是工作单元包装器。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文