12

假设您有一个可以跨多个对象执行某些操作的业务逻辑方法。也许您想调用一个彩票号码挑选网络服务,为从列表中选择的每个人调用一次。在 Java 中,代码可能如下所示:

Set<Person> selectedPeople = ... // fetch list of people
for ( Person person : selectedPeople ) {
    String lotteryNumber = callLotteryNumberWebService( person );
    // ...
}

请注意,彩票号码 Web 服务可能会产生副作用,例如记录此人已请求彩票号码(可能会向他们的帐户收费),因此即使 Web 服务调用对一个人失败,也可能对其他人成功。此信息(彩票号码)将需要反馈到更高级别(视图)。

如果这是发生单个操作的情况,则业务逻辑方法可以返回单个值(例如,彩票号码)或抛出带有任何失败细节的异常。但是对于批量操作,有可能一些操作成功而一些操作失败。

这似乎是许多应用程序中都会出现的一种问题,应该有一种干净的方法来处理它。那么,将这种类型的信息从业务逻辑层反馈到应用程序中的另一层(如视图)的最佳方式是什么,最好是以可重用于不同类型的数据和操作的通用方式?

4

8 回答 8

10

这个问题强调了正确使用异常处理、事务和想法工作流“补偿”之间的重要区别,这是提问者在正确说明时试图达到的目的:

这似乎是许多应用程序中都会出现的一种问题,应该有一种干净的方法来处理它。

这是一个常见问题,首先是您当前尝试的事务方法的一些背景知识:

数据交易最初是根据复式记账法建模的——一个贷方和相应的借方必须一起记录或根本不记录。随着事务变得比这更大,它们变得越来越难以正确实施,并且更难处理失败。当您开始跨系统边界执行单个事务的想法时,您很可能会错误地处理它。它可以完成,但需要复杂且必然具有更高延迟的事务协调器。在一定规模上,交易是错误的心态,补偿开始变得更有意义。

这是您返回并查看业务实际操作的地方。一笔大笔交易很可能不是商务人士所看到的。通常他们会看到一个步骤已完成,并且根据后续结果,可能需要采取不同的行动来弥补。这就是工作流和补偿的想法出现的地方。 这是对这些概念的一个介绍

例如,如果您从亚马逊订购一本书,他们可能不会在您的购物车中“锁定”记录,甚至在订单确认时使用严格的交易来确定该书是否仍有库存。他们无论如何都会把它卖给你,并在可能的时候发货。如果他们在几周内没有设法将其进货,他们可能会向您发送一封电子邮件,告诉您他们正在努力满足您的需求,您可以继续等待他们进货,或者您可以取消您的订单。这称为补偿,在许多实际业务流程中是必需的。

最后,这一切都没有什么特别之处。预计这会发生并使用正常的控制流。您不应该在这里使用您的语言的异常处理功能(关于何时抛出异常的良好规则)。您也不应该依赖工具特定(WCF?)机制来查看或处理服务实现中发生的异常。传达故障应该是您的数据合同(故障合同)的正常部分。

不幸的是,通过“干净的方式来处理它”没有设置可以神奇地处理它的标志,您必须继续分解问题并处理所有产生的部分。希望这些概念能够将您与其他人在处理此问题时所做的事情联系起来。

概括:

  • 您的问题已经超出了事务的概念-> 研究工作流补偿。

祝你好运 -

于 2009-09-01T18:56:14.490 回答
1

我更愿意返回标识对象的定制错误对象的集合,这些对象受错误、错误代码和描述的影响。这样可以尝试纠正错误或进一步向用户显示错误。

于 2009-08-27T10:06:22.113 回答
1

理想情况下,对 Web 服务的调用应该是这样的。

List<Person> selectedPeople = ... //fetch list of people
callLotteryNumberWebService(selectedPeople.ToArray );

为每个人进行 Web 服务调用的成本很高。在服务器端,您需要遍历列表并执行操作。服务器端代码可以抛出 2 个异常: BulkOperationFailedException - 如果由于 db down 或配置文件丢失而出现致命错误。无法进行进一步处理。BulkOperationException - 这包含与一个人有关的异常数组。您可以保留一些 id 以唯一地引用每个对象。您的代码将如下所示:

List<Person> selectedPeople = ... // fetch list of people 

try{
    callLotteryNumberWebService(selectedPeople.ToArray);
}catch  (BulkOperationFailedException e) {
    SOP("Some config file missing/db down.No person records processed")
}catch(BulkOperationException e)  {
    UserDefinedExceptions us =  e.getExceptions()
    foreach(exception ein us)   {
        // read unique id to find which person object failed
    }
}

construct msg based on which personobject succeeded and which failed

当没有抛出异常时,认为操作成功。您可以为失败使用自定义错误代码,而不是使用 UserDefined 异常。在服务器端构造 BulkOperationException 很棘手。其次,您需要将服务器端抛出的错误分类为 BulkOperationFailedException 和 BulkOperationException。这就是我在我的一个项目中的处理方式

于 2009-09-07T08:43:30.487 回答
1

如果您从这些方面考虑,我认为您确实在过度使用异常!

返回意味着失败的值而不是抛出异常是完全可以的。通常情况会更好。当您无法在您所处的抽象级别恢复时,最好使用异常,但您不应该将它们用作控制流的主要手段,否则您的程序将变得非常难以阅读。

Web 服务不返回异常,它返回返回代码和消息。我将存储一些有用的表示形式来呈现返回的信息,并返回视图的列表或将要查看的任何内容。

于 2009-08-31T17:16:08.323 回答
1

如果我理解,您会遇到一些请求可以通过而一些请求可能会失败的情况。我不确定您希望错误在哪里返回,但您可以拥有以下之一(或变体,或组合):

  • 错误列表和受影响的域对象。基域对象或具有可持久 ID 的东西可能对重用很有用。例如,引用域对象的错误集合。
  • 您可以将(AOP,DI)注入到 Person 对象中某种错误对象/消息。例如如果(人。错误){...}
  • 您可以将 Person 集合包装成带有标题、正文、错误信息的消息
  • 您的所有域对象都可以包含可通过接口访问的错误集合;或 Person 支持 IHasErrors 接口。您可以使其通用并使用支持警告和验证以及各种方式的基本错误对象。

如果您在一个真正的多层(而不是分层)系统中,那么您可能拥有一个基于消息的架构,可以轻松适应某种通用错误/警告/验证机制。SOA/Ajax 系统适合这一点。

如果您有一些具体问题,很乐意深入研究。

于 2009-09-07T06:00:42.220 回答
0

我会研究DTO来完成这类任务。DTO 还可以包括有关持久化是否成功以及其他类型的“元数据”的信息。

于 2009-08-31T13:43:48.897 回答
0

另一种方法,特别是对于高吞吐量系统,将使用基于队列的设计,其中处理实体将对对象执行操作,然后根据结果将对象放入不同的队列中以供其他实体进行额外处理,然后继续. 这将减少由于需要额外处理而出现的瓶颈,例如处理缺货产品的订单

于 2009-09-02T09:27:28.423 回答
0

我可能会Map<Person,Future<String>>从我的getLotteryNumbers<Collection<Person>>服务返回一个类型的结果映射。

然后我会遍历地图并使用Future.get()来获取彩票号码或抛出的异常。

在我的一些服务中,我喜欢将所有调用实现为单个项目调用,然后在我的服务中使用逻辑来将它们进行批处理并将它们作为一个组进行处理。批处理是使用一个LinkedBlockingQueue和一个轮询线程实现的。

在这种情况下,我返回一个Future<Thing>等待批处理结果可用的CountdownLatch.

在实践中查看 Java 并发,了解这些组件如何协同工作http://jcip.net/

于 2009-08-31T10:34:15.020 回答