标题可能不正确,但我会尝试解释我的问题。我的项目是一个Spring Boot项目。我有调用外部REST端点的服务。
我有一个服务方法,它包含对我所拥有的其他服务的几个方法调用。每个方法调用都可以成功或不成功。每个方法调用都是对REST端点进行的,并且可能存在一些问题,例如Web服务不可用或者在极少数情况下会引发未知异常。发生了什么,我需要能够跟踪哪些方法调用成功,如果其中任何一个失败,我想回滚到原始状态,好像什么也没发生,看到它有点像@Transactional注释。所有REST调用都是不同的端点,需要单独调用,并且来自我没有影响的外部方。例如:
public MyServiceImpl implements MyService {
@Autowired
private Process1Service;
@Autowired
private Process2Service;
@Autowired
private Process3Service;
@Autowired
private Process4Service;
public void bundledProcess() {
process1Service.createFileRESTcall();
process2Service.addFilePermissionsRESTcall();
process3Service.addFileMetadataRESTcall(); <-- might fail for example
process4Service.addFileTimestampRESTcall();
}
}
如果例如 process3Service.addFileMetadataRESTcall 失败,我想为process3之前的每一步执行类似撤消(以相反顺序)的操作:
process2Service.removeFilePermissionsRESTcall();
process1Service.deleteFileRESTcall();
我读过有关Command模式的内容,但这似乎是用于撤消应用程序内部的操作,作为执行操作的历史记录,而不是在Spring Web应用程序中。这对我的用例也是正确的,还是应该跟踪每个方法/ webservice调用是否成功?这样做有最好的做法吗?
我想我会跟踪它,我需要知道哪个方法调用失败,并从那里执行我的'undo'方法REST调用。虽然理论上甚至这些电话当然也可能失败。
我的主要目标是不创建文件(在我的示例中),其中没有执行任何进一步的过程。它要么全部成功要么全无。一种交易。
Update1:基于评论改进了伪实现:
public Process1ServiceImpl implements Process1Service {
public void createFileRESTcall() throws MyException {
// Call an external REST api, pseudo code:
if (REST-call fails) {
throw new MyException("External REST api failed");
}
}
}
public class BundledProcessEvent {
private boolean createFileSuccess;
private boolean addFilePermissionsSuccess;
private boolean addFileMetadataSuccess;
private boolean addFileTimestampSuccess;
// Getters and setters
}
public MyServiceImpl implements MyService {
@Autowired
private Process1Service;
@Autowired
private Process2Service;
@Autowired
private Process3Service;
@Autowired
private Process4Service;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Transactional(rollbackOn = MyException.class)
public void bundledProcess() {
BundleProcessEvent bundleProcessEvent = new BundleProcessEvent();
this.applicationEventPublisher.publishEvent(bundleProcessEvent);
bundleProcessEvent.setCreateFileSuccess = bundprocess1Service.createFileRESTcall();
bundleProcessEvent.setAddFilePermissionsSuccess = process2Service.addFilePermissionsRESTcall();
bundleProcessEvent.setAddFileMetadataSuccess = process3Service.addFileMetadataRESTcall();
bundleProcessEvent.setAddFileTimestampSuccess = process4Service.addFileTimestampRESTcall();
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void rollback(BundleProcessEvent bundleProcessEvent) {
// If the last process event is successful, we should not
// be in this rollback method even
//if (bundleProcessEvent.isAddFileTimestampSuccess()) {
// remove timestamp
//}
if (bundleProcessEvent.isAddFileMetadataSuccess()) {
// remove metadata
}
if (bundleProcessEvent.isAddFilePermissionsSuccess()) {
// remove file permissions
}
if (bundleProcessEvent.isCreateFileSuccess()) {
// remove file
}
}
答案 0 :(得分:1)
您的操作看起来像一个事务,因此您可以使用@Transactional
注释。从您的代码中我无法真正告诉您如何管理每个操作的HTTP响应调用,但是您应该考虑使用您的服务方法来返回它们,然后根据响应调用进行回滚。您可以创建一系列类似的方法,但您希望逻辑的确切程度取决于您。
private Process[] restCalls = new Process[] {
new Process() { public void call() { process1Service.createFileRESTcall(); } },
new Process() { public void call() { process2Service.addFilePermissionsRESTcall(); } },
new Process() { public void call() { process3Service.addFileMetadataRESTcall(); } },
new Process() { public void call() { process4Service.addFileTimestampRESTcall(); } },
};
interface Process {
void call();
}
@Transactional(rollbackOn = Exception.class)
public void bundledProcess() {
restCalls[0].call();
... // say, see which process returned wrong response code
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void rollback() {
// handle rollback according to failed method index
}
检查this article.可能派上用场。
答案 1 :(得分:1)
这个问题的答案非常广泛。有多种方法可以在这里完成分布式事务。但是,由于您使用的是Java和Spring,最好的办法是使用JTA(Java Transaction API)之类的东西,它可以在多个服务/实例/等之间实现分布式事务。幸运的是,Spring Boot使用Atomikos或Bitronix支持JTA 。您可以阅读文档here。
启用分布式事务的一种方法是通过消息代理(如JMS,RabbitMQ,Kafka,ActiveMQ等),并使用XA事务(两阶段提交)等协议。对于不支持分布式的外部服务,一种方法是编写一个包装服务,该服务将XA事务理解为该外部服务。