Sagas for microservices
Clone or download
Latest commit 304d095 Sep 25, 2018
Permalink
Failed to load latest commit information.
.circleci added build-and-test-everything.sh Dec 5, 2017
buildSrc/src/main/groovy Initial code Oct 23, 2017
eventuate-jpa-sagas-framework Removed obselete code Sep 25, 2018
eventuate-tram-sagas-event-sourcing-support Initial code Oct 23, 2017
eventuate-tram-sagas-in-memory Experimental saga participant proxy concept, misc refactoring Feb 2, 2018
eventuate-tram-sagas-simple-dsl Experimental saga participant proxy concept, misc refactoring Feb 2, 2018
eventuate-tram-sagas-testing-support Removed obselete code Sep 25, 2018
gradle Initial code Oct 23, 2017
mysql Removed obselete code Sep 25, 2018
orders-and-customers Removed obselete code Sep 25, 2018
postgres Removed obselete code Sep 25, 2018
.gitignore Experimental saga participant proxy concept, misc refactoring Feb 2, 2018
LICENSE.md Initial code Oct 23, 2017
README.adoc Updated README Dec 13, 2017
build-and-test-all-mysql.sh Adding postgres support. Dec 4, 2017
build-and-test-all-postgres.sh Adding postgres support. Dec 4, 2017
build-and-test-everything.sh added build-and-test-everything.sh Dec 5, 2017
build.gradle Initial code Oct 23, 2017
deploy-artifacts.sh Adding postgres support. Dec 4, 2017
docker-compose-mysql.yml Updating eventuate tram Mar 13, 2018
docker-compose-postgres.yml Updating eventuate tram Mar 13, 2018
gradle.properties Added testing framework for sagas Aug 23, 2018
gradlew Initial code Oct 23, 2017
gradlew.bat Initial code Oct 23, 2017
mysql-cli.sh Added testing framework for sagas Aug 23, 2018
postgres-cli.sh Added testing framework for sagas Aug 23, 2018
publish-docker-images.sh Initial code Oct 23, 2017
set-env-mysql.sh Adding postgres support. Dec 4, 2017
set-env-postgres.sh Adding postgres support. Dec 4, 2017
set-env.sh Adding postgres support. Dec 4, 2017
settings.gradle Added testing framework for sagas Aug 23, 2018
wait-for-mysql.sh Fixing test fail. Nov 21, 2017
wait-for-postgres.sh Adding postgres support. Dec 4, 2017

README.adoc

Eventuate Tram Saga

download

The Eventuate Tram Saga framework is a saga framework for Java microservices that use JDBC/JPA.

A major challenge when implementing business applications using the microservice architecture is maintaining data consistency across services. Each service has its own private data and you can’t use distributed transactions. The solution is to use sagas.

A saga maintains consistency across multiple microservices by using a series of a local transactions that are coordinated using messages or events. A saga consists of a series of steps. Each step consists of either transaction, a compensating transaction or both. Each transaction is the invocation of a saga participant using a command message. A saga executes the forward transactions sequentially. If one of them fails then the saga executes the compensating transactions in reverse order to rollback the saga.

Eventuate Tram Saga is described in more detail in my book Microservice Patterns. It is built on the Eventuate Tram framework, which enables an application to atomically update a database and publish a message without using JTA.

Learn more

Recent presentations:

Example applications:

Writing an orchestrator

The Customers and Orders uses a saga to create an Order in the Order Service and reserve credit in the Customer Service. The CreateOrderSaga consists of the following three steps:

  1. The CreateOrderSaga is instantiated after the Order is created. Consequently, the first step is simply a compensating transaction, which is executed in the credit cannot be reserved to reject the order.

  2. Requests the CustomerService to reserve credit for the order. If the reservation is success, the next step is executed. Otherwise, the compensating transactions are executed to roll back the saga.

  3. Approves the order, if the credit is reserved.

Here is part of the definition of CreateOrderSaga.

public class CreateOrderSaga implements SimpleSaga<CreateOrderSagaData> {

  private SagaDefinition<CreateOrderSagaData> sagaDefinition =
          step()
            .withCompensation(this::reject)
          .step()
            .invokeParticipant(this::reserveCredit)
          .step()
            .invokeParticipant(this::approve)
          .build();


  @Override
  public SagaDefinition<CreateOrderSagaData> getSagaDefinition() {
    return this.sagaDefinition;
  }


  private CommandWithDestination reserveCredit(CreateOrderSagaData data) {
    long orderId = data.getOrderId();
    Long customerId = data.getOrderDetails().getCustomerId();
    Money orderTotal = data.getOrderDetails().getOrderTotal();
    return send(new ReserveCreditCommand(customerId, orderId, orderTotal))
            .to("customerService")
            .build();

...

The reserveCredit() creates a message to send to the Customer Service to reserve credit.

Creating an saga orchestrator

The OrderService creates the saga:

public class OrderService {

  @Autowired
  private SagaManager<CreateOrderSagaData> createOrderSagaManager;

  @Autowired
  private OrderRepository orderRepository;

  @Transactional
  public Order createOrder(OrderDetails orderDetails) {
    ResultWithEvents<Order> oe = Order.createOrder(orderDetails);
    Order order = oe.result;
    orderRepository.save(order);
    CreateOrderSagaData data = new CreateOrderSagaData(order.getId(), orderDetails);
    createOrderSagaManager.create(data, Order.class, order.getId());
    return order;
  }

}

Writing a saga participant

Here is the CustomerCommandHandler, which handles the command to reserve credit:

public class CustomerCommandHandler {

  @Autowired
  private CustomerRepository customerRepository;

  public CommandHandlers commandHandlerDefinitions() {
    return SagaCommandHandlersBuilder
            .fromChannel("customerService")
            .onMessage(ReserveCreditCommand.class, this::reserveCredit)
            .build();
  }

  public Message reserveCredit(CommandMessage<ReserveCreditCommand> cm) {
     ...
  }
  ...

Maven/Gradle artifacts

The artifacts are in JCenter. The latest version is:

download

If you are writing a Saga orchestrator add this dependency to your project:

  • io.eventuate.tram.sagas:eventuate-tram-sagas-simple-dsl:$eventuateTramSagasVersion

If you are writing a saga participant then add this dependency:

  • io.eventuate.tram.sagas:eventuate-jpa-sagas-framework:$eventuateTramSagasVersion

You must also include one of the Eventuate Tram 'implementation' artifacts:

  • io.eventuate.tram.core:eventuate-tram-jdbc-kafka:$eventuateTramVersion - JDBC database and Apache Kafka message broker

  • io.eventuate.tram.core:eventuate-tram-in-memory:$eventuateTramVersion - In-memory JDBC database and in-memory messaging for testing

Running the CDC service

In addition to a database and message broker, you will need to run the Eventuate Tram CDC service. It reads messages and events inserted into the database and publishes them to Apache Kafka. It is written using Spring Boot. The easiest way to run this service during development is to use Docker Compose. The Eventuate Tram Code Basic examples project has an example docker-compose.yml file.