在交易结束时发送事件

时间:2011-08-12 11:19:31

标签: java spring architecture design-patterns

我有一个Service对象的接口,类似于以下内容(简化为简洁):

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单个实现,并作为Spring bean连接。它由我们项目的UI部分以及处理Ajax请求的代码使用,用于在数据存储中创建和修改Item个对象。

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

最近,需要在单个事务中一起组合多个方法调用。有时候有多个服务。例如,我们可能想要做类似的事情:

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

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

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

按照目前的情况,如果事务中途失败,事件将在事务回滚之前由ItemService发送一半,这意味着某些模块将收到一堆未发生事件的事件。

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

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

}

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

显然,问题是我们以丑陋的方式改变了ItemService的合同。调用类即使它们是服务,也不必担心管理事件。但我一直无法想到解决这个问题的方法,因此这个问题。

这看起来像以前可能解决过的问题。有没有人有类似的问题,如果有,你是如何解决的?

3 个答案:

答案 0 :(得分:22)

总结您的问题:您正在寻找一种以事务安全的方式发送消息。

选项1:JMS

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

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

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

选项2:交易同步

或者,如果JMS对于您的案例来说太重量,您可以使用事务同步:发送事件时,不要直接发送事件,而是使用Spring的TransactionSynchronizationManager.registerSynchronization,并发送您afterCommit()的{​​{1}}中的邮件。 您可以为每个要发送的事件添加新同步,也可以添加一个同步,并通过使用TransactionSynchronization将包含该列表的对象绑定到事务来跟踪要发送的事件。

我建议不要试图使用你自己的TransactionSynchronizationManager.bindResource,因为在某些情况下会出错;例如,如果在您的交易中,您将开始新的交易(ThreadLocal)。

与您当前设置的差异:

  • 如果在这种情况下处理事件时抛出异常,您的服务将抛出异常,但更改将已在数据库中提交。
  • 如果您的某个侦听器也写入数据库,则必须在新事务中执行此操作。

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

与使用JMS相比,它不那么健壮(较少的事务性),但更轻松,更容易设置,并且通常足够好

答案 1 :(得分:1)

与Wouter Coekaerts相比,我理解您正在寻找一种方式来发送通知,如果创建它们的事务成功完成,它们将仅提交给reciver。 - 所以你正在寻找类似于transactional CDI-Event mechanism.

的东西

我的想法是这样解决的:

  • 发送事件并将它们“存储”在事件处理机制的列表中,但不要将事件转发给reciver。 (我想这很容易实现)
  • 如果交易是成功的,则将事件从列表转发给收集者
  • 如果事务具有支持滚动的
  • ,则从列表中删除事件

要转发或删除事件,我首先要了解一下spring事务机制。如果无法扩展它,您可以编写一个AOP方面,如果使用@Transactional注释的方法与(运行时)异常一起离开,则转发事件。但是,如果@Transactional带注释的方法与(运行时)异常一起离开,则从列表中删除事件。

答案 2 :(得分:1)

正如@Wouter提到的一种替代方法是使用异步消息传递技术。我可以想到另外两种方法:

  1. ItemService触发的事件在提交后存储在数据库表中(因此在回滚时它们是不可用的)。后台(异步)作业检查事件并调用适当的服务。你可以称之为'自制JMS'。如果消息传递基础结构不可用,则可能是替代方案。
  2. 使用ThreadLocal临时排队所有事件,并在提交复合事务后,触发事件。这不是持久性的,可能会失败,例如在提交之后并且在所有事件已经出列之前。我不是 ThreadLocal 的粉丝,但它比使用非相关参数搞乱业务接口(ItemService)更好。请参阅此相关的春季相关implementation
  3. 我想最后的答案是不可能的,因为它实际上取决于系统的细节,尤其是事件触发的所有外部服务。