GitHub - berndruecker/trip-booking-saga-java:使用轻量级开源工作流引擎(Camunda) 实现Saga模式的示例


Saga模式描述了如何在没有两阶段提交2PC的情况下解决分布式(业务)事务,因为2PC不能在分布式系统中扩展。Saga模式基本思路是将整个交易分解为多个步骤或活动。只有内部的步骤可以在原子事务中执行,但整体的一致性由Saga处理。Saga有责任完成整个业务交易或使系统处于已知的终止状态。因此,如果出现错误,则应用业务回滚过程,该过程通过以相反顺序调用补偿步骤或活动来实现。可以更详细地了解Sagas :如何在没有两阶段提交的情况下实现复杂的业务交易

在该Github示例的酒店案例中(点击标题),汽车和航班预订可能由不同的远程服务完成。所以没有技术事务,而只有业务事务。如果无法成功进行航班预订,您需要取消酒店和汽车。

使用Camunda,您可以使用图形建模或称为Model-API的Java DSL来实现Saga。由于Camunda非常轻量级,您可以启动所谓的流程引擎,定义Saga并通过几行Java代码运行实例(如果使用默认配置和内存中的H2数据库),请参阅TripBookingSaga.java

public class TripBookingSaga {

  public static void main(String args) {
    // Configure and startup (in memory) engine
    ProcessEngine camunda = 
        new StandaloneInMemProcessEngineConfiguration()
          .buildProcessEngine();
    
    // define saga as BPMN process
    ProcessBuilder saga = Bpmn.createExecutableProcess("trip");
    
    // - flow of activities and compensating actions
    saga.startEvent()
        .serviceTask("car").name("Reserve car").camundaClass(ReserveCarAdapter.class)
          .boundaryEvent().compensateEventDefinition().compensateEventDefinitionDone()
          .compensationStart().serviceTask("car-compensate").name("Cancel car").camundaClass(CancelCarAdapter.class).compensationDone()
        .serviceTask("hotel").name("Book hotel").camundaClass(BookHotelAdapter.class)
          .boundaryEvent().compensateEventDefinition().compensateEventDefinitionDone()
          .compensationStart().serviceTask("hotel-compensate").name("Hotel car").camundaClass(CancelCarAdapter.class).compensationDone()
        .serviceTask("flight").name("Book flight").camundaClass(BookFlightAdapter.class)
          .boundaryEvent().compensateEventDefinition().compensateEventDefinitionDone()
          .compensationStart().serviceTask("flight-compensate").name("Cancel flight").camundaClass(CancelCarAdapter.class).compensationDone()
        .endEvent();
    
    // - trigger compensation in case of any exception (other triggers are possible)
    saga.eventSubProcess()
        .startEvent().error("java.lang.Throwable")
        .intermediateThrowEvent().compensateEventDefinition().compensateEventDefinitionDone()
        .endEvent();     

    // finish Saga and deploy it to Camunda
    camunda.getRepositoryService().createDeployment() //
        .addModelInstance("trip.bpmn", saga.done()) //
        .deploy();
    
    // now we can start running instances of our saga - its state will be persisted
    camunda.getRuntimeService().startProcessInstanceByKey("trip", Variables.putValue("name", "trip1"));
    camunda.getRuntimeService().startProcessInstanceByKey("trip", Variables.putValue("name", "trip2"));
  }

}

真正业务逻辑在适配器类,如BookHotelAdapter.
上述定义可能看起来有点冗长,因为您必须使用BPMN术语。但是你可以编写一个瘦的SagaBuilder来提高Saga定义的可读性:

SagaBuilder saga = SagaBuilder.newSaga("trip")
        .activity("Reserve car", ReserveCarAdapter.class) 
        .compensationActivity("Cancel car", CancelCarAdapter.class) 
        .activity("Book hotel", BookHotelAdapter.class) 
        .compensationActivity("Cancel hotel", CancelHotelAdapter.class) 
        .activity("Book flight", BookFlightAdapter.class) 
        .compensationActivity("Cancel flight", CancelFlightAdapter.class) 
        .end()
        .triggerCompensationOnAnyError();

camunda.getRepositoryService().createDeployment() 
        .addModelInstance(saga.getModel()) 
        .deploy();


引擎将负责状态处理,补偿,还可以处理超时和升级。
在现实场景中,您可以以不同方式配置和运行Camunda引擎,例如使用Spring或Spring Boot。在这个例子中,您还可以使用Spring Boot应用程序启动应用程序 - 之后甚至可以连接Camundas可视化工具。


相关项目:
Uber优步使用上述架构的案例:cadence-java-samples