Read this document to learn how to get started with Eventuate Tram for Spring Boot, which has a simpler getting started experience.

Note
This document describes how to use the 2024.0.RELEASE version of the platform and later with Spring Boot and Gradle.

Please see the original getting started guide for variants: Getting started with Eventuate Tram

What is Eventuate Tram?

Eventuate Tram is a microservices collaboration platform. It directly or indirectly implements the following service collaboration patterns: Saga, Command-side replica and CQRS.

Eventuate sends and receives messages as part of a database transaction, which ensures that your service/application atomically updates the database and publishes/consumes messages. Messages are sent using the Transactional Outbox pattern, which guarantees that the service sends messages only if the database transaction commits successfully. Messages are consumed using the Idempotent Consumer pattern, which ensures that the service processes each message only once.

To learn more, please see the article Eventuate explained using microservices patterns.

What technologies does it support?

Currently, Eventuate Tram supports the following databases:

And, the following message brokers:

  • Apache Kafka

  • ActiveMQ

  • RabbitMQ

  • Redis

How to use Eventuate Tram with Spring Boot

Getting started examples

The Eventuate Tram basic examples contains the complete code for the features described in this document.

See also

JavaDocs

Maven/Gradle project setup

Latest framework version:

Release

eventuate platform dependencies

Snapshot

Maven metadata URL

Gradle Configuration

In gradle.properties:

eventuatePlatformVersion=LATEST_VERSION

In build.gradle, specify these Maven repositories:

repositories {
    mavenCentral()
}

Include the platform BOM:

dependencies {
  implementation(platform("io.eventuate.platform:eventuate-platform-dependencies:$eventuatePlatformVersion"))
}

Maven Configuration

TODO

Eventuate Tram APIs and (Spring Boot) starters

Eventuate Tram provides the following APIs and Spring Boot starters:

  • Publishing and consuming events

  • Sending and receiving commands

  • Sending and receiving messages

Event publishing and consuming events

There are two APIs and starters:

  • Publishing events

  • Subscribing to events

Publishing events

To publish events, add this dependency to the build.gradle:

dependencies {
  implementation "io.eventuate.tram.core:eventuate-tram-spring-events-publisher-starter"

The starter auto-configures a DomainEventPublisher bean, which can be used as follows:

public class AccountService {

  @Autowired
  private DomainEventPublisher domainEventPublisher;

  @Transactional
  public void debitAccount(String accountId, Money amount) {
    ...

    DomainEvent domainEvent = new AccountDebited(...);

    domainEventPublisher.publish("Account", accountId, Collections.singletonList(domainEvent));

Subscribing to events

To subscribe to events, add this dependency to the build.gradle:

dependencies {
  implementation "io.eventuate.tram.core:eventuate-tram-spring-events-subscriber-starter"

The starter auto-configures a DomainEventDispatcherFactory bean, which can be used as follows:

@Configuration
public class EventSubscriberConfiguration {

  @Bean
  public DomainEventDispatcher domainEventDispatcher(DomainEventDispatcherFactory
                  domainEventDispatcherFactory, AccountEventsConsumer target) {
    return domainEventDispatcherFactory.make("eventSubscriberId",
                                             target.domainEventHandlers());
  }

  @Bean
  public AccountEventsConsumer accountEventsConsumer() {
    return new AccountEventsConsumer();
  }

where the AccountEventsConsumer class is as follows:

public class AccountEventsConsumer {

  public DomainEventHandlers domainEventHandlers() {
    return DomainEventHandlersBuilder
            .forAggregateType("Account")
            .onEvent(AccountDebited.class, this::handleAccountDebited)
            .build();
  }

  public void handleAccountDebited(DomainEventEnvelope<AccountDebited> event) { ... }

Sending and receiving commands

To send and receive commands, add the following dependencies to the build.gradle:

dependencies {
  implementation "io.eventuate.tram.core:eventuate-tram-spring-commands-starter"

Sending commands and handling replies

The eventuate-tram-spring-commands-starter dependency auto-configures a CommandProducer bean, which is used as follows:

public class CommandProducingService {
  @Autowired
  private CommandProducer commandProducer;

  void produceCommand(ProduceRequest produceRequest) {
    String messageId = commandProducer.send(channel, command, replyChannel, );
    ...

The commandProducer.send() method returns the message ID, which you can use to correlate the command with the reply message.

Handling commands

The eventuate-tram-spring-commands-starter dependency auto-configures a CommandDispatcherFactory bean, which you can use to configure command handlers:

public class CommandConsumerConfiguration {
  @Bean
  public CommandDispatcher commandDispatcher(CommandDispatcherFactory commandDispatcherFactory, CreditManagementCommandHandlers creditManagementCommandHandlers) {
    return commandDispatcherFactory.make(commandDispatcherId, creditManagementCommandHandlers.getCommandHandlers());
  }
 ...

where CreditManagementCommandHandlers is as follows:

public class CreditManagementCommandHandlers {

  public CommandHandlers getCommandHandlers() {
    return CommandHandlersBuilder
            .fromChannel(commandChannel)
            .onMessage(ReserveCreditCommand.class, this::reserveCredit)
            .build();
  }

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

It defines a getCommandHandlers() method that returns a CommandHandlers object that specifies the command handlers.

Sending and receiving messages

To send messages and receive messages add the following starter to the build.gradle:

dependencies {
  implementation "io.eventuate.tram.core:eventuate-tram-spring-messaging-starter"

Sending messages

This starter auto-configures a MessageProducer bean that you can use to send messages:

public class MessageProducingService {

    @Autowired
    private MessageProducer messageProducer;

    void sendMessage(long accountId) {
        messageProducer.send(messageChannel, MessageBuilder.withPayload(String.valueOf(accountId)).build());
    }

Receiving messages

The eventuate-tram-spring-messaging-starter dependency auto-configures a MessageConsumer bean that you can use to subscribe to messages:

@Component
public class MessageHandler {

  public MessageHandler(MessageConsumer messageConsumer ...) {
    this.messageConsumer = messageConsumer;
    ...

  @PostConstruct
  public void subscribe() {
    messageConsumer.subscribe(messageConsumerId, Set.of(messageChannel), this::handleMessage);
  }

  public void handleMessage(Message message) {
   ...

Adding the transport dependencies

In addition to specifying the API dependencies, you must also specify the dependencies for the transport you are using.

  • Producer transport - JDBC

  • Consumer transport/message broker - kafka, rabbitmq, activemq, redis

Adding the transport-specific dependencies to the classpath triggers the auto-configuration (by eventuate-tram-spring-messaging-starter) of the needed beans.

Dependencies for a service that sends and receives messages

dependencies {
    runtimeOnly "io.eventuate.tram.core:eventuate-tram-spring-jdbc-$messageBroker"
}

This dependency auto-configures the message broker-specific beans and JDBC-based idempotency.

Dependencies for a service that receives messages without using JDBC-based idempotency

dependencies {
    runtimeOnly "io.eventuate.tram.core:eventuate-tram-spring-consumer-$messageBroker"
}

This dependency only auto-configures the message broker-specific beans.

Dependencies for a service that only sends messages

dependencies {
    runtimeOnly "io.eventuate.tram.core:eventuate-tram-spring-producer-jdbc"
}

Configuration properties

You must specific various configuration properties so that the Eventuate Tram framework can connect to the message broker and the database.

Configuration properties for a producer service

Since a producer inserts messages into an outbox table, you must configure the standard Spring Boot JDBC properties:

spring.datasource.url=jdbc:mysql://localhost/eventuate
spring.datasource.username=mysqluser
spring.datasource.password=mysqlpw
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

In addition, you can optionally configure the eventuate.database.schema property, which specifies the qualifier that Eventuate Tram should use for the Eventuate tables in SQL statements.

The default value for this property is eventuate. Other possible values are:

  • none - tables are referenced without a qualifier

  • the name to use as a table qualifier

Configuration properties for a consumer service

The configuration properties depend on the message broker you are using.

Apache Kafka

eventuatelocal.kafka.bootstrap.servers=localhost:9092

ActiveMQ

activemq.url=tcp://localhost:61616

RabbitMQ

rabbitmq.broker.addresses=localhost
eventuatelocal.zookeeper.connection.string=localhost:2181

Eventuate Tram uses Zookeeper to coordinate the RabbitMQ consumers in order to implement the Competing Consumers pattern (scaling consumers while preserving ordering).

Configuration properties for consumers that implement idempotency using JDBC

If a consumer uses JDBC-based idempotency, you must configure the standard Spring Boot JDBC properties and optionally, eventuate.database.schema, which, is described above, in order to specify the qualifier for the table that tracks the messages that have been processed.