GithubHelp home page GithubHelp logo

eventuate-tram-sagas's Introduction

An Eventuate project

logo

This project is part of Eventuate, which is a microservices collaboration platform.

Eventuate Tram (Transactional Messaging) platform

Spring/Micronaut

eventuate tram bom

Quarkus

eventuate tram quarkus bom

Eventuate Tram is a platform that solves the distributed data management problems inherent in a microservice architecture.

It is described in more detail in my book Microservice Patterns and the getting started guide.

Key benefits of Eventuate Tram

Maintain data consistency using sagas

Implement commands that update data in multiple microservices by using Sagas, which are sequences of local transactions coordinated using messages

Implement queries using CQRS

Implement queries that retrieve data from multiple services by using CQRS views, which are easily queryable replicas maintained using events

Communicate using transactional messaging

Reliably send and receive messages and events as part of a database transaction by using the Transactional Outbox pattern

How it works

Eventuate Tram provides several messaging abstractions:

  • messaging - send and receive messages over named channels

  • events - publish domain events and subscribe to domain events

  • commands - asynchronously send a command to a service and receive a reply

Eventuate Tram messaging implements the Transactional Outbox pattern. An message producer inserts events into an OUTBOX table as part of the ACID transaction that updates data, such as JPA entities. A separate message relay (a.k.a. Eventuate CDC service) publishes messages to the message broker.

ReliablePublication

The Eventuate CDC service works in one of two ways:

Supported technologies

Languages:

Databases:

Message brokers:

  • Apache Kafka

  • ActiveMQ

  • RabbitMQ

  • Redis Streams

Getting started

Please see the getting started guide.

Example applications

There are numerous example applications:

Got questions?

Don’t hesitate to create an issue or see

Need support?

Take a look at the available paid support options.

Transactional messaging

Send a message using MessageProducer:

public interface MessageProducer {
  void send(String destination, Message message);
}

Receive messages using:

public interface MessageConsumer {
  void subscribe(String subscriberId, Set<String> channels, MessageHandler handler);
}

See this example of transactional messaging.

Transactional domain events

The domain event package builds on the core APIs.

Publish domain events using the DomainEventPublisher interface:

public interface DomainEventPublisher {

  void publish(String aggregateType, Object aggregateId, List<DomainEvent> domainEvents);
  ...

Subscribe to domain events using a DomainEventDispatcher:

public class DomainEventDispatcher {
    public DomainEventDispatcher(String eventDispatcherId,
                DomainEventHandlers eventHandlers,
                ...) {
...
}

Handle the events using DomainEventHandlers:

public class RestaurantOrderEventConsumer {

  public DomainEventHandlers domainEventHandlers() {
    return DomainEventHandlersBuilder
            .forAggregateType("net.chrisrichardson.ftgo.restaurantservice.Restaurant")
            .onEvent(RestaurantMenuRevised.class, this::reviseMenu)
            .build();
  }

  public void reviseMenu(DomainEventEnvelope<RestaurantMenuRevised> de) {

See this example of transaction events.

Transactional commands

Transaction commands are implemented using transactional messaging.

Send a command using a CommandProducer:

public interface CommandProducer {
  String send(String channel, Command command, String replyTo, Map<String, String> headers);
  ...
}

Subscribe to commands using a CommandDispatcher:

public class CommandDispatcher {

  public CommandDispatcher(String commandDispatcherId,
           CommandHandlers commandHandlers) {
  ...
}

Handle commands and send a reply using CommandHandlers:

public class OrderCommandHandlers {


  public CommandHandlers commandHandlers() {
    return CommandHandlersBuilder
          .fromChannel("orderService")
          .onMessage(ApproveOrderCommand.class, this::approveOrder)
          ...
          .build();
  }

  public Message approveOrder(CommandMessage<ApproveOrderCommand> cm) {
    ApproveOrderCommand command = cm.getCommand();
    ...
  }

See this example of transactional commands.

Maven/Gradle artifacts

The artifacts are in JCenter. The latest version is:

RC

download

Release

download

There are the following API artifacts:

  • io.eventuate.tram.core:eventuate-tram-messaging:$eventuateTramVersion - core messaging APIs

  • io.eventuate.tram.core:eventuate-tram-events:$eventuateTramVersion - domain event API

  • io.eventuate.tram.core:eventuate-tram-commands:$eventuateTramVersion - commands/reply API

There are the following 'implementation' artifacts:

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

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

  • io.eventuate.tram.core:eventuate-tram-jdbc-rabbitmq:$eventuateTramVersion - JDBC database and RabbitMQ message broker

  • io.eventuate.tram.core:eventuate-tram-jdbc-redis:$eventuateTramVersion - JDBC database and Redis Streams

  • 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 events inserted into the database and publishes them to the message broker. 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.

Contributing

Contributions are welcome.

eventuate-tram-sagas's People

Contributors

cer avatar dartartem avatar kwonglau avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

eventuate-tram-sagas's Issues

Posibillity to fork and join Sagas

Hey,

great work on the framework, love implementing with it and runs really nice.

One question that came up while designing more complex sagas. Given an order with e.g. 3 products, is there any way a saga could process those products concurrently? Like in the form of forking out a saga for each product and on successful or failed completion join them again to the main saga.

Thanks

build 176 randomly failed

Build failed: https://app.circleci.com/pipelines/github/eventuate-tram/eventuate-tram-sagas/63/workflows/686e4476-9603-4809-a0b1-e23b67eb8b9f/jobs/176
After restart passed.

Locally is not reproducible.

I researched logs and debugged failed test and found that there 2 saga managers in the same time:

2021-02-17 15:12:10.262 DEBUG 10221 --- [pool-1-thread-2] i.e.t.s.orchestration.SagaManagerImpl    : handle message invoked io.eventuate.tram.messaging.common.MessageImpl@274a0f74[payload={},headers={commandreply_saga_id=00000177b08c70e0-5acbf80076950000, DATE=Wed, 17 Feb 2021 15:12:10 GMT, reply_outcome-type=SUCCESS, commandreply__destination=customerService, commandreply_reply_to=io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.createorder.LocalCreateOrderSaga-reply, commandreply_type=io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.participants.ReserveCreditCommand, DESTINATION=io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.createorder.LocalCreateOrderSaga-reply, commandreply_saga_type=io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.createorder.LocalCreateOrderSaga, reply_type=io.eventuate.examples.tram.sagas.ordersandcustomers.customers.service.CustomerCreditReserved, reply_to_message_id=00000177b08c7142-5acbf80076950000, ID=00000177b08c71fb-5acbf80076950000}]
2021-02-17 15:12:10.302 DEBUG 10221 --- [pool-1-thread-3] i.e.t.s.orchestration.SagaManagerImpl    : handle message invoked io.eventuate.tram.messaging.common.MessageImpl@274a0f74[payload={},headers={commandreply_saga_id=00000177b08c70e0-5acbf80076950000, DATE=Wed, 17 Feb 2021 15:12:10 GMT, reply_outcome-type=SUCCESS, commandreply__destination=customerService, commandreply_reply_to=io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.createorder.LocalCreateOrderSaga-reply, commandreply_type=io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.participants.ReserveCreditCommand, DESTINATION=io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.createorder.LocalCreateOrderSaga-reply, commandreply_saga_type=io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.createorder.LocalCreateOrderSaga, reply_type=io.eventuate.examples.tram.sagas.ordersandcustomers.customers.service.CustomerCreditReserved, reply_to_message_id=00000177b08c7142-5acbf80076950000, ID=00000177b08c71fb-5acbf80076950000}]

2 identical message handlings from different threads. So, it seems it is concurrency issue.

I found that there 2 saga managers.

  1. Created by SagaInstanceFactory

  1. Created explicitly in configuration:

public SagaManager<LocalCreateOrderSagaData> localCreateOrderSagaManager(Saga<LocalCreateOrderSagaData> saga,

Since SagaInstanceFactory is newer, second place is redundant.

Saga withReply() not working

Hi,
First of all, thank you for great job. I have a problem for withReply() saga method. I check everything according to your project but i could not find it. I'll be happy if you can help me. Thanks..

private Message createMail(CommandMessage cm) {
......
return withLock(Mail.class, mail.getId()).withSuccess(reply); // this method is called but no message is passed to kafka. I trace it
// on kafdrop.
}

so the following method did not handle because there is no message from kafka..
......
.onReply(CreateMailCommandReply.class, CreateObligedSagaState::handleCreateMailCommandReply)
........

Is this project ready for production?

Thanks for bring us this great project! I know this repo from a book, which is Microservices Patterns. I really want to try it, can this framework use in production now?

Handle scenario where compensating transaction returns a failure

Current behavor

} else if (compensating) {
throw new UnsupportedOperationException("Failure when compensating");

This breaks reply message handling.

What about happen:

  • The saga should transition to a failed state.
  • (Possible) the orchestration should tell the participant's command handling logic (via a header) to require a successful response.

Handling multiple local steps in a row

This won't work as intended. The steps will be executed one after the other. But there is no guarantee that the saga will be completed successfully/compensated if any kind of failure occurs. Local and non-local steps should be interleaved.

Either:

  • Not support this and throw an exception. The workaround would be to define non-local steps.
  • Implement an internal messaging-based mechanism to drive the execution of local steps.

Investigate flakey test: shouldReject

repeated failures:

https://app.circleci.com/pipelines/github/eventuate-tram/eventuate-tram-sagas/116/workflows/cde3d011-1e42-4b78-867d-407f2ed8a57e/jobs/582

io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.integrationtests.CustomersAndOrdersIntegrationTest

io.eventuate.util.test.async.EventuallyException: Failed after 60 iterations every 500 milliseconds
	at io.eventuate.util.test.async.Eventually.eventuallyReturning(Eventually.java:70)
	at io.eventuate.util.test.async.Eventually.eventually(Eventually.java:37)
	at io.eventuate.util.test.async.Eventually.eventually(Eventually.java:33)
	at io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.integrationtests.CustomersAndOrdersIntegrationTest.assertOrderState(CustomersAndOrdersIntegrationTest.java:83)
	at io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.integrationtests.CustomersAndOrdersIntegrationTest.shouldReject(CustomersAndOrdersIntegrationTest.java:65)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
	at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
	at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:119)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: org.junit.ComparisonFailure: expected:<[REJECTED]> but was:<[PENDING]>
	at org.junit.Assert.assertEquals(Assert.java:115)
	at org.junit.Assert.assertEquals(Assert.java:144)
	at io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.integrationtests.CustomersAndOrdersIntegrationTest.lambda$assertOrderState$0(CustomersAndOrdersIntegrationTest.java:86)
	at io.eventuate.util.test.async.Eventually.lambda$eventually$0(Eventually.java:38)
	at io.eventuate.util.test.async.Eventually.eventuallyReturning(Eventually.java:59)
	... 59 more

ReactiveSagaManagerImpl's saga_instance's state_name bug

I have tested the "eventuate-tram-sagas-examples-customers-and-orders" and "eventuate-tram-sagas-reactive-examples-customers-and-orders". I found that the reactive version doesn't update the state_name, end_state correctly when the saga completed successfully.

The bug is on line 233 where sendCommands could return empty stream. Also, line 234 should be a flatMap instead of map.
https://github.com/eventuate-tram/eventuate-tram-sagas/blob/master/eventuate-tram-sagas-reactive-orchestration/src/main/java/io/eventuate/tram/sagas/reactive/orchestration/ReactiveSagaManagerImpl.java#L233

Mono<SagaActions<Data>> nextActions = sagaCommandProducer
                .sendCommands(this.getSagaType(), sagaId, acts.getCommands(), this.makeSagaReplyChannel())
                .map(Optional::of)
                .defaultIfEmpty(Optional.empty())
                .flatMap(lastId -> {
                  sagaInstance.setLastRequestId(lastId.orElse(null));
                  updateState(sagaInstance, acts);
                  sagaInstance.setSerializedSagaData(SagaDataSerde.serializeSagaData(acts.getUpdatedSagaData().orElse(sagaData)));
                  if (acts.isEndState()) {
                    return performEndStateActions(sagaId, sagaInstance, acts.isCompensating(), sagaData).thenReturn(lastId);
                  }
                  return Mono.just(lastId);
                })
                .then(Mono.defer(() -> sagaInstanceRepository.update(sagaInstance)))
                .then(Mono.defer(() -> {
                  if (!acts.isLocal()) return Mono.empty();
                  else return Mono.just(acts);
                }))
                .flatMap(newActs ->
                  Mono.from(getStateDefinition()
                          .handleReply(newActs.getUpdatedState().get(),
                                  newActs.getUpdatedSagaData().get(),
                                  MessageBuilder
                                          .withPayload("{}")
                                          .withHeader(ReplyMessageHeaders.REPLY_OUTCOME, CommandReplyOutcome.SUCCESS.name())
                                          .withHeader(ReplyMessageHeaders.REPLY_TYPE, Success.class.getName())
                                          .build())));

after handling the empty stream like above, it is working correctly like the blocking version.

Is possible run a sequence of compensation?

Now, I config a saga dsl as below:

       step()
           .invokeParticipant(this::step1)
           .withCompensation(this::compensation1)
       .step()
           .invokeParticipant(this::step2)
           .withCompensation(this::compensation2)
       .step()
           .invokeParticipant(this::step3)
       .build();

When the step 3 fail, I want the compensation1 and compensation2 are run. But now it seen maybe only compensation2 is run as default.

Anyone have a solution?

Is it Possible to have Multi Instances of CDC Services?

can we deploy CDC Services using MySQL Bin Log to multiple Nodes and still works in co-ordination or it has to be single instance running so that it doesn't mess up ? Maintaining the Single instance is easy in Docker but when it comes to OpenStack IaaS based deployment; we have to manage the Multiple Instances. A Guidance would be appreciated.

Send a reply outside of the SagaCommandHandler

First of all, thank you for the framework Chris, you did a fantastic job.

I intend to use eventuate to handle movie renting transactions, I follow your recommendation to use the orchestrated sagas, I like to have the business of the transaction centralized in one place.

I'm facing an issue with our workflow, in the the middle of the saga we have to wait until the UI confirm that the video stream is available to bill the customer, that's break the command/reply principle of the orchestration and sounds like a choreography interaction.

I found a way to send the reply whenever the confirm message arrives thru the io.eventuate.tram.messaging.producer.common.MessageProducerImplementation class, when the ConfimStreamCommand arrives I persist the message in a DB table that I added and I use the message later to construct the reply.

This works but I'm wondering if there is a cleaner way to do it thru the framework.

In-memory tests failed

test failed: o.eventuate.examples.tram.sagas.ordersandcustomers.integrationtests.micronaut > OrdersAndCustomersInMemoryIntegrationTest

initializationError

Caused by: io.micronaut.context.exceptions.NoSuchBeanException: No bean of type [javax.persistence.EntityManager] exists

https://gist.github.com/dartartem/62ee7c5104161642830a56c4c8d015f6

I found the change that produced that error: dartartem/eventuate-common-1@42fc179

I rebuilt snapshots and retest to confirm, without this dartartem/eventuate-common-1@42fc179#diff-d7d990cf61eaeeb108e551894e03ecfbR15 tests passed.

Then I found that tests worked without that change because there is real datasource configured:
https://github.com/eventuate-tram/eventuate-tram-sagas/blob/master/orders-and-customers-micronaut-in-memory-integration-tests/src/test/resources/application.yml#L2

For some reason custom datasource bean creates the error on replacement. (Or if just remove the real datasource)
Unfortunately I did not find way to solve it.
Looks like micronaut does not allow to replace datasource bean for jpa.
But since we have tests on real database probably we can remove in-memory tests?

How to work with other message broker

Hi,

I see the description attached to Kafka as message broker. Is there any support in the near future so that users can specify message broker at his will.
For example, I am using K8S in GCP which has google-cloud-PubSub service. I do not want to use dedicated Kafka pods installed in K8s since I have to maintain it manually even though with K8S help.

Thanks

Support nested sagas

Currently

  • a Saga command handler returns a reply message.
  • The command handler cannot be implemented by a saga.

Proposed:

Support saga command handlers that

  • Return void
  • Have a parameter - SagaReplyInfo - that contains the data needed to construct a reply, e.g. message headers etc.
  • The SagaReplyInfo can be passed to a Saga
  • A Saga's completion callbacks onXXX() - can use the SagaReplyInfo to send a reply to the command

Eventuate CDC isn't working with my own Postgres Database

Hi all,

I just want to start off by saying how much I like your framework, even though I haven't got my sagas working yet, Eventuate Sagas makes developing the sagas much easier rather than having to implement all of this stuff ourselves!

Just a quick question about the Eventuate CDC (from your Docker image) - do I need to link this and all of my spring cloud services to a Postgres database that runs on your image specifically? I thought the only difference between the DB created in the saga-customers-and-orders-example https://github.com/eventuate-tram/eventuate-tram-sagas-examples-customers-and-orders was that some tables were already created - and that the CDC would just create them anyway? Or does it not work like that?

Here is my stacktrace coming from the CDC:

java.lang.RuntimeException: Cannot get table io.eventuate.local.common.SchemaAndTable@50514dec[schema=eventuate,tableName=message]: result set is empty
        at io.eventuate.local.polling.PollingDao.queryPrimaryKey(PollingDao.java:235) [eventuate-local-java-cdc-connector-polling-0.10.0-SNAPSHOT.jar!/:na]
        at io.eventuate.local.polling.PollingDao.lambda$getPrimaryKey$4(PollingDao.java:208) [eventuate-local-java-cdc-connector-polling-0.10.0-SNAPSHOT.jar!/:na]
        at io.eventuate.local.common.DaoUtils.handleConnectionLost(DaoUtils.java:22) ~[eventuate-local-java-cdc-connector-common-0.10.0-SNAPSHOT.jar!/:na]
        at io.eventuate.local.polling.PollingDao.getPrimaryKey(PollingDao.java:206) [eventuate-local-java-cdc-connector-polling-0.10.0-SNAPSHOT.jar!/:na]
        at io.eventuate.local.polling.PollingDao.processEvents(PollingDao.java:113) [eventuate-local-java-cdc-connector-polling-0.10.0-SNAPSHOT.jar!/:na]
        at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_252]
        at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) ~[na:1.8.0_252]
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482) ~[na:1.8.0_252]
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) ~[na:1.8.0_252]
        at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) ~[na:1.8.0_252]
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_252]
        at java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:541) ~[na:1.8.0_252]
        at io.eventuate.local.polling.PollingDao.start(PollingDao.java:93) [eventuate-local-java-cdc-connector-polling-0.10.0-SNAPSHOT.jar!/:na]
        at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_252]

2021-01-20 06:05:26.708 ERROR 6 --- [       Thread-6] io.eventuate.local.common.DaoUtils       : Could not access database Cannot get table io.eventuate.local.common.SchemaAndTable@50514dec[schema=eventuate,tableName=message
]: result set is empty - retrying in 500 milliseconds

In my Kubernetes Deployment for this, I have pointed at the image to it's default tag, like this eventuateio/eventuate-cdc-service - do I need to specify a specific version number as well?

Also, do all of my services still need to be linked to the same database or is that happening just for less complication for the example?

Thanks,

Ben

java.lang.RuntimeException: No method for io.eventuate.tram.messaging.common.MessageImpl@5ae52f04

@cer

I have created all the eventuate framework tables and order and also customer micro service specific tables in the same customer_order_service's eventuate schema.

image

On http://localhost:8082/api/orders POST call getting below error in customer-service micro service.

image

java.lang.RuntimeException: No method for io.eventuate.tram.messaging.common.MessageImpl@5ae52f04[payload={"orderId":2,"orderTotal":{"amount":210.0},"customerId":2},headers={command_saga_id=00000186da5861a3-0000000000e00000, PARTITION_ID=849aa8cc-12b3-45b6-9a54-85611a3fdc44, DATE=Mon, 13 Mar 2023 09:41:26 GMT, command_type=com.mt.unity.orderservice.saga.apimessaging.command.ReserveCreditCommand, command_reply_to=com.mt.unity.orderservice.saga.CreateOrderSaga-reply, DESTINATION=customerService, command_saga_type=com.mt.unity.orderservice.saga.CreateOrderSaga, command__destination=customerService, ID=00000186da58a0af-0000000000e00000}]
	at io.eventuate.tram.commands.consumer.CommandDispatcher.messageHandler(CommandDispatcher.java:61) ~[eventuate-tram-commands-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.sagas.participant.SagaCommandDispatcher.messageHandler(SagaCommandDispatcher.java:38) ~[eventuate-tram-sagas-participant-0.20.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.common.DecoratedMessageHandlerFactory.lambda$decorate$0(DecoratedMessageHandlerFactory.java:34) ~[eventuate-tram-consumer-common-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.common.PrePostHandlerMessageHandlerDecorator.accept(PrePostHandlerMessageHandlerDecorator.java:27) ~[eventuate-tram-consumer-common-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.common.PrePostHandlerMessageHandlerDecorator.accept(PrePostHandlerMessageHandlerDecorator.java:12) ~[eventuate-tram-consumer-common-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.common.MessageHandlerDecoratorChainBuilder.lambda$buildChain$0(MessageHandlerDecoratorChainBuilder.java:33) ~[eventuate-tram-consumer-common-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.common.DuplicateDetectingMessageHandlerDecorator.lambda$accept$0(DuplicateDetectingMessageHandlerDecorator.java:16) ~[eventuate-tram-consumer-common-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.jdbc.TransactionalNoopDuplicateMessageDetector.lambda$doWithMessage$0(TransactionalNoopDuplicateMessageDetector.java:28) ~[eventuate-tram-consumer-jdbc-0.31.0.RELEASE.jar:na]
	at io.eventuate.common.spring.jdbc.EventuateSpringTransactionTemplate.lambda$executeInTransaction$0(EventuateSpringTransactionTemplate.java:18) ~[eventuate-common-spring-jdbc-0.16.0.RELEASE.jar:na]
	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140) ~[spring-tx-6.0.6.jar:6.0.6]
	at io.eventuate.common.spring.jdbc.EventuateSpringTransactionTemplate.executeInTransaction(EventuateSpringTransactionTemplate.java:18) ~[eventuate-common-spring-jdbc-0.16.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.jdbc.TransactionalNoopDuplicateMessageDetector.doWithMessage(TransactionalNoopDuplicateMessageDetector.java:26) ~[eventuate-tram-consumer-jdbc-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.common.DuplicateDetectingMessageHandlerDecorator.accept(DuplicateDetectingMessageHandlerDecorator.java:16) ~[eventuate-tram-consumer-common-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.common.DuplicateDetectingMessageHandlerDecorator.accept(DuplicateDetectingMessageHandlerDecorator.java:6) ~[eventuate-tram-consumer-common-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.common.MessageHandlerDecoratorChainBuilder.lambda$buildChain$0(MessageHandlerDecoratorChainBuilder.java:33) ~[eventuate-tram-consumer-common-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.common.PrePostReceiveMessageHandlerDecorator.accept(PrePostReceiveMessageHandlerDecorator.java:26) ~[eventuate-tram-consumer-common-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.common.PrePostReceiveMessageHandlerDecorator.accept(PrePostReceiveMessageHandlerDecorator.java:12) ~[eventuate-tram-consumer-common-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.common.MessageHandlerDecoratorChainBuilder.lambda$buildChain$0(MessageHandlerDecoratorChainBuilder.java:33) ~[eventuate-tram-consumer-common-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.common.MessageConsumerImpl.lambda$subscribe$0(MessageConsumerImpl.java:40) ~[eventuate-tram-consumer-common-0.31.0.RELEASE.jar:na]
	at io.eventuate.tram.consumer.kafka.EventuateTramKafkaMessageConsumer.lambda$subscribe$0(EventuateTramKafkaMessageConsumer.java:29) ~[eventuate-tram-consumer-kafka-0.31.0.RELEASE.jar:na]
	at io.eventuate.messaging.kafka.consumer.MessageConsumerKafkaImpl.lambda$subscribe$0(MessageConsumerKafkaImpl.java:46) ~[eventuate-messaging-kafka-consumer-0.16.0.RELEASE.jar:na]
	at io.eventuate.messaging.kafka.consumer.MessageConsumerKafkaImpl.handle(MessageConsumerKafkaImpl.java:99) ~[eventuate-messaging-kafka-consumer-0.16.0.RELEASE.jar:na]
	at io.eventuate.messaging.kafka.consumer.MessageConsumerKafkaImpl.lambda$subscribeWithReactiveHandler$1(MessageConsumerKafkaImpl.java:59) ~[eventuate-messaging-kafka-consumer-0.16.0.RELEASE.jar:na]
	at io.eventuate.messaging.kafka.consumer.SwimlaneDispatcher.processQueuedMessage(SwimlaneDispatcher.java:72) ~[eventuate-messaging-kafka-consumer-0.16.0.RELEASE.jar:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
	at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

Improve exception handling for local saga steps

Current behavior

If a local step throws an exception the saga is rolled back.

Application code, e.g. onSagaRolledBack() does not have access to the exception and so cannot doesn't know the reason.

Also, the rollback occurs for any exception, not just 'business-related' exceptions. Consequently, a transient technical failure will result in a rollback

Proposal

            .step()
                  .invokeLocal(steps::approveOrder)
                    .onException(InvalidOrderException.class, LocalExceptionCreateOrderSagaData::saveInvalidOrder)
                    .onExceptionRollback(InvalidOrderException.class)
  • onException() is the equivalent of a try-catch that updates the saga data with the reason for the error. Note: an exception can't be simply stored in the saga data because of the requirement to (de)serialize to/from JSON
  • onExceptionRollback()
    • specifies those exceptions that should cause a rollback.
    • other exceptions are assumed to indicate a transient technical failure and are rethrown by the saga logic - saga creation or reply handling will fail
    • if onExceptionRollback() is not used, then all exceptions cause a rollback (original behavior)

Make sagas work with devTools

Problem:

  • devtools uses it's own classloader
  • eventuate libraries use a different classloader
  • DevTools can cause errors such as java.lang.ClassCastException: class X cannot be cast to class X - X and X are from different class loaders

This is the likely solution: spring-projects/spring-boot#3805

Eventuate libraries (including JSonMapper) need to use ThreadLocal.getThread().getContextClassLoader()

ReactiveSaga not started if i use PostgreSQL

Hi @cer. I set up a project with reactive saga with PostgreSQL DB. But when I try to create a new saga instance I got:

java.lang.RuntimeException: org.springframework.dao.InvalidDataAccessApiUsageException: No parameter specified for [param5] in query [INSERT INTO eventuate.saga_instance(saga_type, saga_id, state_name, last_request_id, saga_data_type, saga_data_json, end_state, compensating, failed) VALUES(:param1, :param2, :param3, NULL, :param5, :param6, :param7, :param8, :param9)] at io.eventuate.common.spring.jdbc.reactive.EventuateSpringReactiveJdbcStatementExecutor.handleDuplicateKeyException(EventuateSpringReactiveJdbcStatementExecutor.java:127) ~[eventuate-common-spring-reactive-jdbc-0.16.0.RELEASE.jar:na
I debug your framework and found that you have a custom logic for postgres null parameters but looks like this code have a bug.

My setup:
spring boot version 2.7.9
pg image: eventuateio/eventuate-tram-sagas-mysql:0.20.0.RELEASE
eventuate dependencies:
implementation(platform("io.eventuate.platform:eventuate-platform-dependencies:2022.2.RELEASE"))
implementation("io.eventuate.tram.core:eventuate-tram-spring-reactive-jdbc-kafka")
implementation("io.eventuate.tram.sagas:eventuate-tram-sagas-spring-reactive-orchestration-simple-dsl-starter")

Hint: I tried change DB to MySQL and I don't get this error but in my case I need PostgreSql

Retry execution of command handler instead of triggering compensation

Hi,

how can we retry the execution of a command handler method in case of failure instead of triggering the compensation logic immediately?

public CommandHandlers commandHandlerDefinitions() {
  return SagaCommandHandlersBuilder.fromChannel("CHANNEL_NAME")
      .onMessage(Command.class, this::handleCommand)
      .build();
}

public Message handleCommand(final CommandMessage<Command> command) {
  try {
    
    //logic
    return withSuccess();
} catch (Exception e) {

   // in case of failure, I want to retry the method instead of directly triggering the compensations action
   return withFailure(SagaError.of(e));
}

No qualifying bean of type 'io.eventuate.tram.sagas.orchestration.SagaInstanceFactory' available

I am new to SAGA design pattern and just started to try some sample with your eventuate tram saga framework but I am unable to build project due to below issues.
Please help me to solve this issue.

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.eventuate.tram.sagas.orchestration.SagaInstanceFactory' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

Github link:
https://github.com/eventuate-tram/eventuate-tram-sagas-examples-customers-and-orders/blob/master/order-service/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/service/OrderSagaService.java

My Reference Link:
https://eventuate.io/docs/manual/eventuate-tram/latest/getting-started-eventuate-tram-sagas.html

Added dependencies:

           <properties>
	<java.version>17</java.version>		
	<eventuateTramVersion>0.31.0.RELEASE</eventuateTramVersion>
	<eventuateTramSagasVersion>0.20.0.RELEASE</eventuateTramSagasVersion>		
	</properties>
	
	<dependency>
		<groupId>io.eventuate.tram.sagas</groupId>
		<artifactId>eventuate-tram-sagas-spring-orchestration-simple-dsl-starter</artifactId>
		<version>${eventuateTramSagasVersion}</version>
	</dependency>
	
	<dependency>
		<groupId>io.eventuate.tram.core</groupId>
		<artifactId>eventuate-tram-spring-jdbc-kafka</artifactId>
		<version>${eventuateTramVersion}</version>
	</dependency>


     @Service
      public class OrderSagaService {

  @Autowired
  private OrderRepository orderRepository;

  @Autowired
  private SagaInstanceFactory sagaInstanceFactory;

  @Autowired
  private CreateOrderSaga createOrderSaga;	

  @Transactional
  public Order createOrder(OrderDetails orderDetails) {
    CreateOrderSagaData data = new CreateOrderSagaData(orderDetails);
    sagaInstanceFactory.create(createOrderSaga, data);
    return orderRepository.findById(data.getOrderId()).get();
  }
}

What i have tried:
I added below import statement:
@import(SagaOrchestratorConfiguration.class)
public class OrderServiceApplication {

      public static void main(String[] args) {
	      SpringApplication.run(OrderServiceApplication.class, args);
      }	 
  }

But getting below issues:
'io.eventuate.tram.messaging.producer.MessageProducer' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'orderSagaService': Unsatisfied dependency expressed through field 'sagaInstanceFactory': Error creating bean with name 'sagaInstanceFactory' defined in io.eventuate.tram.sagas.spring.orchestration.SagaOrchestratorConfiguration: Unsatisfied dependency expressed through method 'sagaInstanceFactory' parameter 1: Error creating bean with name 'commandProducer' defined in io.eventuate.tram.spring.commands.producer.TramCommandProducerConfiguration: Unsatisfied dependency expressed through method 'commandProducer' parameter 0: No qualifying bean of type 'io.eventuate.tram.messaging.producer.MessageProducer' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sagaInstanceFactory' defined in io.eventuate.tram.sagas.spring.orchestration.SagaOrchestratorConfiguration: Unsatisfied dependency expressed through method 'sagaInstanceFactory' parameter 1: Error creating bean with name 'commandProducer' defined in io.eventuate.tram.spring.commands.producer.TramCommandProducerConfiguration: Unsatisfied dependency expressed through method 'commandProducer' parameter 0: No qualifying bean of type 'io.eventuate.tram.messaging.producer.MessageProducer' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'commandProducer' defined in io.eventuate.tram.spring.commands.producer.TramCommandProducerConfiguration: Unsatisfied dependency expressed through method 'commandProducer' parameter 0: No qualifying bean of type 'io.eventuate.tram.messaging.producer.MessageProducer' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.eventuate.tram.messaging.producer.MessageProducer' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

Refactor to reduce copy/paste in SQL code

These lines are duplicated in non-reactive repository:

sagaInstance.getSagaType(),
sagaInstance.getId(),
sagaInstance.getStateName(),
sagaInstance.getLastRequestId(),
sagaInstance.getSerializedSagaData().getSagaDataType(),
sagaInstance.getSerializedSagaData().getSagaDataJSON(),
sagaInstance.isEndState(),
sagaInstance.isCompensating())

Consider moving into SagaInstanceRepositorySql and renaming it to ...Mapper.

Orchestrator saga: postpone participant's response

From email thread:

Is it possible to postpone a participant's response in the orchestrator saga?
One of my saga's participants is connecting to an external service. When the participant receives the command it send a file to the external service. The service's response to the file could take hours or days, so I cannot send command reply (withSuccess/withFailure) immediately. The response is important because it effects the outcome of the saga.
Can I separate the command consumption and response generation somehow? I would like my saga to stop and wait for the participant's response (which waits for external system's response) before going to the next step.

In case of unhandled exception Eventuate tram Saga gets blocked

To replicate the issue below are process:-
We are connecting 3 services in Saga flow
while executing the saga we are facing some unhandled exceptions, which can't be handled by try-catch.

  1. try to trigger the negative case by payload, by sending an improper message in Kafka to the command handler eg:-
  • Command Sent from e.g. CreateOrderSaga into the channel (customerService).
  • And Command Received by channel "customerService" in "CustomerCommandHandler".
  • An Unhandled exception gets thrown at method call customer.reserveCredit().
  1. After executing the failure case, we can't execute the positive case or any case as it will not allow running the flow due to eventuate being blocked.

Please refer example

public Message reserveCredit(CommandMessage<ReserveCreditCommand> cm) {
    ReserveCreditCommand cmd = cm.getCommand();
    long customerId = cmd.getCustomerId();
    Customer customer = customerDao.findById(customerId);
    // TODO null check
    try {
      customer.reserveCredit(cmd.getOrderId(), cmd.getOrderTotal()); //An Unhandled exception occurs here.
      return withSuccess(new CustomerCreditReserved());
    } catch (CustomerCreditLimitExceededException e) {
      return withFailure(new CustomerCreditReservationFailed());
    }
  }

@cer @dartartem We are looking for a resolution for these kinds of cases when exceptions are not caught inside command handles and Saga got blocked as a result.

Please refer to "Health check endpoint" in the below doc

https://eventuate.io/docs/manual/eventuate-tram/latest/cdc-configuration.html

build 169 failed, permission denied to execute save-containers-and-tests.sh

See: https://app.circleci.com/pipelines/github/eventuate-tram/eventuate-tram-sagas/61/workflows/b19a9c4c-7810-46d8-95db-7afaf06f9793/jobs/169

Script: https://github.com/eventuate-tram/eventuate-tram-sagas/blob/878e60d92d6d49e0d3eef55f363a45ac8371751f/.circleci/upgrade-docker-compose.sh

Failed to execute here:

command: ./.circleci/save-containers-and-tests.sh

With error:
/bin/bash: ./.circleci/save-containers-and-tests.sh: Permission denied

However permissions are:

ls -lt ./.circleci/save-containers-and-tests.sh
-rwxr-xr-x  1 cer  staff  345 Feb 16 07:55 ./.circleci/save-containers-and-tests.sh

NoSuchBeanDefinitionException: No qualifying bean of type 'io.eventuate.common.id.IdGenerator' available

Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:123)
	at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190)
	at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244)
	at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:98)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$5(ClassBasedTestDescriptor.java:341)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:346)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$6(ClassBasedTestDescriptor.java:341)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
	at java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:312)
	at java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:743)
	at java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:742)
	at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:340)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:263)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$2(ClassBasedTestDescriptor.java:256)
	at java.util.Optional.orElseGet(Optional.java:267)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$3(ClassBasedTestDescriptor.java:255)
	at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:29)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:108)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:107)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:71)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$1(NodeTestTask.java:107)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:107)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:75)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
	at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
	at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy2.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:132)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:413)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sagaListener': Unsatisfied dependency expressed through field 'sagaInstanceFactory'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sagaInstanceFactory' defined in class path resource [io/eventuate/tram/sagas/spring/orchestration/SagaOrchestratorConfiguration.class]: Unsatisfied dependency expressed through method 'sagaInstanceFactory' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sagaInstanceRepository' defined in class path resource [io/eventuate/tram/sagas/spring/orchestration/SagaOrchestratorConfiguration.class]: Unsatisfied dependency expressed through method 'sagaInstanceRepository' parameter 1; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.eventuate.common.id.IdGenerator' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:643)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:895)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
	at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
	... 88 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sagaInstanceFactory' defined in class path resource [io/eventuate/tram/sagas/spring/orchestration/SagaOrchestratorConfiguration.class]: Unsatisfied dependency expressed through method 'sagaInstanceFactory' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sagaInstanceRepository' defined in class path resource [io/eventuate/tram/sagas/spring/orchestration/SagaOrchestratorConfiguration.class]: Unsatisfied dependency expressed through method 'sagaInstanceRepository' parameter 1; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.eventuate.common.id.IdGenerator' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:798)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:539)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
	... 107 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sagaInstanceRepository' defined in class path resource [io/eventuate/tram/sagas/spring/orchestration/SagaOrchestratorConfiguration.class]: Unsatisfied dependency expressed through method 'sagaInstanceRepository' parameter 1; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.eventuate.common.id.IdGenerator' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:798)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:539)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:885)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:789)
	... 120 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.eventuate.common.id.IdGenerator' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1716)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1272)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:885)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:789)
	... 134 more

Implement a participant timeout mechanism

Something like:

          .step()
            .invokeParticipant(this::reserveCredit)
            .withTimeout(timeout, this::cancelReserveCredit)

The arguments to withTimeout() are

  • timeout - e.g. java.time.Duration
  • this::cancelReserveCredit - generates the message to send to the customer service to cancel the reserveCredit

Eventuate Tram Sagas artifacts are not properly replicated to jcenter

Unfortunately, you need to get them from the bintray repository: https://dl.bintray.com/eventuateio-oss/eventuate-maven-release

Gradle build.gradle:

repositories {

        maven {
            url 'https://dl.bintray.com/eventuateio-oss/eventuate-maven-release'
        }
}

Maven pom.xml:

  <repositories>
    <repository>
      <id>eventuate</id>
      <name>Eventuate repo</name>
      <url>https://dl.bintray.com/eventuateio-oss/eventuate-maven-release</url>
    </repository>
  </repositories>

ERROR: relation "eventuate.saga_instance" does not exist

Stack Trace:
2023-03-09T04:53:23.687+05:30 ERROR 40264 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [INSERT INTO eventuate.saga_instance(saga_type, saga_id, state_name, last_request_id, saga_data_type, saga_data_json, end_state, compensating) VALUES(?, ?, ?, ?, ?, ?, ?, ?)]] with root cause

org.postgresql.util.PSQLException: ERROR: relation "eventuate.saga_instance" does not exist
Position: 13
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2676) ~[postgresql-42.5.4.jar:42.5.4]
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2366) ~[postgresql-42.5.4.jar:42.5.4]
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:356) ~[postgresql-42.5.4.jar:42.5.4]
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:496) ~[postgresql-42.5.4.jar:42.5.4]
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:413) ~[postgresql-42.5.4.jar:42.5.4]
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:190) ~[postgresql-42.5.4.jar:42.5.4]
at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:152) ~[postgresql-42.5.4.jar:42.5.4]
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) ~[HikariCP-5.0.1.jar:na]
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) ~[HikariCP-5.0.1.jar:na]
at org.springframework.jdbc.core.JdbcTemplate.lambda$update$2(JdbcTemplate.java:965) ~[spring-jdbc-6.0.6.jar:6.0.6]
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:651) ~[spring-jdbc-6.0.6.jar:6.0.6]
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:960) ~[spring-jdbc-6.0.6.jar:6.0.6]
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:1015) ~[spring-jdbc-6.0.6.jar:6.0.6]
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:1025) ~[spring-jdbc-6.0.6.jar:6.0.6]
at io.eventuate.common.common.spring.jdbc.EventuateSpringJdbcStatementExecutor.update(EventuateSpringJdbcStatementExecutor.java:23) ~[eventuate-common-common-spring-jdbc-0.9.0.RELEASE.jar:na]
at io.eventuate.tram.sagas.orchestration.SagaInstanceRepositoryJdbc.save(SagaInstanceRepositoryJdbc.java:89) ~[eventuate-tram-sagas-orchestration-0.13.0.RELEASE.jar:na]
at io.eventuate.tram.sagas.orchestration.SagaManagerImpl.create(SagaManagerImpl.java:87) ~[eventuate-tram-sagas-orchestration-0.13.0.RELEASE.jar:na]
at io.eventuate.tram.sagas.orchestration.SagaManagerImpl.create(SagaManagerImpl.java:69) ~[eventuate-tram-sagas-orchestration-0.13.0.RELEASE.jar:na]
at io.eventuate.tram.sagas.orchestration.SagaInstanceFactory.create(SagaInstanceFactory.java:21) ~[eventuate-tram-sagas-orchestration-0.13.0.RELEASE.jar:na]
at com.mt.unity.orderservice.saga.OrderSagaService.createOrder(OrderSagaService.java:37) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-6.0.6.jar:6.0.6]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.0.6.jar:6.0.6]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.0.6.jar:6.0.6]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-6.0.6.jar:6.0.6]

Application.yml
spring:
datasource:
driver-class-name: org.postgresql.Driver
username: postgres
password: admin
url: jdbc:postgresql://${DATABASE_HOST:localhost}:5432/${DB_POSTGRES_DATABASE_NAME:orders}
jpa:
properties:
'[hibernate.default_schema]': public note: I used eventuate instead of public but that also doesn't work.
show-sql: true
generate-ddl: true
hibernate:
ddl-auto: update
database: postgresql

eventuate dependencies

eventuatelocal.kafka.bootstrap.servers: ${DOCKER_HOST_IP:localhost}:9092
eventuatelocal.zookeeper.connection.string: ${DOCKER_HOST_IP:localhost}:2181

Main Class:
@SpringBootApplication
@slf4j
@import({
SagaOrchestratorConfiguration.class,
TramMessageProducerJdbcConfiguration.class,
EventuateTramKafkaMessageConsumerConfiguration.class
})
public class OrderServiceApplication {

public static void main(String[] args) {
	SpringApplication.run(OrderServiceApplication.class, args);
}

@Bean
public ChannelMapping channelMapping() {
	return DefaultChannelMapping.builder().build();
}

//@Bean
//public DuplicateMessageDetector duplicateMessageDetector() {
//	return new NoopDuplicateMessageDetector();
//}

   @Bean
public DuplicateMessageDetector duplicateMessageDetector(EventuateSchema eventuateSchema,
        //String currentTimeInMillisecondsSql,
        EventuateJdbcStatementExecutor eventuateJdbcStatementExecutor,
        EventuateTransactionTemplate eventuateTransactionTemplate) {
	return new SqlTableBasedDuplicateMessageDetector(eventuateSchema, null, eventuateJdbcStatementExecutor, eventuateTransactionTemplate);
}

@Bean
public HttpMessageConverters customConverters() {
	HttpMessageConverter<?> additional = new MappingJackson2HttpMessageConverter();
	return new HttpMessageConverters(additional);
}

@Bean
public OncePerRequestFilter logFilter() {
	return new OncePerRequestFilter() {
		@Override
		protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
				FilterChain filterChain) throws ServletException, IOException {
			log.info("Path: {}", request.getRequestURI());
			filterChain.doFilter(request, response);
			log.info("Path: {} {}", request.getRequestURI(), response.getStatus());
		}
	};
}

}

Pom.xml

<java.version>17</java.version>
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
0.24.0.RELEASE
0.13.0.RELEASE



	<dependency>
		<groupId>io.eventuate.tram.sagas</groupId>
		<artifactId>eventuate-tram-sagas-spring-orchestration-simple-dsl</artifactId>
		<version>${eventuateTramSagasVersion}</version>
	</dependency>

	<!-- Eventuate Tram dependencies -->
	<dependency>
		<groupId>io.eventuate.tram.core</groupId>
		<artifactId>eventuate-tram-spring-jdbc-kafka</artifactId>
		<version>${eventuateTramVersion}</version>
	</dependency>
	<dependency>
		<groupId>io.eventuate.tram.core</groupId>
		<artifactId>eventuate-tram-spring-optimistic-locking</artifactId>
		<version>${eventuateTramVersion}</version>
	</dependency>

Consider defining a bean of type 'io.eventuate.common.id.IdGenerator' in your configuration

Hello,
Getting below issues while setting up SAGA , plain setup even without any code:

Issues no1:
Parameter 1 of method sagaInstanceRepository in io.eventuate.tram.sagas.spring.orchestration.SagaOrchestratorConfiguration required a bean of type 'io.eventuate.common.id.IdGenerator' that could not be found.

The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
- @org.springframework.beans.factory.annotation.Qualifier("xyzSagaServiceImpl")

Dependency used for Issue 1:

 <dependency>
 	<groupId>org.springframework.boot</groupId>
 	<artifactId>spring-boot-starter-data-jpa</artifactId>
 </dependency>


<dependency>
    <groupId>io.eventuate.tram.sagas</groupId>
    <artifactId>eventuate-tram-sagas-spring-orchestration</artifactId>
    <version>${eventuateTramSagasVersion}</version>
</dependency>



<dependency>
	<groupId>io.eventuate.tram.sagas</groupId>
	<artifactId>eventuate-tram-sagas-spring-orchestration-simple-dsl</artifactId>
	<version>${eventuateTramSagasVersion}</version>
</dependency>

<dependency>
	<groupId>io.eventuate.tram.core</groupId>
	<artifactId>eventuate-tram-spring-optimistic-locking</artifactId>
	<version>${eventuateTramVersion}</version>
</dependency>

<dependency>
	<groupId>io.eventuate.tram.core</groupId>
	<artifactId>eventuate-tram-spring-commands</artifactId>
	<version>${eventuateTramVersion} </version>
</dependency>
<dependency>
	<groupId>io.eventuate.tram.core</groupId>
	<artifactId>eventuate-tram-spring-producer-jdbc</artifactId>
	<version>${eventuateTramVersion} </version>
</dependency>


<dependency>
	<groupId>io.eventuate.tram.core</groupId>
	<artifactId>eventuate-tram-spring-consumer-kafka</artifactId>
	<version>${eventuateTramVersion} </version>
</dependency>


<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<version>9.4-1200-jdbc41</version>
	<exclusions>
    <exclusion>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
    </exclusion>
</exclusions>
</dependency>

Issues No2:
below error comes while including

io.eventuate.tram.core eventuate-tram-spring-consumer-jdbc ${eventuateTramVersion}

Parameter 2 of method duplicateMessageDetector in io.eventuate.tram.spring.consumer.jdbc.TramConsumerJdbcAutoConfiguration required a bean of type 'io.eventuate.common.jdbc.EventuateJdbcStatementExecutor' that could not be found.

Proposal: 'Global' failure handlers for failed participants

Currently:

  • If a SagaParticipant sends back a failure message, step() onReply() must be used to update the SagaData with the error
  • This can be repetitive if the application has a standardized way to handle errors

Proposed:

Define Spring @beans that implement the following interface:

interface SagaParticipantFailureHandler<T SagaData> extends Ordered   {
     void handleFailure(T, SagaData sagaData);
}

If a saga participant throws an exception, the first (sorted by Ordered) applicable - to exception and saga data - is invoked to update the SagaData prior to starting compensation.

Analogous to #86

Eventuate sagas not well described

I tried to start basic saga - but i did not find how to start it.
In documentation: https://eventuate.io/docs/manual/eventuate-tram/latest/getting-started-eventuate-tram-sagas.html#getting-started-tram-sagas

I see:

Creating an saga orchestrator

The OrderService creates the saga:

public class OrderService {

@Autowired
private SagaManager createOrderSagaManager;

@Autowired
private OrderRepository orderRepository;

@transactional
public Order createOrder(OrderDetails orderDetails) {
ResultWithEvents 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;
}
}

But i only wanted to create saga, so i need to use
createOrderSagaManager.create(data); ?
Why it is not described in documentation?
I am wondering what this code actually do - i can not find any information in the documentation.
Can i start Saga with passing some DTO wihout these lines:

ResultWithEvents<Order> oe = Order.createOrder(orderDetails);
Order order = oe.result;
orderRepository.save(order);

? Can you add video when you tell how this framework work line by line?

And this:
@Autowired private SagaManager<CreateOrderSagaData> createOrderSagaManager;

From where is this createOrderSagaManager injected?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.