我有一个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.HibernateTransactionManager
和org.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的合同。调用类即使它们是服务,也不必担心管理事件。但我一直无法想到解决这个问题的方法,因此这个问题。
这看起来像以前可能解决过的问题。有没有人有类似的问题,如果有,你是如何解决的?
答案 0 :(得分:22)
总结您的问题:您正在寻找一种以事务安全的方式发送消息。
事务安全消息传递正是JMS的用途。在Spring中也有很好的JMS集成,请参阅Spring文档中的JMS chapter。
当且仅当提交事务时,才会确保消息是发送的。 它还有助于处理这些事件的侦听器中的错误。
与当前设置的不同之处在于,这些事件将以异步方式处理:您的服务将在处理这些事件之前返回。 (JMS将确保最终处理它们,它可以配置为多次尝试以及如何处理错误,......)。根据您的需要,这可能是好事也可能是坏事。
或者,如果JMS对于您的案例来说太重量,您可以使用事务同步:发送事件时,不要直接发送事件,而是使用Spring的TransactionSynchronizationManager.registerSynchronization
,并发送您afterCommit()
的{{1}}中的邮件。
您可以为每个要发送的事件添加新同步,也可以添加一个同步,并通过使用TransactionSynchronization
将包含该列表的对象绑定到事务来跟踪要发送的事件。
我建议不要试图使用你自己的TransactionSynchronizationManager.bindResource
,因为在某些情况下会出错;例如,如果在您的交易中,您将开始新的交易(ThreadLocal
)。
与您当前设置的差异:
或者,您可以使用RequiresNew
而不是beforeCommit
,但是即使以后对数据库的实际提交失败,您的事件也会被处理(邮件发送,......)。
与使用JMS相比,它不那么健壮(较少的事务性),但更轻松,更容易设置,并且通常足够好。
答案 1 :(得分:1)
与Wouter Coekaerts相比,我理解您正在寻找一种方式来发送通知,如果创建它们的事务成功完成,它们将仅提交给reciver。 - 所以你正在寻找类似于transactional CDI-Event mechanism.
的东西我的想法是这样解决的:
要转发或删除事件,我首先要了解一下spring事务机制。如果无法扩展它,您可以编写一个AOP方面,如果使用@Transactional
注释的方法与(运行时)异常一起离开,则转发事件。但是,如果@Transactional
带注释的方法与(运行时)异常一起离开,则从列表中删除事件。
答案 2 :(得分:1)
正如@Wouter提到的一种替代方法是使用异步消息传递技术。我可以想到另外两种方法:
ItemService
触发的事件在提交后存储在数据库表中(因此在回滚时它们是不可用的)。后台(异步)作业检查事件并调用适当的服务。你可以称之为'自制JMS'。如果消息传递基础结构不可用,则可能是替代方案。我想最后的答案是不可能的,因为它实际上取决于系统的细节,尤其是事件触发的所有外部服务。