Convert Figma logo to code with AI

eventuate-tram logoeventuate-tram-sagas

Sagas for microservices

1,010
230
1,010
47

Top Related Projects

Example code for the book Microservice patterns

Example code for my building and deploying microservices with event sourcing, CQRS and Docker presentation

Quick Overview

Eventuate Tram Sagas is a Java library for implementing distributed transactions and sagas in microservices architectures. It provides a framework for coordinating multiple services to maintain data consistency across distributed systems, using the saga pattern to manage long-running transactions.

Pros

  • Simplifies the implementation of distributed transactions in microservices
  • Supports both orchestration and choreography saga patterns
  • Integrates well with Spring Boot and other Java frameworks
  • Provides built-in support for compensating transactions and error handling

Cons

  • Requires a learning curve to understand and implement sagas effectively
  • May introduce additional complexity to the overall system architecture
  • Limited to Java-based microservices environments
  • Dependency on external message broker (e.g., Apache Kafka) for communication

Code Examples

  1. Defining a Saga:
public class CreateOrderSaga implements SimpleSaga<CreateOrderSagaData> {
  private SagaDefinition<CreateOrderSagaData> sagaDefinition =
    step()
      .invokeParticipant(this::reserveCredit)
      .withCompensation(this::releaseCredit)
    .step()
      .invokeParticipant(this::createOrder)
      .withCompensation(this::cancelOrder)
    .build();

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

  // Participant methods...
}
  1. Starting a Saga:
@Autowired
private SagaManager<CreateOrderSagaData> createOrderSagaManager;

public CompletableFuture<CreateOrderResult> createOrder(CreateOrderRequest createOrderRequest) {
  CreateOrderSagaData data = new CreateOrderSagaData(createOrderRequest);
  return createOrderSagaManager.create(data, CreateOrderSagaState.class);
}
  1. Implementing a Saga Participant:
@SagaCommandHandlersBuilder
public class OrderCommandHandlers {
  @SagaCommandHandler
  public CommandWithDestination create(CreateOrderCommand cmd) {
    Order order = orderService.createOrder(cmd.getOrderDetails());
    return CommandWithDestination.reply(new CreateOrderReply(order.getId()));
  }

  @SagaCommandHandler
  public CommandWithDestination cancel(CancelOrderCommand cmd) {
    orderService.cancelOrder(cmd.getOrderId());
    return CommandWithDestination.reply(new CancelOrderReply());
  }
}

Getting Started

  1. Add the Eventuate Tram Sagas dependency to your project:
dependencies {
    implementation 'io.eventuate.tram.sagas:eventuate-tram-sagas-spring-orchestration:0.18.0.RELEASE'
}
  1. Configure Eventuate Tram in your Spring Boot application:
@Configuration
@EnableEventuateTramSagaParticipant
public class EventuateConfig {
    // Configuration beans...
}
  1. Implement your saga and saga participants as shown in the code examples above.

  2. Start the saga from your service:

@Autowired
private SagaManager<MySagaData> mySagaManager;

public void startSaga(MyRequest request) {
    MySagaData data = new MySagaData(request);
    mySagaManager.create(data);
}

Competitor Comparisons

Example code for the book Microservice patterns

Pros of ftgo-application

  • Comprehensive example of a real-world microservices application
  • Includes multiple services and demonstrates complex interactions
  • Provides a full-stack implementation with both backend and frontend components

Cons of ftgo-application

  • More complex and potentially overwhelming for beginners
  • Requires more setup and resources to run the entire application
  • May be overkill for learning basic microservices concepts

Code Comparison

eventuate-tram-sagas:

public class CreateOrderSaga implements SimpleSaga<CreateOrderSagaData> {
  private SagaDefinition<CreateOrderSagaData> sagaDefinition =
          step()
            .invokeParticipant(this::reserveCredit)
            .step()
            .invokeParticipant(this::approveOrder)
            .build();
}

ftgo-application:

public class CreateOrderSaga implements SimpleSaga<CreateOrderSagaData> {
  private SagaDefinition<CreateOrderSagaData> sagaDefinition =
          step()
            .invokeParticipant(this::reserveCredit)
            .withCompensation(this::releaseCredit)
          .step()
            .invokeParticipant(this::approveOrder)
            .withCompensation(this::rejectOrder)
          .build();
}

The ftgo-application example provides a more detailed saga implementation with compensation steps, while eventuate-tram-sagas offers a simpler structure focused on the core concepts.

Example code for my building and deploying microservices with event sourcing, CQRS and Docker presentation

Pros of event-sourcing-examples

  • Provides a broader range of event sourcing patterns and examples
  • Includes implementations in multiple programming languages
  • Offers more comprehensive documentation and explanations

Cons of event-sourcing-examples

  • Less focused on specific saga implementation patterns
  • May be more complex for beginners to understand and implement
  • Not as actively maintained as eventuate-tram-sagas

Code Comparison

event-sourcing-examples (Java):

@Aggregate
public class Order {
    @AggregateIdentifier
    private String orderId;
    private OrderStatus status;

    @CommandHandler
    public Order(CreateOrderCommand cmd) {
        apply(new OrderCreatedEvent(cmd.getOrderId()));
    }
}

eventuate-tram-sagas (Java):

public class CreateOrderSaga implements SimpleSaga<CreateOrderSagaData> {
    @Autowired
    private OrderService orderService;

    private SagaDefinition<CreateOrderSagaData> sagaDefinition =
            step()
                .invokeLocal(this::createOrder)
                .build();

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

The event-sourcing-examples code focuses on aggregate implementation, while eventuate-tram-sagas emphasizes saga definition and orchestration. eventuate-tram-sagas provides a more structured approach to implementing sagas, making it easier to manage complex distributed transactions.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

= An Eventuate project

image::https://eventuate.io/i/logo.gif[]

This project is part of http://eventuate.io[Eventuate], which is a microservices collaboration platform.

= Eventuate Tram Sagas

[cols="5%,20%a"] [cols="a,a"] |=== | Spring/Micronaut | image::https://img.shields.io/maven-central/v/io.eventuate.tram.sagas/eventuate-tram-sagas-bom[link="https://search.maven.org/search?q=io.eventuate.tram.sagas"] | Quarkus | image::https://img.shields.io/maven-central/v/io.eventuate.tram.sagas/eventuate-tram-sagas-quarkus-bom[link="https://search.maven.org/search?q=io.eventuate.tram.sagas"] |===

The Eventuate Tram Sagas framework is a saga framework for Java microservices that use JDBC/JPA and Spring Boot/Micronaut.

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 http://microservices.io/patterns/data/saga.html[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 Sagas is a saga orchestration framework for Spring Boot, Micronaut and Quarkus (using https://github.com/eventuate-tram/eventuate-tram-sagas-quarkus[Eventuate Tram for Quarkus]) applications. It is built on the https://github.com/eventuate-tram/eventuate-tram-core[Eventuate Tram framework], which enables an application to atomically update a database and publish a message without using JTA. Eventuate Tram Sagas is described in more detail in my book https://www.manning.com/books/microservice-patterns[Microservice Patterns].

== Learn more

Presentations, videos and articles:

Example applications:

== Writing an orchestrator

The https://github.com/eventuate-tram/eventuate-tram-sagas-examples-customers-and-orders[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 https://bintray.com/eventuateio-oss/eventuate-maven-release/eventuate-tram-sagas[JCenter]. The latest version is:

[cols="5%,20%a"] |=== | Release | image::https://api.bintray.com/packages/eventuateio-oss/eventuate-maven-release/eventuate-tram-sagas/images/download.svg[link="https://bintray.com/eventuateio-oss/eventuate-maven-release/eventuate-tram-sagas/_latestVersion"] | RC | image::https://api.bintray.com/packages/eventuateio-oss/eventuate-maven-rc/eventuate-tram-sagas/images/download.svg[link="https://bintray.com/eventuateio-oss/eventuate-maven-rc/eventuate-tram-sagas/_latestVersion"] |===

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

  • io.eventuate.tram.sagas:eventuate-tram-sagas-orchestration-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 https://github.com/eventuate-tram/eventuate-tram-core[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 https://github.com/eventuate-tram/eventuate-tram-core-examples-basic[Eventuate Tram Code Basic examples] project has an example https://github.com/eventuate-tram/eventuate-tram-core-examples-basic/blob/master/docker-compose.yml[docker-compose.yml file].

== Contributing

Contributions are welcome.

Please sign a https://chrisrichardson.net/legal/[contributor license agreement].