我正在使用NHibernate,DI / IoC和工作单元模式。
我见过的大多数UoW示例都确保同时只能有一个有效的UoW /会话,例如this one和this one。
不幸的是,我还不太明白我应该如何处理两个使用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(并且如果你尝试启动另一个,则抛出异常,就像在两个链接的示例中一样),我的代码将在{{1}中崩溃PlaceOrder方法中的行:
PlaceOrder方法已经创建了一个活动的UoW,WriteLog方法将尝试打开第二个,因此UoW实现会因此而抛出异常。
那么,我该怎么办呢? 我提出了两个想法,但两个看起来都“哈哈”给我。
不要在LoggerService中启动新的UoW,而是假设调用代码中已存在活动的UoW。
这就是我现在正在做的事情。我刚从LoggerService中删除了this.service.WriteLog
内容,并确保不能直接从其他服务中调用LoggerService。
这意味着上面的代码可以工作,但如果调用代码没有启动UoW,LoggerService将崩溃,因为LoggerService假定一个已经存在。
保持示例代码不变,但改变UoW.Start()的实现,如下所示:
a)如果没有活动的UoW,请启动一个新的
b)如果已经 一个活跃的UoW,则返回此一个
这将使我能够直接调用LoggerService并从其他服务调用,无论是否已经存在UoW。
但我在网上的任何例子中都没见过这样的东西。
(在这个例子中,只有两个服务。它可能会变得更加复杂,只需要想到一个 using (UnitOfWork.Start())
类,它会做一些特殊的事情,然后调用 PlaceSpecialOrderService
...)
有什么建议吗? 提前谢谢!
修改
感谢您到目前为止的答案。
好吧,也许日志记录不是最好的例子 我看到了关于使用单独的会话进行日志记录的观点,我将对此进行一下并尝试一下。
无论如何,我仍然需要找到一种方法来使嵌套服务调用工作 想象一下其他一些例子,而不是记录,就像我上面提到的PlaceSpecialOrderService示例一样。
对于回答者,建议我在基础设施的某个地方启动我的UoW,而不是直接在服务中:
一方面,这也是有道理的,但另一方面,它显然意味着我不能在一次服务电话中做两个不同的交易。
我将不得不考虑这一点,因为我很确定我会在某个地方需要它(例如:在一个事务中保存订单,然后在第二个事务中执行更多操作,即使失败,订单也不会不会回滚。
您是否以这种方式(在每个服务电话中使用一个UoW)在您的应用中执行此操作? 难道你不需要在同一个服务电话中启动第二个UoW吗?
答案 0 :(得分:3)
我认为您已经找到了一个非常特殊的场景,您永远不应该使用相同的Session进行业务服务和日志记录服务。 UnitOfWork是“业务事务”,但日志记录显然不是事务的一部分。如果您的业务逻辑抛出异常,它将回滚您的日志!使用单独的会话进行日志记录(使用单独的连接字符串限制当前事务中的登记)。
答案 1 :(得分:2)
UOW的构建和生命周期管理不应该是实现业务逻辑的服务的关注点。相反,您应该设置您的基础架构来执行UOW管理。根据您的应用程序,您可以为每个http请求或每个WCF操作调用提供UOW,或者使用MessageModule(想想NserviceBus)。
大多数DI容器已经具有某种支持,可以将实例的生命周期与上述上下文相关联。
关于日志记录 - 在最常见的情况下,这是一个基础设施问题,并且在订单处理服务旁边有一个日志服务是一种气味。让log4net或者nlog或者你喜欢的任何东西都可以做它们的构建。
答案 2 :(得分:2)
就个人而言,我认为编写订单状态并不是一个日志问题。对我而言,这是一个商业问题,所以我会将您的订单服务重构为以下内容:
public int PlaceOrder(int orderNumber)
{
using (UnitOfWork.Start())
{
repository.SaveOrder(order)
repository.SaveOrderStatus(order,"Order placed")
}
}
我将用于未处理的exec,身份验证问题等的日志记录服务。
答案 3 :(得分:1)
我想我自己找到了解决方案。
实际上,这是我提出的解决方案之一:
保持示例代码不变,但改变UoW.Start()的实现,如下所示:
a)如果没有活动的UoW,请启动一个新的 b)如果已经存在活动的UoW,则返回此一个 这将使我能够直接调用LoggerService并从其他服务调用,无论是否已经存在UoW。
但我在网上的任何例子中都没见过这样的东西。
在我问这个问题之前我已经提出了这个想法,但我不确定这是否是一个很好的解决方案,因为我在网上发现了大量不同的UoW实现,但与我的想法没什么相似。
但我实际上找到了这个的实现 - 我读了完整的帖子,但我只是以某种方式过度阅读相关部分:-)
它在the first link that I posted in my original question
UoW的实现有一个bool字段“isRootUnitOfWork”
构造函数基本上这样做(简化):
if (HasActiveSession)
{
isRootUnitOfWork = false;
session = GetActiveSession();
}
else
{
isRootUnitOfWork = true;
session = CreateSession();
}
我认为这是最灵活的解决方案。我可以拨打我的一个服务,或者打电话给另一个......这一切都有效,而不会对UoW做任何特别的伎俩。
答案 4 :(得分:0)
我建议在服务之外启动和停止UnitOfWork。不知道.net世界的适当工具,但你应该寻找一些面向Aspekt的编程工具。或者手动创建一个包装类foreach服务,该服务仅启动工作单元,然后委托给realy服务,然后关闭工作单元。如果一个服务调用另一个服务它使用真正的实现而不是工作包装器的单元。