在交易结束时发送事件

Jon*_*Jon 11 java architecture spring 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);

}
Run Code Online (Sandbox Code Playgroud)

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* 
Run Code Online (Sandbox Code Playgroud)

我们通过创建另一个服务来完成此操作,该服务允许您在父服务中的单个方法中组合来自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);

}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Wou*_*rts 22

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

选项1:JMS

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

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

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

选项2:事务同步

另外,如果JMS是太重量级了你的情况,你可以使用事务同步:当发送,而不是发送它直接使用Spring的事件,TransactionSynchronizationManager.registerSynchronization以及在发送消息afterCommit()你的TransactionSynchronization.您可以为要发送的每个事件添加新同步,也可以添加一个同步并通过将包含该列表的对象绑定到事务来跟踪要发送的事件TransactionSynchronizationManager.bindResource.

我会建议不要试图用你自己ThreadLocal的,因为在某些情况下会出错; 例如,如果在您的交易中,您将启动一个新的交易(RequiresNew).

与您当前设置的差异:

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

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

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