在多层架构中执行批量操作时处理错误和反馈

发布于 2024-08-02 14:36:08 字数 608 浏览 2 评论 0 原文

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

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

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

如果这是发生单个操作的情况,则业务逻辑方法可以返回单个值(例如,彩票号码)或抛出包含失败的任何详细信息的异常。但对于批量操作,可能会出现少数操作成功、少数操作失败的情况。

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

Let's say you have a business logic method that can perform some operation across a number of objects. Maybe you want to call a lottery number picking web service, once for each person selected from a list. In Java, the code might look something like this:

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

Note that the lottery number web service may have side-effects, like recording that the person has requested a lottery number (perhaps charging their account), so even if the web service call fails for one person, it may have succeeded for others. This information (the lottery numbers) will need to be fed back to some higher level (the view).

If this were a case where a single operation took place, the business logic method could return a single value (say, the lottery number) or throw an exception with any details of the failure. But for bulk operations, it would be possible for a few of the operations to succeed and a few to fail.

This seems like a type of problem that would occur in many applications and there should be a clean way to handle it. So, what is the best way to feed this type of information back from the business logic layer to another layer in an application (like a view), preferably in a generic way that can be reused for different types of data and operations?

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

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

发布评论

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

评论(8

十雾 2024-08-09 14:36:08

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

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

这是一个常见问题,首先介绍一下您当前尝试的交易方法的背景:

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

您可以在此处返回并查看该业务的实际业务。单笔大笔交易很可能不是商界人士所看待的方式。通常他们会看到一个步骤已完成,并且根据后续结果,可能需要采取不同的补偿措施。这就是工作流程和补偿的概念的由来。这里是对这些概念的介绍

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

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

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

摘要:

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

祝你好运 -

This question highlights important differences between appropriate uses of exception handling, transactions, and the idea workflow "compensation" which is what the asker is trying to get at, when correctly stating:

This seems like a type of problem that would occur in many applications and there should be a clean way to handle it.

It is a common problem, first some background on the transactional approach you are currently attempting:

Data transactions were originally modeled after double-entry accounting -- a single credit and a corresponding debit had to be recorded together or not at all. As transactions get bigger than this they become increasingly problematic to implement correctly, and harder to deal with a failure. As you start to carry the idea of a single transaction across system boundaries, you are most likely approaching it wrong. It can be done, but requires complex and necessarily higher-latency transaction coordinators. At a certain scale transactions are the wrong mindset, and compensation starts to make a lot more sense.

This is where you go back and look at what the business actually does. A single large transaction is most likely not the way the business people see it. Usually they see a step completed, and depending on subsequent results, different actions to compensate may be needed. This is where the idea of a workflow and compensation comes in. Here's one introduction to those concepts

For example if you order a book from Amazon, they probably don't "lock" the record while it is in your shopping cart, or even use strict transactions to determine if the book is still in-stock when the order is confirmed. They will sell it to you anyways, and ship it when they can. If they haven't managed to get it in-stock within a few weeks they will probably send you an email telling you that they are trying to meet your needs, and you can keep waiting for them to get it in-stock, or you can cancel your order. This is called compensating, and is necessary in many real-world business processes.

Finally, there is nothing exceptional about any of this. Expect that this can happen and use normal control flow. You should not use your language's exception handling features here (good rules for when to throw an exception). Nor should you rely on tool specific (WCF?) mechanisms for seeing or handling exceptions that happen within the service implementation. Communicating faults should be a normal part of your data contract (fault contracts).

Unfortunately, by "clean way to handle it" there is no flag to set that will magically take care of it, you have to continue to decompose the problem and deal with all the resulting pieces. Hopefully these concepts will connect you with what other people have done when dealing with this issue.

Summary:

  • Your problem has outgrown the concept of a transaction --> look into workflow compensation.

Good luck -

一杆小烟枪 2024-08-09 14:36:08

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

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

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

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

If I understand, you have a situation where some requests can pass and some can fail. I'm not sure where you want the error to come back but you could have one of the following (or a variant, or a combination):

  • A list of errors and the domain objects affected. A base domain object or something with a persistable ID might be useful for re-use. E.g. a collection of errors referring to domain objects.
  • You could inject (AOP, DI) into the Person object some kind of error object/message. E.g. if (person. Errors){...}
  • You could wrap the Person collection into a message with header, body, error information
  • All your domain objects could include an error collection accessible via an interface; or Person supports the IHasErrors interface. You could make this generic and use a base Error object supporting warnings and validation and all manner of things.

If you are in a genuine multi-tiered (rather than layered) system then you may have a message based architecture that could easily accommodate some kind of generic error/warning/validation mechanism. SOA/Ajax systems lend themselves to this.

Happy to delve a little deeper if you have some specific questions.

初心 2024-08-09 14:36:08

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

I would prefer to return a collection of custom-made error objects identifying the object, which is effected by the error, error code and description. This way the errors can be tried to remedied or displayed further to the user.

菊凝晚露 2024-08-09 14:36:08

如果您从这些角度思考,我认为您确实过度使用了异常!

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

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

I think you really are overusing exceptions if you are thinking in these terms!

It is perfectly ok to return values that mean failure, rather than throwing an exception. Often it is better. Exceptions are best used when you can't recover at the level of abstraction you are at, but you should not be using them as the main means of control flow or your programs will get very hard to read.

The web service does not return exceptions, it returns return codes and messages. I would store some useful representation that presents the information returned, and return the list of those for the view or whatever is going to be looking at it.

冰雪梦之恋 2024-08-09 14:36:08

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

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

为每个人调用网络服务的成本很高。在服务器端,您需要迭代列表并执行操作。
服务器端代码可以抛出 2 个异常:
BulkOperationFailedException - 如果由于数据库关闭或配置文件丢失而出现致命错误。无法进一步处理。
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

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

Ideally, the call to your web service should be like this.

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

Making a web service call for each person is costly. At the server end, you need to iterate over the list and perform the operation.
The server side code can throw 2 exceptions :
BulkOperationFailedException - if there is a fatal error due to db down or config file missing. Further processing not possible.
BulkOperationException - this contains an array of exceptions pertaining to a person. You can persist some id to uniquely refer to each object.
Your code will be like this:

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

Operation is deemed to be succesful when no exceptions are thrown. You can have custom error codes for failures instead of using UserDefined exceptions.
Constructing the BulkOperationException in the server-side is tricky. Second you need to categorise errors thrown from server side into BulkOperationFailedException and BulkOperationException. This was how i had handled in one of my projects

情泪▽动烟 2024-08-09 14:36:08

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

I'd look into DTOs for this kind of task. The DTO could also include information on whether the persisting was succesful or not and other kinds of "metadata".

醉生梦死 2024-08-09 14:36:08

我可能会从我的 getLotteryNumbers> 服务返回 Map> 类型的结果映射。

然后,我将遍历地图并使用 Future.get() 获取彩票号码或引发的异常。

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

在这种情况下,我返回一个 Future,它使用 CountdownLatch 等待批处理结果可用。

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

I'd probably return a result map of type Map<Person,Future<String>> from my getLotteryNumbers<Collection<Person>> service.

I'd then iterate through the map and use the Future.get() to get either the lottery number or the exception thrown.

In some of my services I like to implement all of the calls as single item calls and then have logic in my service to batch them up and process them as a group. The batching is implemented using a LinkedBlockingQueue and a polling thread.

In this scenario I return a Future<Thing> which waits for the batch results to be available using a CountdownLatch.

Take a look at Java Concurrency in practice to see how these components can all work together http://jcip.net/

盛装女皇 2024-08-09 14:36:08

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

Another way, especially for high throughput systems, would be to use a design based on queues where a processing entity would perform operations on an object and then based on the results put the object in different queues for additional processing by other entities and then move on. This would reduce bottlenecks that would arise due to additional processing that would be required e.g. processing of order for products out of stock

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