在事务结束时发送事件 [英] Sending events at the end of a transaction

查看:48
本文介绍了在事务结束时发送事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 Service 对象的接口,如下所示(为简洁起见进行了简化):

公共接口 ItemService {public Item getItemById(String itemId, int version);public void create(Item item, User user);公共无效更新(项目项目,用户用户);public void delete(Item item, User user);}

ItemService 单个实现,并作为 Spring bean 连接起来.我们项目的 UI 部分以及处理 Ajax 请求的代码使用它来创建和修改数据存储中的 Item 对象.

在幕后,每个方法在被调用时都会发出一系列事件.其他模块接收事件以执行诸如使 Lucene 索引保持最新或向管理员发送消息以让他们知道某些内容已更改之类的事情.每个方法调用构成 Spring 中的单个事务(使用 org.springframework.orm.hibernate3.HibernateTransactionManagerorg.springframework.transaction.interceptor.TransactionProxyFactoryBean).

最近需要在单个事务中组合多个方法调用.有时有多个服务.例如,我们可能想要做这样的事情:

*开始交易*使用 ItemService 获取由用户账单创建的项目对于 Items 中的每个 Item项目上的更新字段使用 LinkService 将项目链接到用户账单使用 ItemService 更新项目*完成交易*

我们通过创建另一个服务来实现这一点,该服务允许您在父服务的单个方法中组合来自服务的调用.让我们称之为ComposingService.ComposingService 与所有其他服务一样,也由 Spring 管理,并且由于事务是可重入的,所以这应该都可以工作..

但是,有一个问题:如果事务中的任何操作失败,导致事务回滚我们不想发送任何事件.

就目前而言,如果事务中途失败,在事务回滚之前,ItemService 会发送一半的事件,这意味着某些模块会收到一堆事件发生了.

我们正试图找到一些方法来解决这个问题,但我们一直想不出任何优雅的方法.到目前为止,我们想出的最好的东西是这样的(而且很丑):

公共接口 ItemService {public Item getItemById(String itemId, int version);public void create(Item item, User user, List events);public void update(Item item, User user, List events);public void delete(Item item, User user, List events);}

在这个修改后的 ItemService 中,不是立即发送事件,而是将它们添加到作为参数传入的事件列表中.该列表由ComposingService 维护,一旦对ItemService 和其他服务的所有调用成功退出,事件就会由ComposingService 发送.

显然,问题在于我们以丑陋的方式更改了 ItemService 上的契约.调用类,即使它们是服务,也不应该担心管理事件.但我一直无法想出办法解决这个问题,因此提出了这个问题.

这看起来像以前可能已经解决的那种问题.有没有人遇到过类似的问题,如果有,您是如何解决的?

解决方案

总结您的问题:您正在寻找一种交易安全的方式来发送消息.

选项 1:JMS

事务安全的消息传递正是 JMS 的用途.Spring 中也有很好的 JMS 集成,请参阅 JMS 章节 在 Spring 文档中.

这将确保当且仅当事务提交时发送消息.它还有助于处理这些事件的侦听器中的错误.

与您当前设置的不同之处在于这些事件将被异步处理:您的服务将在这些事件被处理之前返回.(JMS 将确保它们最终得到处理,它可以配置为多次尝试以及如何处理错误,......).根据您的需求,这可能是好事也可能是坏事.

选项 2:交易同步

或者,如果 JMS 对您的情况来说太重量,您可以使用事务同步:发送事件时,而不是直接发送它,使用 Spring 的 TransactionSynchronizationManager.registerSynchronization,并在您的 TransactionSynchronizationafterCommit() 中发送消息.您可以为每个要发送的事件添加一个新的同步,或者添加一个同步并通过使用 TransactionSynchronizationManager.bindResource 将包含该列表的对象绑定到事务来跟踪要发送的事件..>

我建议不要为此尝试使用您自己的 ThreadLocal,因为这在某些情况下会出错;例如,如果在您的交易中,您将开始一个新交易 (RequiresNew).

与您当前设置的差异:

  • 如果在这种情况下在处理事件时抛出异常,您的服务将抛出异常,但更改已经在数据库中提交.
  • 如果您的侦听器之一也写入数据库,则必须在新事务中这样做.

或者,您可以使用 beforeCommit 而不是 afterCommit,但是即使实际提交到数据库,您的事件也会被处理(发送邮件,...)后来失败了.

与使用 JMS 相比,这种方法不够健壮(事务性较低),但更轻巧、更易于设置,而且通常足够.

I have an interface for a Service object that looks something like the following (simplified for brevity):

public interface ItemService {

  public Item getItemById(String itemId, int version);

  public void create(Item item, User user);

  public void update(Item item, User user);

  public void delete(Item item, User user);

}

ItemService a single implementation, and is wired up as a Spring bean. It gets used by the UI portion of our project, and by the code that handles Ajax requests, to create and modify Item objects in our data store.

Under the hood each method sends out a series of Events when it gets called. The Events are received by other modules to do things like keep Lucene indexes up to date, or to send messages to administrators to let them know something has changed. Each method call constitutes a single transaction in Spring (using org.springframework.orm.hibernate3.HibernateTransactionManager and org.springframework.transaction.interceptor.TransactionProxyFactoryBean).

Recently there has been a need to compose a number of method calls together in a single transaction. Sometimes with more than one Service. For example, we might want to do something like:

*Begin transaction*
Get Items created by User Bill using ItemService
 for each Item in Items 
   Update field on Item
   Link Item to User Bill with LinkService 
   Update Item using ItemService
*Finish transaction* 

We've done this by creating another Service which allows you to compose calls from Services in a single method in the parent service. Lets call it ComposingService. ComposingService, as with all the others, is also managed by Spring, and as transactions are reentrant, this should all work..

However, there is a problem: if any of those operations within the transaction fail, causing the transaction to roll back we do not want to send out any Events whatsoever.

As it stands, if the transaction fails halfway through, half the Events will be sent by ItemService before the transaction rolls back, meaning that some modules will receive a bunch of Events for things that haven't happened.

We are trying to find some way of fixing this, but we've been unable to think of anything elegant. The best we've come up with so far, is something like this (and it's ugly):

public interface ItemService {

  public Item getItemById(String itemId, int version);

  public void create(Item item, User user, List<Event> events);

  public void update(Item item, User user, List<Event> events);

  public void delete(Item item, User user, List<Event> events);

}

In this modified ItemService, instead of the Events being sent right away, they are added to the List of Events passed in as an argument. The list is maintained by ComposingService, and the Events get sent by ComposingService once all the calls to ItemService and other services have exited successfully.

Obviously, the problem is that we've changed the contract on ItemService in an ugly manner. Calling classes, even if they are services, should not have to worry about managing Events. But I've been unable to think of a way around this, hence this question.

This looks like the kind of problem that has probably been solved before. Has anyone had a problem that looks similar, and if so, how did you resolve it?

解决方案

Summarizing your question: You're looking for a transactionally-safe way to send messages.

Option 1: JMS

Transactionally safe messaging is exactly what JMS is for. There's also good JMS integration in Spring, See the JMS chapter in the Spring documentation.

That will make sure that the messages are sent if and only if the transaction is committed. It also helps for dealing with errors in listeners for these events.

A difference with your current setup is that these events will be handled asynchronously: your service will return before these events have been handled. (JMS will make sure that they are processed eventually, it can be configured to try multiple times and how to deal with errors,...). Depending on your needs, that may be a good or a bad thing.

Option 2: Transaction synchronization

Alternatively, if JMS is too heavy-weight for your case, you could use transaction synchronization: When sending an event, instead of sending it directly use Spring's TransactionSynchronizationManager.registerSynchronization, and send the message in the afterCommit() of your TransactionSynchronization. You could either add a new synchronization per event to be sent, or add one synchronization and keep track of which events to be sent by binding an object containing that list to the transaction using TransactionSynchronizationManager.bindResource.

I would advise against trying to use your own ThreadLocal for this, because that would go wrong in some cases; for example if inside your transaction you would start a new transaction (RequiresNew).

Differences with your current setup:

  • if an exception is thrown in the handling of the events in this case, your service will throw the exception, but the changes will already have been committed in the database.
  • if one of your listeners also writes into the database, it will have to do so in a new transaction.

Alternatively, you can use beforeCommit instead of afterCommit, but then your events will be handled (mails sent,...) even if the actual commit to the database later fails.

This is less robust (less transactional), than using JMS, but lighter and easier to set up, and usually good enough.

这篇关于在事务结束时发送事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆