├── .circleci ├── config.yml ├── save-containers-and-tests.sh ├── setenv-circle-ci.sh └── target-tag.sh ├── .gitignore ├── LICENSE.md ├── README.adoc ├── _build-and-test-all.sh ├── build-and-test-all-mssql.sh ├── build-and-test-all-multi-arch-locally-mysql-kafka.sh ├── build-and-test-all-mysql-activemq.sh ├── build-and-test-all-mysql-kafka.sh ├── build-and-test-all-postgres.sh ├── build-and-test-everything.sh ├── build-multi-arch-images.sh ├── build.gradle ├── deploy-artifacts.sh ├── deploy-multi-arch.sh ├── docker-compose-mssql.yml ├── docker-compose-mysql-activemq.yml ├── docker-compose-mysql-kafka.yml ├── docker-compose-postgres.yml ├── docker-compose-registry.yml ├── docs └── design │ ├── customers-participant.diagram │ ├── customers-participant.diagram.png │ ├── orders-orchestration.diagram │ └── orders-orchestration.diagram.png ├── eventuate-tram-sagas-common-in-memory ├── build.gradle └── src │ └── main │ └── resources │ └── eventuate-tram-sagas-embedded.sql ├── eventuate-tram-sagas-common ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── common │ │ ├── LockTarget.java │ │ ├── SagaCommandHeaders.java │ │ ├── SagaLockManager.java │ │ ├── SagaLockManagerImpl.java │ │ ├── SagaLockManagerSql.java │ │ ├── SagaReplyHeaders.java │ │ ├── SagaUnlockCommand.java │ │ ├── StashMessageRequiredException.java │ │ └── StashedMessage.java │ └── test │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── common │ ├── SagaLockManagerImplDefaultSchemaTest.java │ └── SagaLockManagerImplSchemaTest.java ├── eventuate-tram-sagas-event-sourcing-support ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── eventsourcingsupport │ ├── AggregateRepositoryInterceptorExceptionHandlerBuilder.java │ ├── CommandMessageAggregateRepositoryInterceptor.java │ ├── SagaReplyRequestedEvent.java │ ├── SagaReplyRequestedEventSubscriber.java │ └── UpdatingOptionsBuilder.java ├── eventuate-tram-sagas-micronaut-common ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── micronaut │ │ └── common │ │ └── EventuateTramSagaCommonFactory.java │ └── test │ ├── java │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── micronaut │ │ └── common │ │ └── SagaLockManagerIntegrationTest.java │ └── resources │ └── application.yml ├── eventuate-tram-sagas-micronaut-configuration-tests ├── build.gradle └── src │ └── test │ ├── java │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── micronaut │ │ └── configuration │ │ └── test │ │ └── SagaMessageProducerConfigurationTest.java │ └── resources │ ├── application-mssql.yml │ ├── application-postgres.yml │ └── application.yml ├── eventuate-tram-sagas-micronaut-in-memory ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── micronaut │ └── inmemory │ └── TramSagaInMemoryFactory.java ├── eventuate-tram-sagas-micronaut-orchestration-simple-dsl └── build.gradle ├── eventuate-tram-sagas-micronaut-orchestration ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── micronaut │ └── orchestration │ ├── SagaManagerImplInitializer.java │ └── SagaOrchestratorFactory.java ├── eventuate-tram-sagas-micronaut-participant ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── micronaut │ └── participant │ └── SagaParticipantFactory.java ├── eventuate-tram-sagas-micronaut-testing-support ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── micronaut │ └── testing │ ├── SagaParticipantStubManagerFactory.java │ └── SagaParticipantStubManagerInitializer.java ├── eventuate-tram-sagas-orchestration-simple-dsl ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── simpledsl │ │ ├── AbstractParticipantInvocation.java │ │ ├── AbstractSagaActionsProvider.java │ │ ├── AbstractSimpleSagaDefinition.java │ │ ├── AbstractStepToExecute.java │ │ ├── CommandEndpoint.java │ │ ├── CommandEndpointBuilder.java │ │ ├── ISagaStep.java │ │ ├── InvokeParticipantStepBuilder.java │ │ ├── LocalExceptionSaver.java │ │ ├── LocalStep.java │ │ ├── LocalStepBuilder.java │ │ ├── ParticipantEndpointInvocationImpl.java │ │ ├── ParticipantInvocation.java │ │ ├── ParticipantInvocationBuilder.java │ │ ├── ParticipantInvocationImpl.java │ │ ├── ParticipantInvocationStep.java │ │ ├── ParticipantParamsAndCommand.java │ │ ├── SagaActionsProvider.java │ │ ├── SagaEndpointInvocation.java │ │ ├── SagaExecutionState.java │ │ ├── SagaExecutionStateJsonSerde.java │ │ ├── SagaStep.java │ │ ├── SimpleSaga.java │ │ ├── SimpleSagaDefinition.java │ │ ├── SimpleSagaDefinitionBuilder.java │ │ ├── SimpleSagaDsl.java │ │ ├── StepBuilder.java │ │ ├── StepOutcome.java │ │ ├── StepToExecute.java │ │ ├── WithCompensationBuilder.java │ │ └── annotations │ │ ├── SagaParticipantOperation.java │ │ └── SagaParticipantProxy.java │ └── test │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── simpledsl │ ├── ConditionalSaga.java │ ├── ConditionalSagaData.java │ ├── ConditionalSagaTest.java │ ├── Do1Command.java │ ├── Handlers.java │ ├── LocalSaga.java │ ├── LocalSagaData.java │ ├── LocalSagaSteps.java │ ├── LocalSagaTest.java │ ├── ReleaseCreditCommand.java │ ├── ReserveCreditCommand.java │ ├── Undo1Command.java │ ├── WithHandlersSaga.java │ ├── WithHandlersSagaTest.java │ ├── localexceptions │ ├── InvalidOrderException.java │ ├── LocalExceptionCreateOrderSaga.java │ ├── LocalExceptionCreateOrderSagaData.java │ ├── LocalExceptionCreateOrderSagaSteps.java │ └── LocalExceptionCreateOrderSagaTest.java │ ├── nested │ ├── InMemoryCommandProducer.java │ ├── InMemorySagaInstanceRepository.java │ ├── InMemorySagaOrchestrationAndParticipants.java │ ├── InnerCommand.java │ ├── InnerSaga.java │ ├── InnerSagaData.java │ ├── NestedSagaTest.java │ ├── OuterSaga.java │ ├── OuterSagaData.java │ └── ParticipantCommandHandlers.java │ └── notifications │ ├── ConditionalNotificationBasedCreateOrderSaga.java │ ├── ConditionalNotificationBasedCreateOrderSagaData.java │ ├── ConditionalNotificationBasedCreateOrderSagaSteps.java │ ├── ConditionalNotificationBasedCreateOrderSagaTest.java │ ├── FulfillOrder.java │ ├── NotificationBasedCreateOrderSaga.java │ ├── NotificationBasedCreateOrderSagaData.java │ ├── NotificationBasedCreateOrderSagaSteps.java │ ├── NotificationBasedCreateOrderSagaTest.java │ ├── ReleaseInventory.java │ └── ReserveInventory.java ├── eventuate-tram-sagas-orchestration ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── orchestration │ │ ├── CommandWithDestinationAndType.java │ │ ├── DestinationAndResource.java │ │ ├── EnlistedAggregate.java │ │ ├── EnlistedAggregatesDao.java │ │ ├── EventClassAndAggregateId.java │ │ ├── EventStartingHandler.java │ │ ├── EventToPublish.java │ │ ├── JdbcSqlQueryRow.java │ │ ├── PendingSagaCommand.java │ │ ├── Saga.java │ │ ├── SagaActions.java │ │ ├── SagaCommandProducer.java │ │ ├── SagaCommandProducerImpl.java │ │ ├── SagaCompletedForAggregateEvent.java │ │ ├── SagaDataSerde.java │ │ ├── SagaDefinition.java │ │ ├── SagaEventHandler.java │ │ ├── SagaInstance.java │ │ ├── SagaInstanceData.java │ │ ├── SagaInstanceFactory.java │ │ ├── SagaInstanceRepository.java │ │ ├── SagaInstanceRepositoryJdbc.java │ │ ├── SagaInstanceRepositorySql.java │ │ ├── SagaManager.java │ │ ├── SagaManagerFactory.java │ │ ├── SagaManagerImpl.java │ │ ├── SagaStateMachineAction.java │ │ ├── SagaStateMachineEventHandler.java │ │ ├── SagaTypeAndId.java │ │ ├── SerializedSagaData.java │ │ ├── SqlQueryRow.java │ │ └── StartingHandler.java │ └── test │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── orchestration │ ├── RuntimeExceptionAnswer.java │ ├── SagaCommandHeadersTest.java │ ├── SagaInstanceFactoryTest.java │ ├── SagaInstanceRepositoryJdbcCustomSchemaTest.java │ ├── SagaInstanceRepositoryJdbcDefaultSchemaTest.java │ ├── SagaInstanceRepositoryJdbcEmptySchemaTest.java │ ├── SagaInstanceRepositoryJdbcSchemaTest.java │ ├── SagaManagerImplTest.java │ ├── TestCommand.java │ ├── TestSaga.java │ └── TestSagaData.java ├── eventuate-tram-sagas-participant ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── participant │ ├── AbstractSagaCommandHandlersBuilder.java │ ├── PostLockFunction.java │ ├── SagaCommandDispatcher.java │ ├── SagaCommandDispatcherFactory.java │ ├── SagaCommandHandler.java │ ├── SagaCommandHandlerBuilder.java │ ├── SagaCommandHandlersBuilder.java │ ├── SagaReplyMessage.java │ └── SagaReplyMessageBuilder.java ├── eventuate-tram-sagas-reactive-common ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── reactive │ └── common │ ├── ReactiveSagaLockManager.java │ └── ReactiveSagaLockManagerImpl.java ├── eventuate-tram-sagas-reactive-orchestration-simple-dsl ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── reactive │ │ └── simpledsl │ │ ├── AbstractReactiveParticipantInvocation.java │ │ ├── InvokeReactiveParticipantStepBuilder.java │ │ ├── ReactiveLocalStep.java │ │ ├── ReactiveLocalStepBuilder.java │ │ ├── ReactiveParticipantEndpointInvocationImpl.java │ │ ├── ReactiveParticipantInvocation.java │ │ ├── ReactiveParticipantInvocationImpl.java │ │ ├── ReactiveParticipantInvocationStep.java │ │ ├── ReactiveSagaActionsProvider.java │ │ ├── ReactiveSagaStep.java │ │ ├── ReactiveStepBuilder.java │ │ ├── ReactiveStepToExecute.java │ │ ├── ReactiveWithCompensationBuilder.java │ │ ├── SimpleReactiveSaga.java │ │ ├── SimpleReactiveSagaDefinition.java │ │ ├── SimpleReactiveSagaDefinitionBuilder.java │ │ └── SimpleReactiveSagaDsl.java │ └── test │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── reactive │ └── simpledsl │ ├── AbstractReactiveLocalSagaTest.java │ ├── LocalSagaData.java │ ├── LocalSagaSteps.java │ ├── NotifyCommand.java │ ├── ReactiveLocalSaga.java │ ├── ReactiveLocalSagaTest.java │ ├── ReactiveLocalSagaWithNotification.java │ ├── ReactiveLocalSagaWithNotificationTest.java │ ├── ReleaseCreditCommand.java │ ├── ReserveCreditCommand.java │ └── framework │ ├── MessageWithDestination.java │ ├── MultipleCommandsExpected.java │ └── ReactiveSagaUnitTestSupport.java ├── eventuate-tram-sagas-reactive-orchestration ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── reactive │ └── orchestration │ ├── ReactiveSaga.java │ ├── ReactiveSagaCommandProducer.java │ ├── ReactiveSagaDefinition.java │ ├── ReactiveSagaInstanceFactory.java │ ├── ReactiveSagaInstanceRepository.java │ ├── ReactiveSagaInstanceRepositoryJdbc.java │ ├── ReactiveSagaManager.java │ ├── ReactiveSagaManagerFactory.java │ ├── ReactiveSagaManagerImpl.java │ └── ReactiveSqlQueryRow.java ├── eventuate-tram-sagas-reactive-participant ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── reactive │ │ └── participant │ │ ├── AbstractReactiveSagaCommandHandlersBuilder.java │ │ ├── ReactiveSagaCommandDispatcher.java │ │ ├── ReactiveSagaCommandDispatcherFactory.java │ │ ├── ReactiveSagaCommandHandler.java │ │ ├── ReactiveSagaCommandHandlerBuilder.java │ │ └── ReactiveSagaCommandHandlersBuilder.java │ └── test │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── reactive │ └── participant │ └── ReactiveSagaCommandDispatcherTest.java ├── eventuate-tram-sagas-spring-common ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── spring │ │ └── common │ │ └── EventuateTramSagaCommonConfiguration.java │ └── test │ ├── java │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── spring │ │ └── common │ │ ├── SagaLockManagerIntegrationTest.java │ │ └── SagaLockManagerIntegrationTestConfiguration.java │ └── resources │ ├── application-mssql.properties │ ├── application-postgres.properties │ └── application.properties ├── eventuate-tram-sagas-spring-flyway └── src │ └── main │ └── resources │ └── flyway │ ├── mysql │ └── V1000__tram-saga-schema.sql │ └── postgresql │ └── V1000__tram-saga-schema.sql ├── eventuate-tram-sagas-spring-in-memory ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── spring │ └── inmemory │ └── TramSagaInMemoryConfiguration.java ├── eventuate-tram-sagas-spring-orchestration-simple-dsl-starter ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── spring │ │ └── orchestration │ │ └── autoconfigure │ │ └── SpringOrchestratorSimpleDslAutoConfiguration.java │ └── resources │ └── META-INF │ ├── spring.factories │ └── spring │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports ├── eventuate-tram-sagas-spring-orchestration-simple-dsl └── build.gradle ├── eventuate-tram-sagas-spring-orchestration ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── spring │ │ └── orchestration │ │ └── SagaOrchestratorConfiguration.java │ └── test │ ├── java │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── orchestration │ │ └── SagaInstanceRepositoryJdbcIntegrationTest.java │ └── resources │ ├── application-mssql.properties │ ├── application-postgres.properties │ └── application.properties ├── eventuate-tram-sagas-spring-participant-starter ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── spring │ │ └── participant │ │ └── autoconfigure │ │ └── SpringParticipantAutoConfiguration.java │ └── resources │ └── META-INF │ ├── spring.factories │ └── spring │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports ├── eventuate-tram-sagas-spring-participant ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── spring │ └── participant │ └── SagaParticipantConfiguration.java ├── eventuate-tram-sagas-spring-reactive-common ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── spring │ │ └── reactive │ │ └── common │ │ └── EventuateReactiveTramSagaCommonConfiguration.java │ └── test │ ├── java │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── spring │ │ └── reactive │ │ └── common │ │ ├── ReactiveSagaLockManagerIntegrationTest.java │ │ └── ReactiveSagaLockManagerIntegrationTestConfiguration.java │ └── resources │ └── application.properties ├── eventuate-tram-sagas-spring-reactive-orchestration-simple-dsl-starter ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── spring │ │ └── reactive │ │ └── orchestration │ │ └── autoconfigure │ │ └── SpringReactiveOrchestratorSimpleDslAutoConfiguration.java │ └── resources │ └── META-INF │ ├── spring.factories │ └── spring │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports ├── eventuate-tram-sagas-spring-reactive-orchestration ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── spring │ │ └── reactive │ │ └── orchestration │ │ └── ReactiveSagaOrchestratorConfiguration.java │ └── test │ ├── java │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── orchestration │ │ └── ReactiveSagaInstanceRepositoryJdbcIntegrationTest.java │ └── resources │ └── application.properties ├── eventuate-tram-sagas-spring-reactive-participant-starter ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── eventuate │ │ └── tram │ │ └── sagas │ │ └── spring │ │ └── reactive │ │ └── participant │ │ └── autoconfigure │ │ └── ReactiveSpringParticipantAutoConfiguration.java │ └── resources │ └── META-INF │ ├── spring.factories │ └── spring │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports ├── eventuate-tram-sagas-spring-reactive-participant ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── spring │ └── reactive │ └── participant │ └── ReactiveSagaParticipantConfiguration.java ├── eventuate-tram-sagas-spring-testing-support-cloud-contract ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── spring │ └── testing │ └── contract │ ├── EventuateTramSagasSpringCloudContractSupportConfiguration.java │ └── SagaMessagingTestHelper.java ├── eventuate-tram-sagas-spring-testing-support ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── spring │ └── testing │ └── SagaParticipantStubManagerConfiguration.java ├── eventuate-tram-sagas-testing-support ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── testing │ ├── SagaParticipantChannels.java │ ├── SagaParticipantStubManager.java │ └── commandhandling │ ├── ReconfigurableCommandHandlers.java │ ├── SagaParticipantStubCommandHandler.java │ └── UnhandledMessageTrackingCommandDispatcher.java ├── eventuate-tram-sagas-unit-testing-support ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── tram │ └── sagas │ └── testing │ ├── MessageWithDestination.java │ ├── MultipleCommandsExpected.java │ └── SagaUnitTestSupport.java ├── gradle.properties ├── gradle ├── gradlew ├── gradlew.bat └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mssql ├── 5.tram-saga-schema.sql ├── Dockerfile └── build-docker.sh ├── mysql-cli.sh ├── mysql ├── Dockerfile ├── build-docker-multi-arch.sh ├── build-docker.sh └── tram-saga-schema.sql ├── orders-and-customers-micronaut-in-memory-integration-tests ├── build.gradle └── src │ └── test │ ├── java │ └── io │ │ └── eventuate │ │ └── examples │ │ └── tram │ │ └── sagas │ │ └── ordersandcustomers │ │ └── integrationtests │ │ └── micronaut │ │ └── OrdersAndCustomersInMemoryIntegrationTest.java │ └── resources │ └── application.yml ├── orders-and-customers-micronaut-integration-tests ├── build.gradle └── src │ └── test │ ├── java │ └── io │ │ └── eventuate │ │ └── examples │ │ └── tram │ │ └── sagas │ │ └── ordersandcustomers │ │ └── integrationtests │ │ └── micronaut │ │ └── OrdersAndCustomersIntegrationTest.java │ └── resources │ ├── application-mssql.yml │ ├── application-postgres.yml │ └── application.yml ├── orders-and-customers-micronaut-local-saga-in-memory-integration-tests ├── build.gradle └── src │ └── test │ ├── java │ └── io │ │ └── eventuate │ │ └── examples │ │ └── tram │ │ └── sagas │ │ └── ordersandcustomers │ │ └── integrationtests │ │ └── micronaut │ │ └── OrdersAndCustomersLocalSagaInMemoryIntegrationTest.java │ └── resources │ └── application.yml ├── orders-and-customers-micronaut ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── eventuate │ └── examples │ └── tram │ └── sagas │ └── ordersandcustomers │ └── micronaut │ ├── customers │ ├── CustomerFactory.java │ └── domain │ │ └── CustomerDaoImpl.java │ ├── orders │ ├── OrderFactory.java │ └── domain │ │ └── OrderDaoImpl.java │ └── tests │ ├── AbstractOrdersAndCustomersIntegrationTest.java │ ├── CommonIntegrationTestFactory.java │ ├── SagaEventsConsumer.java │ └── TramCommandsAndEventsIntegrationData.java ├── orders-and-customers-spring-reactive ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── examples │ │ └── tram │ │ └── sagas │ │ └── ordersandcustomers │ │ └── spring │ │ └── reactive │ │ ├── common │ │ └── Money.java │ │ ├── customers │ │ ├── CustomerConfiguration.java │ │ ├── commands │ │ │ ├── ReleaseCreditCommand.java │ │ │ └── ReserveCreditCommand.java │ │ ├── domain │ │ │ ├── CreditReservation.java │ │ │ ├── CreditReservationRepository.java │ │ │ ├── Customer.java │ │ │ ├── CustomerCreditLimitExceededException.java │ │ │ ├── CustomerNotFoundException.java │ │ │ └── CustomerRepository.java │ │ ├── replies │ │ │ ├── CustomerCreditLimitExceeded.java │ │ │ ├── CustomerCreditReserved.java │ │ │ ├── CustomerNotFound.java │ │ │ └── ReserveCreditResult.java │ │ └── service │ │ │ ├── CustomerCommandHandler.java │ │ │ └── CustomerService.java │ │ └── orders │ │ ├── OrderConfiguration.java │ │ ├── commandsandreplies │ │ └── CancelOrderCommand.java │ │ ├── common │ │ ├── OrderDetails.java │ │ ├── OrderState.java │ │ └── RejectionReason.java │ │ ├── createorder │ │ └── CreateOrderSagaData.java │ │ ├── domain │ │ ├── Order.java │ │ ├── OrderIsTooBigException.java │ │ └── OrderRepository.java │ │ └── service │ │ ├── CreateOrderSaga.java │ │ ├── OrderSagaService.java │ │ └── OrderService.java │ └── test │ ├── java │ └── io │ │ └── eventuate │ │ └── examples │ │ └── tram │ │ └── sagas │ │ └── ordersandcustomers │ │ └── spring │ │ └── reactive │ │ └── integrationtests │ │ └── CustomersAndOrdersIntegrationTest.java │ └── resources │ └── application.properties ├── orders-and-customers-spring ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── examples │ │ └── tram │ │ └── sagas │ │ └── ordersandcustomers │ │ └── spring │ │ ├── customers │ │ ├── CustomerConfiguration.java │ │ └── domain │ │ │ ├── CustomerDaoImpl.java │ │ │ └── CustomerRepository.java │ │ └── orders │ │ ├── OrderConfiguration.java │ │ ├── SagaFailedEvent.java │ │ ├── SagaLifecycleEvent.java │ │ ├── SagaStartedEvent.java │ │ └── domain │ │ ├── OrderDaoImpl.java │ │ └── OrderRepository.java │ └── test │ ├── java │ └── io │ │ └── eventuate │ │ └── examples │ │ └── tram │ │ └── sagas │ │ └── ordersandcustomers │ │ └── spring │ │ └── integrationtests │ │ ├── AbstractOrdersAndCustomersIntegrationTest.java │ │ ├── ActiveMQConfiguration.java │ │ ├── KafkaConfiguration.java │ │ ├── OrderCommandHandlerWithFailingCompensatingTransaction.java │ │ ├── OrdersAndCustomersInMemoryFailingCompensatingTransactionIntegrationTest.java │ │ ├── OrdersAndCustomersInMemoryIntegrationTest.java │ │ ├── OrdersAndCustomersInMemoryIntegrationTestConfiguration.java │ │ ├── OrdersAndCustomersIntegrationCommonIntegrationTestConfiguration.java │ │ ├── OrdersAndCustomersIntegrationTest.java │ │ ├── OrdersAndCustomersIntegrationTestConfiguration.java │ │ ├── OrdersAndCustomersLocalSagaInMemoryIntegrationTest.java │ │ ├── SagaEventsConsumer.java │ │ ├── SagaLifecycleEventListener.java │ │ └── TramCommandsAndEventsIntegrationData.java │ └── resources │ ├── application-mssql.properties │ ├── application-postgres.properties │ └── application.properties ├── orders-and-customers ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── eventuate │ │ └── examples │ │ └── tram │ │ └── sagas │ │ └── ordersandcustomers │ │ ├── commondomain │ │ └── Money.java │ │ ├── customers │ │ ├── domain │ │ │ ├── Customer.java │ │ │ ├── CustomerCreditLimitExceededException.java │ │ │ └── CustomerDao.java │ │ └── service │ │ │ ├── CustomerCommandHandler.java │ │ │ ├── CustomerCreditReservationFailed.java │ │ │ ├── CustomerCreditReserved.java │ │ │ ├── CustomerService.java │ │ │ └── ResultCreditResult.java │ │ └── orders │ │ ├── domain │ │ ├── Order.java │ │ ├── OrderDao.java │ │ └── OrderState.java │ │ ├── sagas │ │ ├── cancelorder │ │ │ └── ReleaseCreditCommand.java │ │ ├── createorder │ │ │ ├── CreateOrderSaga.java │ │ │ ├── CreateOrderSagaCompletedSuccesfully.java │ │ │ ├── CreateOrderSagaData.java │ │ │ ├── CreateOrderSagaRolledBack.java │ │ │ ├── LocalCreateOrderSaga.java │ │ │ └── LocalCreateOrderSagaData.java │ │ └── participants │ │ │ ├── ApproveOrderCommand.java │ │ │ ├── CancelOrderCommand.java │ │ │ ├── ReserveCreditCommand.java │ │ │ └── proxy │ │ │ ├── CustomerServiceProxy.java │ │ │ └── OrderServiceProxy.java │ │ └── service │ │ ├── OrderCommandHandler.java │ │ ├── OrderDetails.java │ │ ├── OrderService.java │ │ └── RejectOrderCommand.java │ └── test │ └── java │ └── io │ └── eventuate │ └── examples │ └── tram │ └── sagas │ └── ordersandcustomers │ └── orders │ └── sagas │ └── createorder │ └── CreateOrderSagaTest.java ├── postgres-cli.sh ├── postgres ├── Dockerfile ├── build-docker.sh └── tram-saga-schema.sql ├── publish-docker-images.sh ├── schema-for-testing-reactive-framework.sql ├── set-multi-arch-image-env-vars.sh └── settings.gradle /.circleci/save-containers-and-tests.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | mkdir -p ~/junit /home/circleci/container-logs 4 | docker ps -a > /home/circleci/container-logs/containers.txt 5 | find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/junit/ \; 6 | sudo bash -c 'find /var/lib/docker/containers -name "*-json.log" -exec cp {} /home/circleci/container-logs \;' 7 | sudo bash -c 'find /home/circleci/container-logs -type f -exec chown circleci {} \;' 8 | -------------------------------------------------------------------------------- /.circleci/setenv-circle-ci.sh: -------------------------------------------------------------------------------- 1 | export TERM=dumb 2 | 3 | 4 | -------------------------------------------------------------------------------- /.circleci/target-tag.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | BRANCH=$(git rev-parse --abbrev-ref HEAD) 4 | 5 | if [[ $BRANCH == "master" ]] ; then 6 | TARGET_TAG=$(sed -e '/^version=/!d' -e 's/version=//' -e 's/-SNAPSHOT/.BUILD-SNAPSHOT/' < gradle.properties) 7 | elif [[ $BRANCH =~ RELEASE$ ]] ; then 8 | TARGET_TAG=$BRANCH 9 | elif [[ $BRANCH =~ M[0-9]+$ ]] ; then 10 | TARGET_TAG=$BRANCH 11 | elif [[ $BRANCH =~ RC[0-9]+$ ]] ; then 12 | TARGET_TAG=$BRANCH 13 | elif [[ $BRANCH =~ ^wip- ]] ; then 14 | TARGET_TAG=$(sed -e '/^version=/!d' -e 's/version=//' -e 's/-SNAPSHOT//' < gradle.properties) 15 | TARGET_TAG=${TARGET_TAG}.$(echo $BRANCH | sed -e 's/wip-//' -e 's/-/_/' | tr '[:lower:]' '[:upper:]' ) 16 | TARGET_TAG=${TARGET_TAG}.BUILD-SNAPSHOT 17 | else 18 | TARGET_TAG=${BRANCH}-${CIRCLE_BUILD_NUM?} 19 | fi 20 | 21 | echo $TARGET_TAG 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | *.idea/ 4 | *.iml 5 | *.log 6 | out 7 | **/bin/** 8 | .java-version 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Eventuate, Inc. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /_build-and-test-all.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | ./gradlew testClasses 6 | 7 | export broker="${broker:-kafka}" 8 | ./gradlew "${database}${broker}AllComposeDown" 9 | ./gradlew "${database}${broker}AllComposeUp" 10 | 11 | if [ "${database}" == "mysql" ]; then 12 | cat schema-for-testing-reactive-framework.sql | ./mysql-cli.sh -i 13 | fi 14 | 15 | if [[ "${SPRING_PROFILES_ACTIVE}" != "ActiveMQ" ]]; then 16 | ./gradlew :orders-and-customers-spring:cleanTest :orders-and-customers-micronaut:cleanTest build 17 | 18 | export EVENTUATE_OUTBOX_ID=1 19 | export USE_DB_ID=true 20 | 21 | ./gradlew "${database}${broker}AllComposeDown" 22 | ./gradlew "${database}${broker}AllComposeUp" 23 | 24 | ./gradlew :orders-and-customers-spring:cleanTest :orders-and-customers-micronaut:cleanTest build 25 | else 26 | ./gradlew -x orders-and-customers-micronaut-integration-tests:test -x :orders-and-customers-micronaut:test -x :eventuate-tram-sagas-micronaut-common:test :orders-and-customers-spring:cleanTest build 27 | fi 28 | 29 | ./gradlew "${database}${broker}AllComposeDown" 30 | -------------------------------------------------------------------------------- /build-and-test-all-mssql.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | export SPRING_PROFILES_ACTIVE=mssql 6 | export MICRONAUT_ENVIRONMENTS=mssql 7 | export database=mssql 8 | export target=mssql 9 | 10 | ./_build-and-test-all.sh -------------------------------------------------------------------------------- /build-and-test-all-multi-arch-locally-mysql-kafka.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | ./gradlew $* testClasses 4 | 5 | export EVENTUATE_COMMON_VERSION=$(sed < gradle.properties -e '/eventuateCommonImageVersion=/!d' -e 's/.*=//') 6 | 7 | ./mysql/build-docker-multi-arch.sh 8 | 9 | docker pull localhost:5002/eventuate-tram-sagas-mysql:multi-arch-local-build 10 | 11 | export database=mysql 12 | export target=mysql 13 | 14 | ./build-and-test-all-mysql-kafka.sh $* 15 | 16 | -------------------------------------------------------------------------------- /build-and-test-all-mysql-activemq.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | export SPRING_PROFILES_ACTIVE=ActiveMQ 6 | export database=mysql 7 | export target=activemq 8 | export broker=activemq 9 | 10 | ./_build-and-test-all.sh -------------------------------------------------------------------------------- /build-and-test-all-mysql-kafka.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | export database=mysql 4 | export target=mysql 5 | 6 | ./_build-and-test-all.sh 7 | -------------------------------------------------------------------------------- /build-and-test-all-postgres.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | export SPRING_PROFILES_ACTIVE=postgres 6 | export MICRONAUT_ENVIRONMENTS=postgres 7 | export database=postgres 8 | export target=postgres 9 | 10 | ./_build-and-test-all.sh -------------------------------------------------------------------------------- /build-and-test-everything.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | set -o pipefail 4 | 5 | SCRIPTS="./build-and-test-all-mysql.sh ./build-and-test-all-postgres.sh ./build-and-test-all-mssql.sh" 6 | 7 | date > build-and-test-everything.log 8 | 9 | for script in $SCRIPTS ; do 10 | echo '****************************************** Running' $script 11 | date >> build-and-test-everything.log 12 | echo '****************************************** Running' $script >> build-and-test-everything.log 13 | $script | tee -a build-and-test-everything.log 14 | done 15 | 16 | echo 'Finished successfully!!!' 17 | -------------------------------------------------------------------------------- /build-multi-arch-images.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | docker login -u ${DOCKER_USER_ID?} -p ${DOCKER_PASSWORD?} 4 | 5 | export EVENTUATE_COMMON_VERSION=$(sed < gradle.properties -e '/eventuateCommonImageVersion=/!d' -e 's/.*=//') 6 | 7 | ./mysql/build-docker-multi-arch.sh 8 | -------------------------------------------------------------------------------- /deploy-artifacts.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | docker login -u ${DOCKER_USER_ID?} -p ${DOCKER_PASSWORD?} 4 | 5 | ./gradlew publishEventuateArtifacts 6 | 7 | ./gradlew publishEventuateDockerImages 8 | -------------------------------------------------------------------------------- /deploy-multi-arch.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | TARGET_TAG=$(./.circleci/target-tag.sh) 4 | docker login -u ${DOCKER_USER_ID?} -p ${DOCKER_PASSWORD?} 5 | 6 | retag() { 7 | BASE=$1 8 | IMAGE=${BASE}:${MULTI_ARCH_TAG?} 9 | TARGET_IMAGE=$BASE:$TARGET_TAG 10 | 11 | echo Retagging $IMAGE $TARGET_IMAGE 12 | 13 | SOURCES=$(docker manifest inspect $IMAGE | \ 14 | jq -r '.manifests[].digest | sub("^"; "'${BASE}'@")') 15 | 16 | docker buildx imagetools create -t ${TARGET_IMAGE} $SOURCES 17 | } 18 | 19 | retag "eventuateio/eventuate-tram-sagas-mysql" 20 | -------------------------------------------------------------------------------- /docker-compose-registry.yml: -------------------------------------------------------------------------------- 1 | services: 2 | registry: 3 | image: registry:2 4 | ports: 5 | - "5002:5000" 6 | -------------------------------------------------------------------------------- /docs/design/customers-participant.diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eventuate-tram/eventuate-tram-sagas/802db395dc8c0b1c3e130bd207094aa1f11bbd3e/docs/design/customers-participant.diagram.png -------------------------------------------------------------------------------- /docs/design/orders-orchestration.diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eventuate-tram/eventuate-tram-sagas/802db395dc8c0b1c3e130bd207094aa1f11bbd3e/docs/design/orders-orchestration.diagram.png -------------------------------------------------------------------------------- /eventuate-tram-sagas-common-in-memory/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | } 3 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-common/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api "io.eventuate.tram.core:eventuate-tram-commands:$eventuateTramVersion" 4 | api "io.eventuate.common:eventuate-common-jdbc:$eventuateCommonVersion" 5 | } 6 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-common/src/main/java/io/eventuate/tram/sagas/common/LockTarget.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.common; 2 | 3 | public class LockTarget { 4 | private String target; 5 | 6 | public LockTarget(Class targetClass, Object targetId) { 7 | this(targetClass.getName(), targetId.toString()); 8 | } 9 | 10 | 11 | public LockTarget(String targetClass, String targetId) { 12 | this(targetClass + "/" + targetId); 13 | } 14 | 15 | public LockTarget(String target) { 16 | this.target = target; 17 | } 18 | 19 | public String getTarget() { 20 | return target; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-common/src/main/java/io/eventuate/tram/sagas/common/SagaCommandHeaders.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.common; 2 | 3 | import io.eventuate.tram.commands.common.CommandMessageHeaders; 4 | 5 | public class SagaCommandHeaders { 6 | public static final String SAGA_TYPE = CommandMessageHeaders.COMMAND_HEADER_PREFIX + "saga_type"; 7 | public static final String SAGA_ID = CommandMessageHeaders.COMMAND_HEADER_PREFIX + "saga_id"; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-common/src/main/java/io/eventuate/tram/sagas/common/SagaLockManager.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.common; 2 | 3 | import io.eventuate.tram.messaging.common.Message; 4 | 5 | import java.util.Optional; 6 | 7 | public interface SagaLockManager { 8 | 9 | boolean claimLock(String sagaType, String sagaId, String target); 10 | 11 | void stashMessage(String sagaType, String sagaId, String target, Message message); 12 | 13 | Optional unlock(String sagaId, String target); 14 | } 15 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-common/src/main/java/io/eventuate/tram/sagas/common/SagaReplyHeaders.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.common; 2 | 3 | import io.eventuate.tram.commands.common.CommandMessageHeaders; 4 | 5 | public class SagaReplyHeaders { 6 | 7 | public static final String REPLY_SAGA_TYPE = CommandMessageHeaders.inReply(SagaCommandHeaders.SAGA_TYPE); 8 | public static final String REPLY_SAGA_ID = CommandMessageHeaders.inReply(SagaCommandHeaders.SAGA_ID); 9 | 10 | public static final String REPLY_LOCKED = "saga-locked-target"; 11 | } 12 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-common/src/main/java/io/eventuate/tram/sagas/common/SagaUnlockCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.common; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class SagaUnlockCommand implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-common/src/main/java/io/eventuate/tram/sagas/common/StashMessageRequiredException.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.common; 2 | 3 | public class StashMessageRequiredException extends RuntimeException { 4 | private String target; 5 | 6 | public String getTarget() { 7 | return target; 8 | } 9 | 10 | public StashMessageRequiredException(String target) { 11 | this.target = target; 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-common/src/main/java/io/eventuate/tram/sagas/common/StashedMessage.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.common; 2 | 3 | import io.eventuate.tram.messaging.common.Message; 4 | 5 | public class StashedMessage { 6 | private String sagaType; 7 | private final String sagaId; 8 | private final Message message; 9 | 10 | public String getSagaType() { 11 | return sagaType; 12 | } 13 | 14 | public StashedMessage(String sagaType, String sagaId, Message message) { 15 | this.sagaType = sagaType; 16 | this.sagaId = sagaId; 17 | this.message = message; 18 | } 19 | 20 | public String getSagaId() { 21 | return sagaId; 22 | } 23 | 24 | public Message getMessage() { 25 | return message; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-event-sourcing-support/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api "io.eventuate.local.java:eventuate-client-java:$eventuateLocalVersion" 4 | api "io.eventuate.tram.core:eventuate-tram-spring-commands:$eventuateTramVersion" 5 | api "javax.annotation:javax.annotation-api:1.3.2" 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-event-sourcing-support/src/main/java/io/eventuate/tram/sagas/eventsourcingsupport/AggregateRepositoryInterceptorExceptionHandlerBuilder.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.eventsourcingsupport; 2 | 3 | import io.eventuate.AggregateRepositoryInterceptor; 4 | import io.eventuate.Command; 5 | import io.eventuate.CommandProcessingAggregate; 6 | import io.eventuate.UpdateEventsAndOptions; 7 | 8 | import java.util.Optional; 9 | import java.util.function.BiFunction; 10 | 11 | public class AggregateRepositoryInterceptorExceptionHandlerBuilder, CT extends Command> { 12 | 13 | public AggregateRepositoryInterceptorExceptionHandlerBuilder(Class exceptionClass, BiFunction> exceptionHandler) { 14 | } 15 | 16 | static , CT extends Command, E extends Throwable> 17 | AggregateRepositoryInterceptorExceptionHandlerBuilder catching(Class exceptionClass, BiFunction> exceptionHandler) { 18 | return new AggregateRepositoryInterceptorExceptionHandlerBuilder<>((Class) exceptionClass, (a, e) -> exceptionHandler.apply((T) a, (E) e)); 19 | } 20 | 21 | public AggregateRepositoryInterceptor build() { 22 | throw new UnsupportedOperationException(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-event-sourcing-support/src/main/java/io/eventuate/tram/sagas/eventsourcingsupport/SagaReplyRequestedEvent.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.eventsourcingsupport; 2 | 3 | import io.eventuate.Event; 4 | 5 | import java.util.Map; 6 | 7 | public class SagaReplyRequestedEvent implements Event { 8 | private Map correlationHeaders; 9 | 10 | private SagaReplyRequestedEvent() { 11 | } 12 | 13 | public SagaReplyRequestedEvent(Map correlationHeaders) { 14 | this.correlationHeaders = correlationHeaders; 15 | } 16 | 17 | public Map getCorrelationHeaders() { 18 | return correlationHeaders; 19 | } 20 | 21 | public void setCorrelationHeaders(Map correlationHeaders) { 22 | this.correlationHeaders = correlationHeaders; 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-event-sourcing-support/src/main/java/io/eventuate/tram/sagas/eventsourcingsupport/UpdatingOptionsBuilder.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.eventsourcingsupport; 2 | 3 | import io.eventuate.UpdateOptions; 4 | import io.eventuate.tram.commands.consumer.CommandMessage; 5 | import io.eventuate.tram.messaging.common.Message; 6 | 7 | import java.util.HashMap; 8 | import java.util.LinkedHashMap; 9 | import java.util.Optional; 10 | import java.util.function.Supplier; 11 | 12 | public class UpdatingOptionsBuilder { 13 | 14 | private CommandMessage commandMessage; 15 | private HashMap, Supplier> exceptionHandlers= new LinkedHashMap<>(); 16 | 17 | public UpdatingOptionsBuilder(CommandMessage commandMessage) { 18 | this.commandMessage = commandMessage; 19 | } 20 | 21 | public static UpdatingOptionsBuilder replyingTo(CommandMessage cm) { 22 | return new UpdatingOptionsBuilder(cm); 23 | } 24 | 25 | public UpdatingOptionsBuilder catching(Class exception, Supplier replySupplier) { 26 | exceptionHandlers.put((Class) exception, replySupplier); 27 | return this; 28 | } 29 | 30 | public Optional build() { 31 | 32 | return Optional.of(new UpdateOptions() 33 | .withIdempotencyKey(commandMessage.getMessageId()) 34 | .withInterceptor(new CommandMessageAggregateRepositoryInterceptor(commandMessage))); 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-common/src/main/java/io/eventuate/tram/sagas/micronaut/common/EventuateTramSagaCommonFactory.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.micronaut.common; 2 | 3 | import io.eventuate.common.jdbc.EventuateJdbcStatementExecutor; 4 | import io.eventuate.common.jdbc.EventuateSchema; 5 | import io.eventuate.tram.sagas.common.SagaLockManager; 6 | import io.eventuate.tram.sagas.common.SagaLockManagerImpl; 7 | import io.micronaut.context.annotation.Factory; 8 | 9 | 10 | import javax.inject.Singleton; 11 | 12 | @Factory 13 | public class EventuateTramSagaCommonFactory { 14 | 15 | @Singleton 16 | public SagaLockManager sagaLockManager(EventuateJdbcStatementExecutor eventuateJdbcStatementExecutor, 17 | EventuateSchema eventuateSchema) { 18 | return new SagaLockManagerImpl(eventuateJdbcStatementExecutor, eventuateSchema); 19 | } 20 | } -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-common/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | default: 3 | url: jdbc:h2:mem:testdb 4 | username: sa 5 | password: test 6 | driverClassName: org.h2.Driver -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-configuration-tests/src/test/java/io/eventuate/tram/sagas/micronaut/configuration/test/SagaMessageProducerConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.micronaut.configuration.test; 2 | 3 | import io.eventuate.tram.messaging.producer.common.MessageProducerImplementation; 4 | import io.eventuate.tram.messaging.producer.jdbc.MessageProducerJdbcImpl; 5 | import io.micronaut.test.annotation.MicronautTest; 6 | import org.junit.Assert; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import javax.inject.Inject; 10 | 11 | @MicronautTest(transactional = false) 12 | public class SagaMessageProducerConfigurationTest { 13 | 14 | @Inject 15 | private MessageProducerImplementation messageProducer; 16 | 17 | @Test 18 | public void testThatJdbcProducerIsUsed() { 19 | Assert.assertTrue(messageProducer instanceof MessageProducerJdbcImpl); 20 | } 21 | } -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-configuration-tests/src/test/resources/application-mssql.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | default: 3 | url: jdbc:sqlserver://${$DOCKER_HOST_IP:localhost}:1433;databaseName=eventuate 4 | username: sa 5 | password: Eventuate123! 6 | driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver 7 | driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-configuration-tests/src/test/resources/application-postgres.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | default: 3 | url: jdbc:postgresql://${$DOCKER_HOST_IP:localhost}/eventuate 4 | username: eventuate 5 | password: eventuate 6 | driverClassName: org.postgresql.Driver 7 | driver-class-name: org.postgresql.Driver -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-configuration-tests/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | default: 3 | url: jdbc:mysql://${$DOCKER_HOST_IP:localhost}/eventuate 4 | username: mysqluser 5 | password: mysqlpw 6 | driverClassName: com.mysql.cj.jdbc.Driver 7 | driver-class-name: com.mysql.cj.jdbc.Driver -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-in-memory/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "io.spring.dependency-management" version "1.0.6.RELEASE" 3 | } 4 | 5 | 6 | 7 | 8 | dependencyManagement { 9 | imports { 10 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion" 11 | } 12 | } 13 | 14 | dependencies { 15 | api project(":eventuate-tram-sagas-common-in-memory") 16 | 17 | implementation "io.eventuate.tram.core:eventuate-tram-micronaut-in-memory:$eventuateTramVersion" 18 | implementation "com.h2database:h2:1.3.166" 19 | 20 | annotationProcessor "io.micronaut:micronaut-inject-java" 21 | annotationProcessor "io.micronaut:micronaut-validation" 22 | annotationProcessor "io.micronaut.configuration:micronaut-openapi" 23 | api "io.micronaut:micronaut-inject" 24 | api "io.micronaut:micronaut-validation" 25 | api "io.micronaut:micronaut-runtime" 26 | } 27 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-in-memory/src/main/java/io/eventuate/tram/sagas/micronaut/inmemory/TramSagaInMemoryFactory.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.micronaut.inmemory; 2 | 3 | import io.eventuate.common.inmemorydatabase.EventuateDatabaseScriptSupplier; 4 | import io.micronaut.context.annotation.Factory; 5 | 6 | import javax.inject.Named; 7 | import javax.inject.Singleton; 8 | import java.util.Collections; 9 | 10 | 11 | @Factory 12 | public class TramSagaInMemoryFactory { 13 | @Singleton 14 | @Named("TramSagasEventuateDatabaseScriptSupplier") 15 | public EventuateDatabaseScriptSupplier eventuateCommonInMemoryScriptSupplierForEventuateTramSagas() { 16 | return () -> Collections.singletonList("eventuate-tram-sagas-embedded.sql"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-orchestration-simple-dsl/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-orchestration-simple-dsl") 4 | api project(":eventuate-tram-sagas-micronaut-orchestration") 5 | } 6 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-orchestration/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "io.spring.dependency-management" version "1.0.6.RELEASE" 3 | } 4 | 5 | 6 | 7 | 8 | 9 | dependencyManagement { 10 | imports { 11 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion" 12 | } 13 | } 14 | 15 | dependencies { 16 | api project(":eventuate-tram-sagas-orchestration") 17 | api project(":eventuate-tram-sagas-micronaut-common") 18 | 19 | api "io.eventuate.tram.core:eventuate-tram-micronaut-events:$eventuateTramVersion" 20 | api "io.eventuate.tram.core:eventuate-tram-micronaut-commands:$eventuateTramVersion" 21 | api "io.eventuate.common:eventuate-common-micronaut-jdbc:$eventuateCommonVersion" 22 | 23 | annotationProcessor "io.micronaut:micronaut-inject-java" 24 | annotationProcessor "io.micronaut:micronaut-validation" 25 | annotationProcessor "io.micronaut.configuration:micronaut-openapi" 26 | api "io.micronaut:micronaut-inject" 27 | api "io.micronaut:micronaut-validation" 28 | api "io.micronaut:micronaut-runtime" 29 | } 30 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-orchestration/src/main/java/io/eventuate/tram/sagas/micronaut/orchestration/SagaManagerImplInitializer.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.micronaut.orchestration; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaManager; 4 | import io.micronaut.context.annotation.Context; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import javax.annotation.PostConstruct; 9 | import javax.inject.Inject; 10 | import java.util.Arrays; 11 | 12 | @Context 13 | public class SagaManagerImplInitializer { 14 | private Logger logger = LoggerFactory.getLogger(this.getClass()); 15 | 16 | @Inject 17 | private SagaManager[] sagaManagers; 18 | 19 | @PostConstruct 20 | public void init() { 21 | logger.info("Initializing saga managers"); 22 | Arrays.stream(sagaManagers).forEach(SagaManager::subscribeToReplyChannel); 23 | logger.info("Initialized saga managers"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-participant/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "io.spring.dependency-management" version "1.0.6.RELEASE" 3 | } 4 | 5 | 6 | 7 | 8 | 9 | dependencyManagement { 10 | imports { 11 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion" 12 | } 13 | } 14 | 15 | dependencies { 16 | api project(":eventuate-tram-sagas-participant") 17 | api project(":eventuate-tram-sagas-micronaut-common") 18 | api "io.eventuate.common:eventuate-common-micronaut-jdbc:$eventuateCommonVersion" 19 | 20 | annotationProcessor "io.micronaut:micronaut-inject-java" 21 | annotationProcessor "io.micronaut:micronaut-validation" 22 | annotationProcessor "io.micronaut.configuration:micronaut-openapi" 23 | api "io.micronaut:micronaut-inject" 24 | api "io.micronaut:micronaut-validation" 25 | api "io.micronaut:micronaut-runtime" 26 | } 27 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-participant/src/main/java/io/eventuate/tram/sagas/micronaut/participant/SagaParticipantFactory.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.micronaut.participant; 2 | 3 | import io.eventuate.tram.commands.common.CommandNameMapping; 4 | import io.eventuate.tram.commands.consumer.CommandReplyProducer; 5 | import io.eventuate.tram.messaging.consumer.MessageConsumer; 6 | import io.eventuate.tram.sagas.common.SagaLockManager; 7 | import io.eventuate.tram.sagas.participant.SagaCommandDispatcherFactory; 8 | import io.micronaut.context.annotation.Factory; 9 | 10 | import javax.inject.Singleton; 11 | 12 | @Factory 13 | public class SagaParticipantFactory { 14 | @Singleton 15 | public SagaCommandDispatcherFactory sagaCommandDispatcherFactory(MessageConsumer messageConsumer, 16 | SagaLockManager sagaLockManager, 17 | CommandNameMapping commandNameMapping, CommandReplyProducer commandReplyProducer) { 18 | return new SagaCommandDispatcherFactory(messageConsumer, sagaLockManager, commandNameMapping, commandReplyProducer); 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-testing-support/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "io.spring.dependency-management" version "1.0.6.RELEASE" 3 | } 4 | 5 | 6 | 7 | 8 | 9 | dependencyManagement { 10 | imports { 11 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion" 12 | } 13 | } 14 | 15 | dependencies { 16 | api project(":eventuate-tram-sagas-testing-support") 17 | 18 | annotationProcessor "io.micronaut:micronaut-inject-java" 19 | annotationProcessor "io.micronaut:micronaut-validation" 20 | annotationProcessor "io.micronaut.configuration:micronaut-openapi" 21 | api "io.micronaut:micronaut-inject" 22 | api "io.micronaut:micronaut-validation" 23 | api "io.micronaut:micronaut-runtime" 24 | } 25 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-testing-support/src/main/java/io/eventuate/tram/sagas/micronaut/testing/SagaParticipantStubManagerFactory.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.micronaut.testing; 2 | 3 | import io.eventuate.tram.commands.common.CommandNameMapping; 4 | import io.eventuate.tram.commands.consumer.CommandReplyProducer; 5 | import io.eventuate.tram.messaging.consumer.MessageConsumer; 6 | import io.eventuate.tram.sagas.testing.SagaParticipantChannels; 7 | import io.eventuate.tram.sagas.testing.SagaParticipantStubManager; 8 | import io.micronaut.context.annotation.Factory; 9 | 10 | import javax.inject.Singleton; 11 | 12 | @Factory 13 | public class SagaParticipantStubManagerFactory { 14 | 15 | @Singleton 16 | public SagaParticipantStubManager sagaParticipantStubManager(SagaParticipantChannels sagaParticipantChannels, 17 | MessageConsumer messageConsumer, 18 | CommandNameMapping commandNameMapping, CommandReplyProducer commandReplyProducer) { 19 | return new SagaParticipantStubManager(sagaParticipantChannels, messageConsumer, commandNameMapping, commandReplyProducer); 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-micronaut-testing-support/src/main/java/io/eventuate/tram/sagas/micronaut/testing/SagaParticipantStubManagerInitializer.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.micronaut.testing; 2 | 3 | import io.eventuate.tram.sagas.testing.SagaParticipantStubManager; 4 | import io.micronaut.context.annotation.Context; 5 | 6 | import javax.annotation.PostConstruct; 7 | import javax.inject.Inject; 8 | import java.util.Arrays; 9 | 10 | @Context 11 | public class SagaParticipantStubManagerInitializer { 12 | 13 | @Inject 14 | private SagaParticipantStubManager[] sagaParticipantStubManagers; 15 | 16 | @PostConstruct 17 | public void init() { 18 | Arrays.stream(sagaParticipantStubManagers).forEach(SagaParticipantStubManager::initialize); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-orchestration") 4 | 5 | testImplementation project(":eventuate-tram-sagas-unit-testing-support") 6 | testImplementation project(":eventuate-tram-sagas-participant") 7 | testImplementation "io.eventuate.tram.core:eventuate-tram-in-memory:$eventuateTramVersion" 8 | testImplementation "io.eventuate.util:eventuate-util-test:$eventuateUtilVersion" 9 | 10 | } 11 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/AbstractParticipantInvocation.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import java.util.Optional; 4 | import java.util.function.Predicate; 5 | 6 | public abstract class AbstractParticipantInvocation implements ParticipantInvocation { 7 | 8 | private Optional> invocablePredicate; 9 | 10 | protected AbstractParticipantInvocation(Optional> invocablePredicate) { 11 | this.invocablePredicate = invocablePredicate; 12 | } 13 | 14 | @Override 15 | public boolean isInvocable(Data data) { 16 | return invocablePredicate.map(p -> p.test(data)).orElse(true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/AbstractSagaActionsProvider.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaActions; 4 | 5 | import java.util.function.Function; 6 | import java.util.function.Supplier; 7 | 8 | public abstract class AbstractSagaActionsProvider { 9 | private final SagaActions sagaActions; 10 | private final Supplier sagaActionsSupplier; 11 | 12 | public AbstractSagaActionsProvider(SagaActions sagaActions) { 13 | this.sagaActions = sagaActions; 14 | this.sagaActionsSupplier = null; 15 | } 16 | 17 | public AbstractSagaActionsProvider(Supplier sagaActionsSupplier) { 18 | this.sagaActions = null; 19 | this.sagaActionsSupplier = sagaActionsSupplier; 20 | } 21 | 22 | public SuppliedValue toSagaActions(Function, SuppliedValue> f1, Function f2) { 23 | return sagaActions != null ? f1.apply(sagaActions) : f2.apply(sagaActionsSupplier.get()); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/AbstractStepToExecute.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaActions; 4 | 5 | import static io.eventuate.tram.sagas.simpledsl.SagaExecutionStateJsonSerde.encodeState; 6 | 7 | public class AbstractStepToExecute> { 8 | protected final SagaStep step; 9 | protected final int skipped; 10 | protected final boolean compensating; 11 | 12 | public AbstractStepToExecute(SagaStep step, int skipped, boolean compensating) { 13 | this.step = step; 14 | this.skipped = skipped; 15 | this.compensating = compensating; 16 | } 17 | 18 | protected int size() { 19 | return 1 + skipped; 20 | } 21 | 22 | protected SagaActions makeSagaActions(SagaActions.Builder builder, Data data, SagaExecutionState newState, boolean compensating) { 23 | String state = encodeState(newState); 24 | return builder.buildActions(data, compensating, state, newState.isEndState()); 25 | } 26 | 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/CommandEndpoint.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | import java.util.Set; 6 | 7 | public class CommandEndpoint { 8 | 9 | private String commandChannel; 10 | private Class commandClass; 11 | private Set replyClasses; 12 | 13 | public CommandEndpoint(String commandChannel, Class commandClass, Set replyClasses) { 14 | this.commandChannel = commandChannel; 15 | this.commandClass = commandClass; 16 | this.replyClasses = replyClasses; 17 | } 18 | 19 | public String getCommandChannel() { 20 | return commandChannel; 21 | } 22 | 23 | public Class getCommandClass() { 24 | return commandClass; 25 | } 26 | 27 | public Set getReplyClasses() { 28 | return replyClasses; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/CommandEndpointBuilder.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | public class CommandEndpointBuilder { 9 | 10 | private String channel; 11 | private Class commandClass; 12 | private Set replyClasses = new HashSet<>(); 13 | 14 | public CommandEndpointBuilder(Class commandClass) { 15 | this.commandClass = commandClass; 16 | } 17 | 18 | public static CommandEndpointBuilder forCommand(Class commandClass) { 19 | return new CommandEndpointBuilder<>(commandClass); 20 | } 21 | 22 | public CommandEndpointBuilder withChannel(String channel) { 23 | this.channel = channel; 24 | return this; 25 | } 26 | 27 | 28 | public CommandEndpointBuilder withReply(Class replyClass) { 29 | this.replyClasses.add(replyClass); 30 | return this; 31 | } 32 | 33 | public CommandEndpoint build() { 34 | return new CommandEndpoint<>(channel, commandClass, replyClasses); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/ISagaStep.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.messaging.common.Message; 4 | 5 | public interface ISagaStep { 6 | boolean isSuccessfulReply(boolean compensating, Message message); 7 | 8 | boolean hasAction(Data data); 9 | 10 | boolean hasCompensation(Data data); 11 | } 12 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/LocalExceptionSaver.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import java.util.function.BiConsumer; 4 | 5 | public class LocalExceptionSaver { 6 | private final Class exceptionType; 7 | private final BiConsumer exceptionConsumer; 8 | 9 | public LocalExceptionSaver(Class exceptionType, BiConsumer exceptionConsumer) { 10 | this.exceptionType = exceptionType; 11 | this.exceptionConsumer = exceptionConsumer; 12 | } 13 | 14 | public boolean shouldSave(Exception e) { 15 | return exceptionType.isInstance(e); 16 | } 17 | 18 | public void save(Data data, RuntimeException e) { 19 | exceptionConsumer.accept(data, e); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/ParticipantInvocation.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.messaging.common.Message; 4 | import io.eventuate.tram.sagas.orchestration.CommandWithDestinationAndType; 5 | 6 | 7 | public interface ParticipantInvocation { 8 | 9 | boolean isSuccessfulReply(Message message); 10 | 11 | boolean isInvocable(Data data); 12 | 13 | CommandWithDestinationAndType makeCommandToSend(Data data); 14 | } 15 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/ParticipantInvocationBuilder.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | import java.util.Map; 6 | 7 | import static java.util.Collections.singletonMap; 8 | 9 | public class ParticipantInvocationBuilder { 10 | private Map params; 11 | 12 | 13 | public ParticipantInvocationBuilder(String key, String value) { 14 | this.params = singletonMap(key, value); 15 | } 16 | 17 | public ParticipantParamsAndCommand withCommand(C command) { 18 | return new ParticipantParamsAndCommand<>(params, command); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/ParticipantParamsAndCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | import java.util.Map; 6 | 7 | public class ParticipantParamsAndCommand { 8 | private final Map params; 9 | private final C command; 10 | 11 | public ParticipantParamsAndCommand(Map params, C command) { 12 | this.params = params; 13 | this.command = command; 14 | } 15 | 16 | public Map getParams() { 17 | return params; 18 | } 19 | 20 | public C getCommand() { 21 | return command; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/SagaActionsProvider.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaActions; 4 | 5 | import java.util.function.Supplier; 6 | 7 | public class SagaActionsProvider extends AbstractSagaActionsProvider> { 8 | 9 | public SagaActionsProvider(SagaActions sagaActions) { 10 | super(sagaActions); 11 | } 12 | 13 | public SagaActionsProvider(Supplier> sagaActionsSupport) { 14 | super(sagaActionsSupport); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/SagaEndpointInvocation.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | public class SagaEndpointInvocation { 4 | } 5 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/SagaExecutionStateJsonSerde.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.common.json.mapper.JSonMapper; 4 | 5 | public class SagaExecutionStateJsonSerde { 6 | public static SagaExecutionState decodeState(String currentState) { 7 | return JSonMapper.fromJson(currentState, SagaExecutionState.class); 8 | } 9 | 10 | public static String encodeState(SagaExecutionState state) { 11 | return JSonMapper.toJson(state); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/SagaStep.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.messaging.common.Message; 4 | 5 | import java.util.Optional; 6 | import java.util.function.BiConsumer; 7 | 8 | public interface SagaStep extends ISagaStep { 9 | 10 | Optional> getReplyHandler(Message message, boolean compensating); 11 | 12 | StepOutcome makeStepOutcome(Data data, boolean compensating); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/SimpleSaga.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.orchestration.Saga; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | public interface SimpleSaga extends Saga, SimpleSagaDsl { 9 | default List getParticipantProxies() { return Collections.emptyList(); } 10 | } 11 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/SimpleSagaDefinitionBuilder.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaDefinition; 4 | 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | public class SimpleSagaDefinitionBuilder { 9 | 10 | private List> sagaSteps = new LinkedList<>(); 11 | 12 | public void addStep(SagaStep sagaStep) { 13 | sagaSteps.add(sagaStep); 14 | } 15 | 16 | public SagaDefinition build() { 17 | return new SimpleSagaDefinition<>(sagaSteps); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/SimpleSagaDsl.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | public interface SimpleSagaDsl { 4 | 5 | default StepBuilder step() { 6 | SimpleSagaDefinitionBuilder builder = new SimpleSagaDefinitionBuilder<>(); 7 | return new StepBuilder<>(builder); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/StepToExecute.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaActions; 4 | 5 | public class StepToExecute extends AbstractStepToExecute> { 6 | 7 | 8 | public StepToExecute(SagaStep step, int skipped, boolean compensating) { 9 | super(step, skipped, compensating); 10 | } 11 | 12 | 13 | public SagaActions executeStep(Data data, SagaExecutionState currentState) { 14 | SagaExecutionState newState = currentState.nextState(size()); 15 | SagaActions.Builder builder = SagaActions.builder(); 16 | boolean compensating = currentState.isCompensating(); 17 | 18 | step.makeStepOutcome(data, this.compensating).visit(builder::withIsLocal, builder::withCommands); 19 | 20 | return makeSagaActions(builder, data, newState, compensating); 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/WithCompensationBuilder.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | import io.eventuate.tram.commands.consumer.CommandWithDestination; 5 | 6 | import java.util.function.Function; 7 | import java.util.function.Predicate; 8 | 9 | public interface WithCompensationBuilder { 10 | 11 | InvokeParticipantStepBuilder withCompensation(Function compensation); 12 | 13 | InvokeParticipantStepBuilder withCompensation(Predicate compensationPredicate, Function compensation); 14 | 15 | InvokeParticipantStepBuilder withCompensation(CommandEndpoint commandEndpoint, Function commandProvider); 16 | 17 | InvokeParticipantStepBuilder withCompensation(Predicate compensationPredicate, CommandEndpoint commandEndpoint, Function commandProvider); 18 | } 19 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/annotations/SagaParticipantOperation.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SagaParticipantOperation { 11 | 12 | Class commandClass() default Object.class; 13 | Class[] replyClasses() default {}; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/simpledsl/annotations/SagaParticipantProxy.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SagaParticipantProxy { 11 | String channel(); 12 | } 13 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/ConditionalSaga.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaDefinition; 4 | 5 | public class ConditionalSaga implements SimpleSaga { 6 | 7 | private SagaDefinition sagaDefinition = 8 | step() 9 | .invokeParticipant(ConditionalSagaData::isInvoke1, ConditionalSagaData::do1) 10 | .withCompensation(ConditionalSagaData::isInvoke1, ConditionalSagaData::undo1) 11 | .step() 12 | .invokeParticipant(ConditionalSagaData::do2) 13 | .build(); 14 | 15 | 16 | @Override 17 | public SagaDefinition getSagaDefinition() { 18 | return this.sagaDefinition; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/ConditionalSagaData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.consumer.CommandWithDestination; 4 | import io.eventuate.tram.commands.consumer.CommandWithDestinationBuilder; 5 | 6 | import java.util.Collections; 7 | import java.util.Map; 8 | 9 | public class ConditionalSagaData { 10 | 11 | private boolean invoke1; 12 | 13 | public ConditionalSagaData(boolean invoke1) { 14 | this.invoke1 = invoke1; 15 | } 16 | 17 | public ConditionalSagaData() { 18 | } 19 | 20 | public static Map DO1_COMMAND_EXTRA_HEADERS = Collections.singletonMap("k", "v"); 21 | 22 | public boolean isInvoke1() { 23 | return invoke1; 24 | } 25 | 26 | public void setInvoke1(boolean invoke1) { 27 | this.invoke1 = invoke1; 28 | } 29 | 30 | public CommandWithDestination do1() { 31 | return CommandWithDestinationBuilder.send(new Do1Command()).to("participant1").withExtraHeaders(DO1_COMMAND_EXTRA_HEADERS).build(); 32 | } 33 | 34 | public CommandWithDestination undo1() { 35 | return new CommandWithDestination("participant1", null, new Undo1Command()); 36 | } 37 | 38 | public CommandWithDestination do2() { 39 | return new CommandWithDestination("participant2", null, new ReserveCreditCommand()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/Do1Command.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class Do1Command implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/Handlers.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Failure; 4 | import io.eventuate.tram.commands.common.Success; 5 | 6 | public interface Handlers { 7 | void success1(ConditionalSagaData data, Success m); 8 | void failure1(ConditionalSagaData data, Failure m); 9 | void compensating1(ConditionalSagaData data, Success m); 10 | 11 | void success2(ConditionalSagaData data, Success m); 12 | void failure2(ConditionalSagaData data, Failure m); 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/LocalSaga.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaDefinition; 4 | import org.junit.Test; 5 | 6 | public class LocalSaga implements SimpleSaga { 7 | 8 | private SagaDefinition sagaDefinition; 9 | 10 | public LocalSaga(LocalSagaSteps steps) { 11 | this.sagaDefinition = 12 | step() 13 | .invokeLocal(steps::localStep1) 14 | .withCompensation(steps::localStep1Compensation) 15 | .step() 16 | .invokeParticipant(LocalSagaData::do2) 17 | .withCompensation(LocalSagaData::undo2) 18 | .step() 19 | .invokeLocal(steps::localStep3) 20 | .build(); 21 | } 22 | 23 | 24 | @Override 25 | public SagaDefinition getSagaDefinition() { 26 | return this.sagaDefinition; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/LocalSagaData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.consumer.CommandWithDestination; 4 | 5 | public class LocalSagaData { 6 | 7 | public CommandWithDestination do2() { 8 | return new CommandWithDestination("participant2", null, new ReserveCreditCommand()); 9 | } 10 | 11 | public CommandWithDestination undo2() { 12 | return new CommandWithDestination("participant2", null, new ReleaseCreditCommand()); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/LocalSagaSteps.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | public interface LocalSagaSteps { 4 | 5 | void localStep1(LocalSagaData data); 6 | void localStep1Compensation(LocalSagaData data); 7 | void localStep3(LocalSagaData data); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/ReleaseCreditCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class ReleaseCreditCommand implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/ReserveCreditCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class ReserveCreditCommand implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/Undo1Command.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class Undo1Command implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/WithHandlersSaga.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Failure; 4 | import io.eventuate.tram.commands.common.Success; 5 | import io.eventuate.tram.sagas.orchestration.SagaDefinition; 6 | 7 | 8 | public class WithHandlersSaga implements SimpleSaga { 9 | 10 | private SagaDefinition sagaDefinition; 11 | private Handlers handlers; 12 | 13 | public WithHandlersSaga(Handlers handlers) { 14 | this.handlers = handlers; 15 | sagaDefinition = step() 16 | .invokeParticipant(ConditionalSagaData::isInvoke1, ConditionalSagaData::do1) 17 | .onReply(Failure.class, handlers::failure1) 18 | .onReply(Success.class, handlers::success1) 19 | .withCompensation(ConditionalSagaData::isInvoke1, ConditionalSagaData::undo1) 20 | .onReply(Success.class, handlers::compensating1) 21 | .step() 22 | .invokeParticipant(ConditionalSagaData::do2) 23 | .onReply(Failure.class, handlers::failure2) 24 | .onReply(Success.class, handlers::success2) 25 | .build(); 26 | } 27 | 28 | 29 | 30 | @Override 31 | public SagaDefinition getSagaDefinition() { 32 | return this.sagaDefinition; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/localexceptions/InvalidOrderException.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.localexceptions; 2 | 3 | public class InvalidOrderException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/localexceptions/LocalExceptionCreateOrderSagaSteps.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.localexceptions; 2 | 3 | public interface LocalExceptionCreateOrderSagaSteps { 4 | 5 | void createOrder(LocalExceptionCreateOrderSagaData data); 6 | void rejectOrder(LocalExceptionCreateOrderSagaData data); 7 | void approveOrder(LocalExceptionCreateOrderSagaData data); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/nested/InMemoryCommandProducer.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.nested; 2 | 3 | import io.eventuate.tram.commands.common.DefaultCommandNameMapping; 4 | import io.eventuate.tram.commands.consumer.CommandReplyProducer; 5 | import io.eventuate.tram.commands.producer.CommandProducerImpl; 6 | import io.eventuate.tram.inmemory.InMemoryMessaging; 7 | 8 | public class InMemoryCommandProducer { 9 | 10 | public final CommandProducerImpl commandProducer; 11 | public final CommandReplyProducer commandReplyProducer; 12 | public final DefaultCommandNameMapping commandNameMapping; 13 | 14 | public InMemoryCommandProducer(CommandProducerImpl commandProducer, CommandReplyProducer commandReplyProducer, DefaultCommandNameMapping commandNameMapping) { 15 | 16 | this.commandProducer = commandProducer; 17 | this.commandReplyProducer = commandReplyProducer; 18 | this.commandNameMapping = commandNameMapping; 19 | } 20 | 21 | public static InMemoryCommandProducer make(InMemoryMessaging inMemoryMessaging) { 22 | DefaultCommandNameMapping commandNameMapping = new DefaultCommandNameMapping(); 23 | CommandProducerImpl commandProducer = new CommandProducerImpl(inMemoryMessaging.messageProducer, commandNameMapping); 24 | CommandReplyProducer commandReplyProducer = new CommandReplyProducer(inMemoryMessaging.messageProducer); 25 | return new InMemoryCommandProducer(commandProducer, commandReplyProducer, commandNameMapping); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/nested/InMemorySagaInstanceRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.nested; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaInstance; 4 | import io.eventuate.tram.sagas.orchestration.SagaInstanceRepository; 5 | 6 | import java.util.UUID; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | public class InMemorySagaInstanceRepository implements SagaInstanceRepository { 10 | 11 | private ConcurrentHashMap sagaInstances = new ConcurrentHashMap<>(); 12 | 13 | @Override 14 | public void save(SagaInstance sagaInstance) { 15 | String id = UUID.randomUUID().toString(); 16 | sagaInstance.setId(id); 17 | sagaInstances.put(id, sagaInstance); 18 | } 19 | 20 | @Override 21 | public SagaInstance find(String sagaType, String sagaId) { 22 | return sagaInstances.get(sagaId); 23 | } 24 | 25 | @Override 26 | public void update(SagaInstance sagaInstance) { 27 | sagaInstances.put(sagaInstance.getId(), sagaInstance); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/nested/InnerCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.nested; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class InnerCommand implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/nested/InnerSaga.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.nested; 2 | 3 | import io.eventuate.tram.commands.consumer.CommandReplyProducer; 4 | import io.eventuate.tram.sagas.orchestration.SagaDefinition; 5 | import io.eventuate.tram.sagas.simpledsl.SimpleSaga; 6 | 7 | import static io.eventuate.tram.commands.consumer.CommandHandlerReplyBuilder.withSuccess; 8 | 9 | public class InnerSaga implements SimpleSaga { 10 | private final SagaDefinition sagaDefinition; 11 | 12 | private final CommandReplyProducer commandReplyProducer; 13 | 14 | public InnerSaga(CommandReplyProducer commandReplyProducer) { 15 | this.commandReplyProducer = commandReplyProducer; 16 | this.sagaDefinition = step() 17 | .invokeParticipant(InnerSagaData::innerOperation) 18 | .build() 19 | ; 20 | } 21 | 22 | 23 | @Override 24 | public SagaDefinition getSagaDefinition() { 25 | return this.sagaDefinition; 26 | } 27 | 28 | @Override 29 | public void onSagaCompletedSuccessfully(String sagaId, InnerSagaData innerSagaData) { 30 | SimpleSaga.super.onSagaCompletedSuccessfully(sagaId, innerSagaData); 31 | commandReplyProducer.sendReplies(innerSagaData.getCommandReplyToken(), withSuccess()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/nested/InnerSagaData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.nested; 2 | 3 | import io.eventuate.tram.commands.consumer.CommandReplyToken; 4 | import io.eventuate.tram.commands.consumer.CommandWithDestination; 5 | 6 | public class InnerSagaData { 7 | private CommandReplyToken commandReplyToken; 8 | 9 | public InnerSagaData(CommandReplyToken commandReplyToken) { 10 | this.commandReplyToken = commandReplyToken; 11 | } 12 | 13 | private InnerSagaData() { 14 | } 15 | 16 | public CommandWithDestination innerOperation() { 17 | return new CommandWithDestination("customerService", null, new InnerCommand()); 18 | } 19 | 20 | public CommandReplyToken getCommandReplyToken() { 21 | return commandReplyToken; 22 | } 23 | 24 | public void setCommandReplyToken(CommandReplyToken commandReplyToken) { 25 | this.commandReplyToken = commandReplyToken; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/nested/OuterSaga.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.nested; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaDefinition; 4 | import io.eventuate.tram.sagas.simpledsl.SimpleSaga; 5 | 6 | public class OuterSaga implements SimpleSaga { 7 | private final SagaDefinition sagaDefinition; 8 | 9 | public OuterSaga() { 10 | this.sagaDefinition = step() 11 | .invokeParticipant(OuterSagaData::reserveCredit) 12 | .withCompensation(OuterSagaData::releaseCredit) 13 | .step() 14 | .invokeLocal(OuterSagaData::approveOrder) 15 | .build() 16 | ; 17 | } 18 | 19 | 20 | @Override 21 | public SagaDefinition getSagaDefinition() { 22 | return this.sagaDefinition; 23 | } 24 | 25 | @Override 26 | public void onSagaCompletedSuccessfully(String sagaId, OuterSagaData sagaData) { 27 | SimpleSaga.super.onSagaCompletedSuccessfully(sagaId, sagaData); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/nested/OuterSagaData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.nested; 2 | 3 | import io.eventuate.tram.commands.consumer.CommandWithDestination; 4 | import io.eventuate.tram.sagas.simpledsl.ReleaseCreditCommand; 5 | import io.eventuate.tram.sagas.simpledsl.ReserveCreditCommand; 6 | 7 | public class OuterSagaData { 8 | public CommandWithDestination reserveCredit() { 9 | return new CommandWithDestination("customerService", null, new ReserveCreditCommand()); 10 | } 11 | 12 | public CommandWithDestination releaseCredit() { 13 | return new CommandWithDestination("customerService", null, new ReleaseCreditCommand()); 14 | } 15 | 16 | public void approveOrder() { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/notifications/ConditionalNotificationBasedCreateOrderSagaSteps.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.notifications; 2 | 3 | public interface ConditionalNotificationBasedCreateOrderSagaSteps { 4 | 5 | void createOrder(ConditionalNotificationBasedCreateOrderSagaData data); 6 | void rejectOrder(ConditionalNotificationBasedCreateOrderSagaData data); 7 | void approveOrder(ConditionalNotificationBasedCreateOrderSagaData data); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/notifications/FulfillOrder.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.notifications; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class FulfillOrder implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/notifications/NotificationBasedCreateOrderSagaData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.notifications; 2 | 3 | import io.eventuate.tram.commands.consumer.CommandWithDestination; 4 | import io.eventuate.tram.sagas.simpledsl.ReleaseCreditCommand; 5 | import io.eventuate.tram.sagas.simpledsl.ReserveCreditCommand; 6 | 7 | public class NotificationBasedCreateOrderSagaData { 8 | 9 | public CommandWithDestination reserveCredit() { 10 | return new CommandWithDestination("customerService", null, new ReserveCreditCommand()); 11 | } 12 | 13 | public CommandWithDestination releaseCredit() { 14 | return new CommandWithDestination("customerService", null, new ReleaseCreditCommand()); 15 | } 16 | 17 | public CommandWithDestination reserveInventory() { 18 | return new CommandWithDestination("inventoryService", null, new ReserveInventory()); 19 | } 20 | 21 | public CommandWithDestination releaseInventory() { 22 | return new CommandWithDestination("inventoryService", null, new ReleaseInventory()); 23 | } 24 | 25 | public CommandWithDestination fulfillOrder() { 26 | return new CommandWithDestination("fulfillmentService", null, new FulfillOrder()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/notifications/NotificationBasedCreateOrderSagaSteps.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.notifications; 2 | 3 | public interface NotificationBasedCreateOrderSagaSteps { 4 | 5 | void createOrder(NotificationBasedCreateOrderSagaData data); 6 | void rejectOrder(NotificationBasedCreateOrderSagaData data); 7 | void approveOrder(NotificationBasedCreateOrderSagaData data); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/notifications/ReleaseInventory.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.notifications; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class ReleaseInventory implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/simpledsl/notifications/ReserveInventory.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.simpledsl.notifications; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class ReserveInventory implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-common") 4 | api "io.eventuate.tram.core:eventuate-tram-events:$eventuateTramVersion" 5 | api "javax.annotation:javax.annotation-api:1.3.2" 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/DestinationAndResource.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import org.apache.commons.lang.builder.EqualsBuilder; 4 | import org.apache.commons.lang.builder.HashCodeBuilder; 5 | 6 | public class DestinationAndResource { 7 | private String destination; 8 | private String resource; 9 | 10 | @Override 11 | public boolean equals(Object o) { 12 | if (this == o) return true; 13 | 14 | if (o == null || getClass() != o.getClass()) return false; 15 | 16 | DestinationAndResource that = (DestinationAndResource) o; 17 | 18 | return new EqualsBuilder() 19 | .append(destination, that.destination) 20 | .append(resource, that.resource) 21 | .isEquals(); 22 | } 23 | 24 | @Override 25 | public int hashCode() { 26 | return new HashCodeBuilder(17, 37) 27 | .append(destination) 28 | .append(resource) 29 | .toHashCode(); 30 | } 31 | 32 | public DestinationAndResource(String destination, String resource) { 33 | this.destination = destination; 34 | this.resource = resource; 35 | } 36 | 37 | public String getDestination() { 38 | return destination; 39 | } 40 | 41 | public String getResource() { 42 | return resource; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/EnlistedAggregate.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import org.apache.commons.lang.builder.EqualsBuilder; 4 | import org.apache.commons.lang.builder.HashCodeBuilder; 5 | import org.apache.commons.lang.builder.ReflectionToStringBuilder; 6 | import org.apache.commons.lang.builder.ToStringBuilder; 7 | 8 | public class EnlistedAggregate { 9 | private final Class aggregateClass; 10 | private final Object aggregateId; 11 | 12 | public EnlistedAggregate(Class aggregateClass, Object aggregateId) { 13 | this.aggregateClass = aggregateClass; 14 | this.aggregateId = aggregateId; 15 | } 16 | 17 | @Override 18 | public int hashCode() { 19 | return HashCodeBuilder.reflectionHashCode(this); 20 | } 21 | 22 | @Override 23 | public boolean equals(Object obj) { 24 | return EqualsBuilder.reflectionEquals(this, obj); 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return ToStringBuilder.reflectionToString(this); 30 | } 31 | 32 | public Class getAggregateClass() { 33 | return aggregateClass; 34 | } 35 | 36 | public Object getAggregateId() { 37 | return aggregateId; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/EventClassAndAggregateId.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | 4 | import io.eventuate.tram.events.common.DomainEvent; 5 | 6 | public class EventClassAndAggregateId { 7 | private final Class eventClass; 8 | private final Long aggregateId; 9 | 10 | public EventClassAndAggregateId(Class eventClass, Long aggregateId) { 11 | this.eventClass = eventClass; 12 | this.aggregateId = aggregateId; 13 | } 14 | 15 | public Class getEventClass() { 16 | return eventClass; 17 | } 18 | 19 | public Long getAggregateId() { 20 | return aggregateId; 21 | } 22 | 23 | public boolean isFor(String aggregateType, long aggregateId, String eventType) { 24 | return eventClass.getName().equals(eventType) && this.aggregateId.equals(aggregateId); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/EventStartingHandler.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | 4 | import io.eventuate.tram.events.common.DomainEvent; 5 | import io.eventuate.tram.events.subscriber.DomainEventEnvelope; 6 | 7 | public interface EventStartingHandler { 8 | void apply(Data data, DomainEventEnvelope event); 9 | } 10 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/EventToPublish.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import io.eventuate.tram.events.common.DomainEvent; 4 | 5 | import java.util.List; 6 | 7 | public class EventToPublish { 8 | private final Class aggregateType; 9 | private final String aggregateId; 10 | private final List domainEvents; 11 | 12 | public EventToPublish(Class aggregateType, String aggregateId, List domainEvents) { 13 | this.aggregateType = aggregateType; 14 | this.aggregateId = aggregateId; 15 | this.domainEvents = domainEvents; 16 | } 17 | 18 | public Class getAggregateType() { 19 | return aggregateType; 20 | } 21 | 22 | public String getAggregateId() { 23 | return aggregateId; 24 | } 25 | 26 | public List getDomainEvents() { 27 | return domainEvents; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/JdbcSqlQueryRow.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | class JdbcSqlQueryRow implements SqlQueryRow { 7 | private final ResultSet rs; 8 | 9 | public JdbcSqlQueryRow(ResultSet rs) { 10 | this.rs = rs; 11 | } 12 | 13 | @Override 14 | public String getString(String name) { 15 | try { 16 | return rs.getString(name); 17 | } catch (SQLException e) { 18 | throw new RuntimeException(e); 19 | } 20 | } 21 | 22 | @Override 23 | public boolean getBoolean(String name) { 24 | try { 25 | return rs.getBoolean(name); 26 | } catch (SQLException e) { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/PendingSagaCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | 4 | import io.eventuate.tram.commands.common.Command; 5 | 6 | public class PendingSagaCommand { 7 | private String destination; 8 | private String resource; 9 | private Command command; 10 | 11 | public PendingSagaCommand(String destination, String resource, Command command) { 12 | this.destination = destination; 13 | this.resource = resource; 14 | this.command = command; 15 | } 16 | 17 | public String getDestination() { 18 | return destination; 19 | } 20 | 21 | public String getResource() { 22 | return resource; 23 | } 24 | 25 | public Command getCommand() { 26 | return command; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/Saga.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | 4 | public interface Saga { 5 | 6 | SagaDefinition getSagaDefinition(); 7 | 8 | default String getSagaType() { 9 | return getClass().getName().replace("$", "_DLR_"); 10 | } 11 | 12 | default void onStarting(String sagaId, Data data) { } 13 | default void onSagaCompletedSuccessfully(String sagaId, Data data) { } 14 | default void onSagaRolledBack(String sagaId, Data data) { } 15 | default void onSagaFailed(String sagaId, Data data) {}; 16 | } 17 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SagaCommandProducer.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import java.util.List; 4 | 5 | public interface SagaCommandProducer { 6 | String sendCommands(String sagaType, String sagaId, List commands, String sagaReplyChannel); 7 | } 8 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SagaCompletedForAggregateEvent.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import io.eventuate.tram.events.common.DomainEvent; 4 | 5 | public class SagaCompletedForAggregateEvent implements DomainEvent { 6 | public SagaCompletedForAggregateEvent(String sagaId) { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SagaDataSerde.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | 4 | import io.eventuate.common.json.mapper.JSonMapper; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | 9 | public class SagaDataSerde { 10 | private static Logger logger = LoggerFactory.getLogger(SagaDataSerde.class); 11 | 12 | public static SerializedSagaData serializeSagaData(Data sagaData) { 13 | return new SerializedSagaData(sagaData.getClass().getName(), JSonMapper.toJson(sagaData)); 14 | } 15 | 16 | public static Data deserializeSagaData(SerializedSagaData serializedSagaData) { 17 | Class clasz = null; 18 | try { 19 | clasz = Thread.currentThread().getContextClassLoader().loadClass(serializedSagaData.getSagaDataType()); 20 | } catch (ClassNotFoundException e) { 21 | logger.error("Class not found", e); 22 | throw new RuntimeException("Class not found", e); 23 | } 24 | Object x = JSonMapper.fromJson(serializedSagaData.getSagaDataJSON(), clasz); 25 | return (Data)x; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SagaDefinition.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | 4 | import io.eventuate.tram.messaging.common.Message; 5 | 6 | public interface SagaDefinition { 7 | 8 | SagaActions start(Data sagaData); 9 | 10 | SagaActions handleReply(String sagaType, String sagaId, String currentState, Data sagaData, Message message); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SagaInstanceData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | public class SagaInstanceData { 4 | 5 | private final SagaInstance sagaInstance; 6 | private final Data sagaData; 7 | 8 | public SagaInstanceData(SagaInstance sagaInstance, Data sagaData) { 9 | this.sagaInstance = sagaInstance; 10 | this.sagaData = sagaData; 11 | } 12 | 13 | public SagaInstance getSagaInstance() { 14 | return sagaInstance; 15 | } 16 | 17 | public Data getSagaData() { 18 | return sagaData; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SagaInstanceFactory.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.Collection; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.ConcurrentMap; 9 | 10 | public class SagaInstanceFactory { 11 | private Logger logger = LoggerFactory.getLogger(this.getClass()); 12 | 13 | private ConcurrentMap, SagaManager> sagaManagers = new ConcurrentHashMap<>(); 14 | 15 | public SagaInstanceFactory(SagaManagerFactory sagaManagerFactory, Collection> sagas) { 16 | for (Saga saga : sagas) { 17 | sagaManagers.put(saga, makeSagaManager(sagaManagerFactory, saga)); 18 | } 19 | } 20 | 21 | public SagaInstance create(Saga saga, SagaData data) { 22 | SagaManager sagaManager = (SagaManager)sagaManagers.get(saga); 23 | if (sagaManager == null) 24 | throw new RuntimeException(("No SagaManager for " + saga)); 25 | return sagaManager.create(data); 26 | } 27 | 28 | private SagaManager makeSagaManager(SagaManagerFactory sagaManagerFactory, Saga saga) { 29 | SagaManagerImpl sagaManager = sagaManagerFactory.make(saga); 30 | sagaManager.subscribeToReplyChannel(); 31 | return sagaManager; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SagaInstanceRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | public interface SagaInstanceRepository { 4 | 5 | void save(SagaInstance sagaInstance); 6 | SagaInstance find(String sagaType, String sagaId); 7 | void update(SagaInstance sagaInstance); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SagaManager.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import java.util.Optional; 4 | 5 | public interface SagaManager { 6 | SagaInstance create(Data sagaData); 7 | 8 | // TODO or should the saga have a pseudo-step that locks the resource 9 | 10 | void subscribeToReplyChannel(); 11 | 12 | SagaInstance create(Data sagaData, Optional lockTarget); 13 | SagaInstance create(Data data, Class targetClass, Object targetId); 14 | } 15 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SagaManagerFactory.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import io.eventuate.tram.commands.producer.CommandProducer; 4 | import io.eventuate.tram.messaging.consumer.MessageConsumer; 5 | import io.eventuate.tram.sagas.common.SagaLockManager; 6 | 7 | public class SagaManagerFactory { 8 | 9 | private final SagaInstanceRepository sagaInstanceRepository; 10 | private final CommandProducer commandProducer; 11 | private final MessageConsumer messageConsumer; 12 | private final SagaLockManager sagaLockManager; 13 | private final SagaCommandProducer sagaCommandProducer; 14 | 15 | public SagaManagerFactory(SagaInstanceRepository sagaInstanceRepository, CommandProducer 16 | commandProducer, MessageConsumer messageConsumer, 17 | SagaLockManager sagaLockManager, SagaCommandProducer sagaCommandProducer) { 18 | this.sagaInstanceRepository = sagaInstanceRepository; 19 | this.commandProducer = commandProducer; 20 | this.messageConsumer = messageConsumer; 21 | this.sagaLockManager = sagaLockManager; 22 | this.sagaCommandProducer = sagaCommandProducer; 23 | } 24 | 25 | public SagaManagerImpl make(Saga saga) { 26 | return new SagaManagerImpl<>(saga, sagaInstanceRepository, commandProducer, messageConsumer, sagaLockManager, sagaCommandProducer); 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SagaStateMachineAction.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | public interface SagaStateMachineAction { 4 | 5 | SagaActions apply(Data data, Reply reply); 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SagaStateMachineEventHandler.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | 4 | import io.eventuate.tram.events.common.DomainEvent; 5 | import io.eventuate.tram.events.subscriber.DomainEventEnvelope; 6 | 7 | public interface SagaStateMachineEventHandler { 8 | 9 | SagaActions apply(Data data, DomainEventEnvelope event); 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SagaTypeAndId.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | public class SagaTypeAndId { 4 | private final String sagaType; 5 | private final String sagaId; 6 | 7 | public SagaTypeAndId(String sagaType, String sagaId) { 8 | this.sagaType = sagaType; 9 | this.sagaId = sagaId; 10 | } 11 | 12 | public String getSagaId() { 13 | return sagaId; 14 | } 15 | 16 | public String getSagaType() { 17 | 18 | return sagaType; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SerializedSagaData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | public class SerializedSagaData { 4 | 5 | private String sagaDataType; 6 | private String sagaDataJSON; 7 | 8 | public SerializedSagaData(String sagaDataType, String sagaDataJSON) { 9 | this.sagaDataType = sagaDataType; 10 | this.sagaDataJSON = sagaDataJSON; 11 | } 12 | 13 | public String getSagaDataJSON() { 14 | return sagaDataJSON; 15 | } 16 | 17 | public String getSagaDataType() { 18 | return sagaDataType; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/SqlQueryRow.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | public interface SqlQueryRow { 4 | String getString(String name); 5 | boolean getBoolean(String name); 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/main/java/io/eventuate/tram/sagas/orchestration/StartingHandler.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import java.util.function.Function; 4 | 5 | public interface StartingHandler extends Function { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/test/java/io/eventuate/tram/sagas/orchestration/RuntimeExceptionAnswer.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import org.mockito.invocation.InvocationOnMock; 4 | import org.mockito.stubbing.Answer; 5 | 6 | public class RuntimeExceptionAnswer implements Answer { 7 | 8 | public static final RuntimeExceptionAnswer INSTANCE = new RuntimeExceptionAnswer(); 9 | 10 | @Override 11 | public Object answer(InvocationOnMock invocation) throws Throwable { 12 | throw new RuntimeException(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/test/java/io/eventuate/tram/sagas/orchestration/SagaCommandHeadersTest.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import io.eventuate.tram.sagas.common.SagaCommandHeaders; 4 | import io.eventuate.tram.sagas.common.SagaReplyHeaders; 5 | import org.junit.Test; 6 | 7 | public class SagaCommandHeadersTest { 8 | 9 | @Test 10 | public void shouldDoSomething() { 11 | System.out.println("SAGA_TYPE=" + SagaCommandHeaders.SAGA_TYPE); 12 | System.out.println("REPLY_SAGA_TYPE=" + SagaReplyHeaders.REPLY_SAGA_TYPE); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/test/java/io/eventuate/tram/sagas/orchestration/SagaInstanceRepositoryJdbcCustomSchemaTest.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import io.eventuate.common.jdbc.EventuateSchema; 4 | 5 | public class SagaInstanceRepositoryJdbcCustomSchemaTest extends SagaInstanceRepositoryJdbcSchemaTest { 6 | 7 | private String custom = "custom"; 8 | 9 | @Override 10 | protected SagaInstanceRepositorySql getSagaInstanceRepositoryJdbcSql() { 11 | return new SagaInstanceRepositorySql(new EventuateSchema(custom)); 12 | } 13 | 14 | @Override 15 | protected String getExpectedPrefix() { 16 | return custom + "."; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/test/java/io/eventuate/tram/sagas/orchestration/SagaInstanceRepositoryJdbcDefaultSchemaTest.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import io.eventuate.common.jdbc.EventuateSchema; 4 | 5 | public class SagaInstanceRepositoryJdbcDefaultSchemaTest extends SagaInstanceRepositoryJdbcSchemaTest { 6 | 7 | @Override 8 | protected SagaInstanceRepositorySql getSagaInstanceRepositoryJdbcSql() { 9 | return new SagaInstanceRepositorySql(new EventuateSchema()); 10 | } 11 | 12 | @Override 13 | protected String getExpectedPrefix() { 14 | return EventuateSchema.DEFAULT_SCHEMA + "."; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/test/java/io/eventuate/tram/sagas/orchestration/SagaInstanceRepositoryJdbcEmptySchemaTest.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import io.eventuate.common.jdbc.EventuateSchema; 4 | 5 | public class SagaInstanceRepositoryJdbcEmptySchemaTest extends SagaInstanceRepositoryJdbcSchemaTest { 6 | 7 | @Override 8 | protected SagaInstanceRepositorySql getSagaInstanceRepositoryJdbcSql() { 9 | return new SagaInstanceRepositorySql(new EventuateSchema(EventuateSchema.EMPTY_SCHEMA)); 10 | } 11 | 12 | @Override 13 | protected String getExpectedPrefix() { 14 | return ""; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/test/java/io/eventuate/tram/sagas/orchestration/TestCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class TestCommand implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/test/java/io/eventuate/tram/sagas/orchestration/TestSaga.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | public interface TestSaga extends Saga { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-orchestration/src/test/java/io/eventuate/tram/sagas/orchestration/TestSagaData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.orchestration; 2 | 3 | import org.apache.commons.lang.builder.EqualsBuilder; 4 | import org.apache.commons.lang.builder.HashCodeBuilder; 5 | 6 | public class TestSagaData { 7 | private String label; 8 | 9 | public TestSagaData() { 10 | } 11 | 12 | public TestSagaData(String label) { 13 | this.label = label; 14 | } 15 | 16 | public String getLabel() { 17 | return label; 18 | } 19 | 20 | public void setLabel(String label) { 21 | this.label = label; 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | 28 | if (o == null || getClass() != o.getClass()) return false; 29 | 30 | TestSagaData that = (TestSagaData) o; 31 | 32 | return new EqualsBuilder() 33 | .append(label, that.label) 34 | .isEquals(); 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | return new HashCodeBuilder(17, 37) 40 | .append(label) 41 | .toHashCode(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-participant/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-common") 4 | } 5 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-participant/src/main/java/io/eventuate/tram/sagas/participant/AbstractSagaCommandHandlersBuilder.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.participant; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | import io.eventuate.tram.commands.consumer.CommandMessage; 5 | import io.eventuate.tram.messaging.common.Message; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | import java.util.function.Consumer; 10 | import java.util.function.Function; 11 | 12 | public interface AbstractSagaCommandHandlersBuilder { 13 | SagaCommandHandlerBuilder onMessageReturningMessages(Class commandClass, 14 | Function, List> handler); 15 | 16 | SagaCommandHandlerBuilder onMessageReturningOptionalMessage(Class commandClass, 17 | Function, Optional> handler); 18 | 19 | SagaCommandHandlerBuilder onMessage(Class commandClass, 20 | Function, Message> handler); 21 | 22 | SagaCommandHandlerBuilder onMessage(Class commandClass, Consumer> handler); 23 | } 24 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-participant/src/main/java/io/eventuate/tram/sagas/participant/PostLockFunction.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.participant; 2 | 3 | import io.eventuate.tram.commands.consumer.CommandMessage; 4 | import io.eventuate.tram.commands.consumer.PathVariables; 5 | import io.eventuate.tram.messaging.common.Message; 6 | import io.eventuate.tram.sagas.common.LockTarget; 7 | 8 | public interface PostLockFunction { 9 | 10 | public LockTarget apply(CommandMessage cm, PathVariables pvs, Message reply); 11 | } 12 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-participant/src/main/java/io/eventuate/tram/sagas/participant/SagaReplyMessage.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.participant; 2 | 3 | import io.eventuate.tram.messaging.common.MessageImpl; 4 | import io.eventuate.tram.sagas.common.LockTarget; 5 | 6 | import java.util.Map; 7 | import java.util.Optional; 8 | 9 | public class SagaReplyMessage extends MessageImpl { 10 | private Optional lockTarget; 11 | 12 | public SagaReplyMessage(String body, Map headers, Optional lockTarget) { 13 | super(body, headers); 14 | this.lockTarget = lockTarget; 15 | } 16 | 17 | public Optional getLockTarget() { 18 | return lockTarget; 19 | } 20 | 21 | public boolean hasLockTarget() { 22 | return lockTarget.isPresent(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-common/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-common") 4 | 5 | api "io.eventuate.tram.core:eventuate-tram-reactive-commands:$eventuateTramVersion" 6 | api "io.eventuate.common:eventuate-common-reactive-jdbc:$eventuateCommonVersion" 7 | } 8 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-common/src/main/java/io/eventuate/tram/sagas/reactive/common/ReactiveSagaLockManager.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.common; 2 | 3 | import io.eventuate.tram.messaging.common.Message; 4 | import reactor.core.publisher.Mono; 5 | 6 | public interface ReactiveSagaLockManager { 7 | 8 | Mono claimLock(String sagaType, String sagaId, String target); 9 | 10 | Mono stashMessage(String sagaType, String sagaId, String target, Message message); 11 | 12 | Mono unlock(String sagaId, String target); 13 | } 14 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-reactive-orchestration") 4 | api project(":eventuate-tram-sagas-orchestration-simple-dsl") 5 | } 6 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/reactive/simpledsl/AbstractReactiveParticipantInvocation.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import java.util.Optional; 4 | import java.util.function.Predicate; 5 | 6 | public abstract class AbstractReactiveParticipantInvocation implements ReactiveParticipantInvocation { 7 | 8 | private Optional> invocablePredicate; 9 | 10 | protected AbstractReactiveParticipantInvocation(Optional> invocablePredicate) { 11 | this.invocablePredicate = invocablePredicate; 12 | } 13 | 14 | @Override 15 | public boolean isInvocable(Data data) { 16 | return invocablePredicate.map(p -> p.test(data)).orElse(true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/reactive/simpledsl/ReactiveLocalStepBuilder.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.reactive.orchestration.ReactiveSagaDefinition; 4 | import org.reactivestreams.Publisher; 5 | 6 | import java.util.Optional; 7 | import java.util.function.Function; 8 | 9 | public class ReactiveLocalStepBuilder { 10 | private final SimpleReactiveSagaDefinitionBuilder parent; 11 | private final Function> localFunction; 12 | private Optional>> compensation = Optional.empty(); 13 | 14 | public ReactiveLocalStepBuilder(SimpleReactiveSagaDefinitionBuilder parent, Function> localFunction) { 15 | this.parent = parent; 16 | this.localFunction = localFunction; 17 | } 18 | 19 | public ReactiveLocalStepBuilder withCompensation(Function> localCompensation) { 20 | this.compensation = Optional.of(localCompensation); 21 | return this; 22 | } 23 | 24 | 25 | public ReactiveStepBuilder step() { 26 | parent.addStep(new ReactiveLocalStep<>(localFunction, compensation)); 27 | return new ReactiveStepBuilder<>(parent); 28 | } 29 | 30 | public ReactiveSagaDefinition build() { 31 | parent.addStep(new ReactiveLocalStep<>(localFunction, compensation)); 32 | return parent.build(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/reactive/simpledsl/ReactiveParticipantInvocation.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.messaging.common.Message; 4 | import io.eventuate.tram.sagas.orchestration.CommandWithDestinationAndType; 5 | import org.reactivestreams.Publisher; 6 | 7 | 8 | public interface ReactiveParticipantInvocation { 9 | 10 | boolean isSuccessfulReply(Message message); 11 | 12 | boolean isInvocable(Data data); 13 | 14 | Publisher makeCommandToSend(Data data); 15 | } 16 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/reactive/simpledsl/ReactiveSagaActionsProvider.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaActions; 4 | import io.eventuate.tram.sagas.simpledsl.AbstractSagaActionsProvider; 5 | import org.reactivestreams.Publisher; 6 | 7 | import java.util.function.Supplier; 8 | 9 | public class ReactiveSagaActionsProvider extends AbstractSagaActionsProvider>> { 10 | 11 | public ReactiveSagaActionsProvider(SagaActions sagaActions) { 12 | super(sagaActions); 13 | } 14 | 15 | public ReactiveSagaActionsProvider(Supplier>> sagaActionsSupplier) { 16 | super(sagaActionsSupplier); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/reactive/simpledsl/ReactiveSagaStep.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.messaging.common.Message; 4 | import io.eventuate.tram.sagas.simpledsl.ISagaStep; 5 | import io.eventuate.tram.sagas.simpledsl.StepOutcome; 6 | import org.reactivestreams.Publisher; 7 | 8 | import java.util.Optional; 9 | import java.util.function.BiFunction; 10 | 11 | public interface ReactiveSagaStep extends ISagaStep { 12 | Optional>> getReplyHandler(Message message, boolean compensating); 13 | 14 | Publisher makeStepOutcome(Data data, boolean compensating); 15 | } 16 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/reactive/simpledsl/ReactiveStepToExecute.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaActions; 4 | import io.eventuate.tram.sagas.simpledsl.AbstractStepToExecute; 5 | import io.eventuate.tram.sagas.simpledsl.SagaExecutionState; 6 | import org.reactivestreams.Publisher; 7 | import reactor.core.publisher.Mono; 8 | 9 | public class ReactiveStepToExecute extends AbstractStepToExecute> { 10 | 11 | 12 | public ReactiveStepToExecute(ReactiveSagaStep step, int skipped, boolean compensating) { 13 | super(step, skipped, compensating); 14 | } 15 | 16 | 17 | public Publisher> executeStep(Data data, SagaExecutionState currentState) { 18 | SagaExecutionState newState = currentState.nextState(size()); 19 | SagaActions.Builder builder = SagaActions.builder(); 20 | boolean compensating = currentState.isCompensating(); 21 | 22 | return Mono 23 | .from(step.makeStepOutcome(data, this.compensating)) 24 | .map(outcome -> { 25 | outcome.visit(builder::withIsLocal, builder::withCommands); 26 | return outcome; 27 | }) 28 | .then(Mono.fromSupplier(() -> 29 | makeSagaActions(builder, data, newState, compensating))); 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/reactive/simpledsl/ReactiveWithCompensationBuilder.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | import io.eventuate.tram.commands.consumer.CommandWithDestination; 5 | import io.eventuate.tram.sagas.simpledsl.CommandEndpoint; 6 | import org.reactivestreams.Publisher; 7 | 8 | import java.util.function.Function; 9 | import java.util.function.Predicate; 10 | 11 | public interface ReactiveWithCompensationBuilder { 12 | 13 | InvokeReactiveParticipantStepBuilder withCompensation(Function> compensation); 14 | 15 | InvokeReactiveParticipantStepBuilder withCompensation(Predicate compensationPredicate, 16 | Function> compensation); 17 | 18 | InvokeReactiveParticipantStepBuilder withCompensation(CommandEndpoint commandEndpoint, 19 | Function> commandProvider); 20 | 21 | InvokeReactiveParticipantStepBuilder withCompensation(Predicate compensationPredicate, 22 | CommandEndpoint commandEndpoint, 23 | Function> commandProvider); 24 | } 25 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/reactive/simpledsl/SimpleReactiveSaga.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.reactive.orchestration.ReactiveSaga; 4 | 5 | public interface SimpleReactiveSaga extends ReactiveSaga, SimpleReactiveSagaDsl { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/reactive/simpledsl/SimpleReactiveSagaDefinitionBuilder.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.reactive.orchestration.ReactiveSagaDefinition; 4 | 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | public class SimpleReactiveSagaDefinitionBuilder { 9 | 10 | private List> sagaSteps = new LinkedList<>(); 11 | 12 | public void addStep(ReactiveSagaStep sagaStep) { 13 | sagaSteps.add(sagaStep); 14 | } 15 | 16 | public ReactiveSagaDefinition build() { 17 | return new SimpleReactiveSagaDefinition<>(sagaSteps); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/main/java/io/eventuate/tram/sagas/reactive/simpledsl/SimpleReactiveSagaDsl.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | public interface SimpleReactiveSagaDsl { 4 | default ReactiveStepBuilder step() { 5 | SimpleReactiveSagaDefinitionBuilder builder = new SimpleReactiveSagaDefinitionBuilder<>(); 6 | return new ReactiveStepBuilder<>(builder); 7 | } 8 | } -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/reactive/simpledsl/LocalSagaData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.commands.consumer.CommandWithDestination; 4 | import reactor.core.publisher.Mono; 5 | 6 | public class LocalSagaData { 7 | 8 | public Mono do2() { 9 | return Mono.just(new CommandWithDestination("participant2", null, new ReserveCreditCommand())); 10 | } 11 | 12 | public Mono undo2() { 13 | return Mono.just(new CommandWithDestination("participant2", null, new ReleaseCreditCommand())); 14 | } 15 | 16 | public Mono notify3() { 17 | return Mono.just(new CommandWithDestination("participant3", null, new NotifyCommand())); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/reactive/simpledsl/LocalSagaSteps.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public interface LocalSagaSteps { 6 | 7 | Mono localStep1(LocalSagaData data); 8 | Mono localStep1Compensation(LocalSagaData data); 9 | Mono localStep3(LocalSagaData data); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/reactive/simpledsl/NotifyCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class NotifyCommand implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/reactive/simpledsl/ReactiveLocalSaga.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.reactive.orchestration.ReactiveSagaDefinition; 4 | 5 | public class ReactiveLocalSaga implements SimpleReactiveSaga { 6 | 7 | private ReactiveSagaDefinition sagaDefinition; 8 | 9 | public ReactiveLocalSaga(LocalSagaSteps steps) { 10 | this.sagaDefinition = 11 | step() 12 | .invokeLocal(steps::localStep1) 13 | .withCompensation(steps::localStep1Compensation) 14 | .step() 15 | .invokeParticipant(LocalSagaData::do2) 16 | .withCompensation(LocalSagaData::undo2) 17 | .step() 18 | .invokeLocal(steps::localStep3) 19 | .build(); 20 | } 21 | 22 | 23 | @Override 24 | public ReactiveSagaDefinition getSagaDefinition() { 25 | return this.sagaDefinition; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/reactive/simpledsl/ReactiveLocalSagaWithNotification.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.sagas.reactive.orchestration.ReactiveSagaDefinition; 4 | 5 | public class ReactiveLocalSagaWithNotification implements SimpleReactiveSaga { 6 | 7 | private ReactiveSagaDefinition sagaDefinition; 8 | 9 | public ReactiveLocalSagaWithNotification(LocalSagaSteps steps) { 10 | this.sagaDefinition = 11 | step() 12 | .invokeLocal(steps::localStep1) 13 | .withCompensation(steps::localStep1Compensation) 14 | .step() 15 | .invokeParticipant(LocalSagaData::do2) 16 | .withCompensationNotification(LocalSagaData::undo2) 17 | .step() 18 | .invokeLocal(steps::localStep3) 19 | .step() 20 | .notifyParticipant(LocalSagaData::notify3) 21 | .build(); 22 | } 23 | 24 | 25 | @Override 26 | public ReactiveSagaDefinition getSagaDefinition() { 27 | return this.sagaDefinition; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/reactive/simpledsl/ReleaseCreditCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class ReleaseCreditCommand implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/reactive/simpledsl/ReserveCreditCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class ReserveCreditCommand implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration-simple-dsl/src/test/java/io/eventuate/tram/sagas/reactive/simpledsl/framework/MessageWithDestination.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.simpledsl.framework; 2 | 3 | import io.eventuate.tram.messaging.common.Message; 4 | import org.apache.commons.lang.builder.ToStringBuilder; 5 | 6 | public class MessageWithDestination { 7 | private final String destination; 8 | private final Message message; 9 | 10 | @Override 11 | public String toString() { 12 | return ToStringBuilder.reflectionToString(this); 13 | } 14 | 15 | public MessageWithDestination(String destination, Message message) { 16 | this.destination = destination; 17 | this.message = message; 18 | } 19 | 20 | public String getDestination() { 21 | return destination; 22 | } 23 | 24 | public Message getMessage() { 25 | return message; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-orchestration") 4 | api project(":eventuate-tram-sagas-reactive-common") 5 | api "io.eventuate.tram.core:eventuate-tram-reactive-events:$eventuateTramVersion" 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration/src/main/java/io/eventuate/tram/sagas/reactive/orchestration/ReactiveSaga.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.orchestration; 2 | 3 | 4 | import reactor.core.publisher.Mono; 5 | 6 | public interface ReactiveSaga { 7 | 8 | ReactiveSagaDefinition getSagaDefinition(); 9 | 10 | default String getSagaType() { 11 | return getClass().getName().replace("$", "_DLR_"); 12 | } 13 | 14 | default Mono onStarting(String sagaId, Data data) { 15 | return Mono.empty(); 16 | } 17 | 18 | default Mono onSagaCompletedSuccessfully(String sagaId, Data data) { 19 | return Mono.empty(); 20 | } 21 | 22 | default Mono onSagaRolledBack(String sagaId, Data data) { 23 | return Mono.empty(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration/src/main/java/io/eventuate/tram/sagas/reactive/orchestration/ReactiveSagaDefinition.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.orchestration; 2 | 3 | 4 | import io.eventuate.tram.messaging.common.Message; 5 | import io.eventuate.tram.sagas.orchestration.SagaActions; 6 | import org.reactivestreams.Publisher; 7 | 8 | public interface ReactiveSagaDefinition { 9 | 10 | Publisher> start(Data sagaData); 11 | 12 | Publisher> handleReply(String sagaType, String sagaId, String currentState, Data sagaData, Message message); 13 | } 14 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration/src/main/java/io/eventuate/tram/sagas/reactive/orchestration/ReactiveSagaInstanceRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.orchestration; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaInstance; 4 | import reactor.core.publisher.Mono; 5 | 6 | public interface ReactiveSagaInstanceRepository { 7 | 8 | Mono save(SagaInstance sagaInstance); 9 | Mono find(String sagaType, String sagaId); 10 | Mono update(SagaInstance sagaInstance); 11 | } 12 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration/src/main/java/io/eventuate/tram/sagas/reactive/orchestration/ReactiveSagaManager.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.orchestration; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SagaInstance; 4 | import reactor.core.publisher.Mono; 5 | 6 | import java.util.Optional; 7 | 8 | public interface ReactiveSagaManager { 9 | Mono create(Data sagaData); 10 | void subscribeToReplyChannel(); 11 | Mono create(Data sagaData, Optional lockTarget); 12 | Mono create(Data data, Class targetClass, Object targetId); 13 | } 14 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-orchestration/src/main/java/io/eventuate/tram/sagas/reactive/orchestration/ReactiveSqlQueryRow.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.orchestration; 2 | 3 | import io.eventuate.tram.sagas.orchestration.SqlQueryRow; 4 | import io.r2dbc.spi.Row; 5 | 6 | public class ReactiveSqlQueryRow implements SqlQueryRow { 7 | private Row row; 8 | 9 | public ReactiveSqlQueryRow(Row row) { 10 | this.row = row; 11 | } 12 | 13 | @Override 14 | public String getString(String name) { 15 | return row.get(name, String.class); 16 | } 17 | 18 | @Override 19 | public boolean getBoolean(String name) { 20 | Integer o = row.get(name, Integer.class); 21 | return o != null && o > 0; 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-participant/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api "io.eventuate.tram.core:eventuate-tram-reactive-commands:$eventuateTramVersion" 4 | 5 | api project(":eventuate-tram-sagas-participant") 6 | api project(":eventuate-tram-sagas-reactive-common") 7 | 8 | api "io.projectreactor:reactor-core:$reactorVersion" 9 | } 10 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-participant/src/main/java/io/eventuate/tram/sagas/reactive/participant/AbstractReactiveSagaCommandHandlersBuilder.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.participant; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | import io.eventuate.tram.commands.consumer.CommandMessage; 5 | import io.eventuate.tram.messaging.common.Message; 6 | import org.reactivestreams.Publisher; 7 | 8 | import java.util.function.Function; 9 | 10 | public interface AbstractReactiveSagaCommandHandlersBuilder { 11 | ReactiveSagaCommandHandlerBuilder onMessage(Class commandClass, 12 | Function, Publisher> handler); 13 | } 14 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-reactive-participant/src/main/java/io/eventuate/tram/sagas/reactive/participant/ReactiveSagaCommandDispatcherFactory.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.reactive.participant; 2 | 3 | import io.eventuate.tram.consumer.common.reactive.ReactiveMessageConsumer; 4 | import io.eventuate.tram.reactive.commands.consumer.ReactiveCommandHandlers; 5 | import io.eventuate.tram.reactive.commands.consumer.ReactiveCommandReplyProducer; 6 | import io.eventuate.tram.sagas.reactive.common.ReactiveSagaLockManager; 7 | 8 | public class ReactiveSagaCommandDispatcherFactory { 9 | 10 | private final ReactiveMessageConsumer messageConsumer; 11 | private final ReactiveSagaLockManager sagaLockManager; 12 | private final ReactiveCommandReplyProducer commandReplyProducer; 13 | 14 | public ReactiveSagaCommandDispatcherFactory(ReactiveMessageConsumer messageConsumer, ReactiveSagaLockManager sagaLockManager, ReactiveCommandReplyProducer commandReplyProducer) { 15 | this.messageConsumer = messageConsumer; 16 | this.sagaLockManager = sagaLockManager; 17 | this.commandReplyProducer = commandReplyProducer; 18 | } 19 | 20 | public ReactiveSagaCommandDispatcher make(String commandDispatcherId, ReactiveCommandHandlers target) { 21 | ReactiveSagaCommandDispatcher reactiveSagaCommandDispatcher = new ReactiveSagaCommandDispatcher(commandDispatcherId, target, messageConsumer, sagaLockManager, commandReplyProducer); 22 | reactiveSagaCommandDispatcher.initialize(); 23 | return reactiveSagaCommandDispatcher; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-common/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-common") 4 | 5 | api "io.eventuate.common:eventuate-common-spring-jdbc:$eventuateCommonVersion" 6 | api "io.eventuate.tram.core:eventuate-tram-spring-commands:$eventuateTramVersion" 7 | 8 | testImplementation project(":eventuate-tram-sagas-spring-in-memory") 9 | testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 10 | } 11 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-common/src/main/java/io/eventuate/tram/sagas/spring/common/EventuateTramSagaCommonConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.spring.common; 2 | 3 | import io.eventuate.common.jdbc.EventuateJdbcStatementExecutor; 4 | import io.eventuate.common.jdbc.EventuateSchema; 5 | import io.eventuate.common.spring.jdbc.EventuateCommonJdbcOperationsConfiguration; 6 | import io.eventuate.tram.sagas.common.SagaLockManager; 7 | import io.eventuate.tram.sagas.common.SagaLockManagerImpl; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Import; 11 | 12 | @Configuration 13 | @Import(EventuateCommonJdbcOperationsConfiguration.class) 14 | public class EventuateTramSagaCommonConfiguration { 15 | 16 | @Bean 17 | public SagaLockManager sagaLockManager(EventuateJdbcStatementExecutor eventuateJdbcStatementExecutor, 18 | EventuateSchema eventuateSchema) { 19 | return new SagaLockManagerImpl(eventuateJdbcStatementExecutor, eventuateSchema); 20 | } 21 | } -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-common/src/test/java/io/eventuate/tram/sagas/spring/common/SagaLockManagerIntegrationTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.spring.common; 2 | 3 | import io.eventuate.tram.sagas.spring.inmemory.TramSagaInMemoryConfiguration; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Import; 7 | 8 | @Configuration 9 | @EnableAutoConfiguration 10 | @Import({TramSagaInMemoryConfiguration.class, EventuateTramSagaCommonConfiguration.class}) 11 | public class SagaLockManagerIntegrationTestConfiguration { 12 | } 13 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-common/src/test/resources/application-mssql.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:sqlserver://${$DOCKER_HOST_IP:localhost}:1433;databaseName=eventuate 2 | spring.datasource.username=sa 3 | spring.datasource.password=Eventuate123! 4 | spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver 5 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-common/src/test/resources/application-postgres.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://${DOCKER_HOST_IP:localhost}/eventuate 2 | spring.datasource.username=eventuate 3 | spring.datasource.password=eventuate 4 | spring.datasource.driver-class-name=org.postgresql.Driver 5 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-common/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://${DOCKER_HOST_IP:localhost}/eventuate?useSSL=false 2 | spring.datasource.username=mysqluser 3 | spring.datasource.password=mysqlpw 4 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 5 | logging.level.io.eventuate=DEBUG -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-in-memory/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-common-in-memory") 4 | api "org.springframework.boot:spring-boot-starter-jdbc:$springBootVersion" 5 | 6 | implementation "io.eventuate.tram.core:eventuate-tram-spring-in-memory:$eventuateTramVersion" 7 | implementation "com.h2database:h2:1.3.166" 8 | 9 | } 10 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-in-memory/src/main/java/io/eventuate/tram/sagas/spring/inmemory/TramSagaInMemoryConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.spring.inmemory; 2 | 3 | import io.eventuate.common.inmemorydatabase.EventuateDatabaseScriptSupplier; 4 | import io.eventuate.tram.spring.inmemory.TramInMemoryConfiguration; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.Import; 8 | 9 | import java.util.Collections; 10 | 11 | @Configuration 12 | @Import({TramInMemoryConfiguration.class}) 13 | public class TramSagaInMemoryConfiguration { 14 | 15 | @Bean 16 | public EventuateDatabaseScriptSupplier eventuateCommonInMemoryScriptSupplierForEventuateTramSagas() { 17 | return () -> Collections.singletonList("eventuate-tram-sagas-embedded.sql"); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-orchestration-simple-dsl-starter/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | 4 | api "io.eventuate.tram.core:eventuate-tram-spring-commands-starter:$eventuateTramVersion" 5 | api "io.eventuate.tram.core:eventuate-tram-spring-events-starter:$eventuateTramVersion" 6 | 7 | api project(":eventuate-tram-sagas-spring-orchestration-simple-dsl") 8 | } 9 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-orchestration-simple-dsl-starter/src/main/java/io/eventuate/tram/sagas/spring/orchestration/autoconfigure/SpringOrchestratorSimpleDslAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.spring.orchestration.autoconfigure; 2 | 3 | import io.eventuate.tram.sagas.spring.orchestration.SagaOrchestratorConfiguration; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Import; 6 | 7 | @Configuration 8 | @Import(SagaOrchestratorConfiguration.class) 9 | public class SpringOrchestratorSimpleDslAutoConfiguration { 10 | } 11 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-orchestration-simple-dsl-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | io.eventuate.tram.sagas.spring.orchestration.autoconfigure.SpringOrchestratorSimpleDslAutoConfiguration -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-orchestration-simple-dsl-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | io.eventuate.tram.sagas.spring.orchestration.autoconfigure.SpringOrchestratorSimpleDslAutoConfiguration 2 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-orchestration-simple-dsl/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-orchestration-simple-dsl") 4 | api project(":eventuate-tram-sagas-spring-orchestration") 5 | } 6 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-orchestration/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-orchestration") 4 | api project(":eventuate-tram-sagas-spring-common") 5 | 6 | api "io.eventuate.tram.core:eventuate-tram-spring-events:$eventuateTramVersion" 7 | api "io.eventuate.common:eventuate-common-spring-jdbc:$eventuateCommonVersion" 8 | 9 | testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 10 | testImplementation "io.eventuate.common:eventuate-common-id:$eventuateCommonVersion" 11 | testImplementation project(":eventuate-tram-sagas-spring-in-memory") 12 | } 13 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-orchestration/src/test/resources/application-mssql.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:sqlserver://${$DOCKER_HOST_IP:localhost}:1433;databaseName=eventuate 2 | spring.datasource.username=sa 3 | spring.datasource.password=Eventuate123! 4 | spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver 5 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-orchestration/src/test/resources/application-postgres.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://${DOCKER_HOST_IP:localhost}/eventuate 2 | spring.datasource.username=eventuate 3 | spring.datasource.password=eventuate 4 | spring.datasource.driver-class-name=org.postgresql.Driver 5 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-orchestration/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.main.allow-bean-definition-overriding=true 2 | 3 | spring.jpa.generate-ddl=true 4 | logging.level.org.springframework.orm.jpa=INFO 5 | logging.level.org.hibernate.SQL=DEBUG 6 | logging.level.io.eventuate=DEBUG 7 | 8 | eventuatelocal.kafka.bootstrap.servers=${DOCKER_HOST_IP:localhost}:9092 9 | eventuatelocal.cdc.db.user.name=root 10 | eventuatelocal.cdc.db.password=rootpassword 11 | eventuatelocal.zookeeper.connection.string=${DOCKER_HOST_IP:localhost}:2181 12 | 13 | spring.datasource.url=jdbc:mysql://${DOCKER_HOST_IP:localhost}/eventuate 14 | spring.datasource.username=mysqluser 15 | spring.datasource.password=mysqlpw 16 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 17 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-participant-starter/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api "io.eventuate.tram.core:eventuate-tram-spring-messaging-starter:$eventuateTramVersion" 4 | api "io.eventuate.tram.core:eventuate-tram-spring-commands-starter:$eventuateTramVersion" 5 | api project(":eventuate-tram-sagas-spring-participant") 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-participant-starter/src/main/java/io/eventuate/tram/sagas/spring/participant/autoconfigure/SpringParticipantAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.spring.participant.autoconfigure; 2 | 3 | 4 | import io.eventuate.tram.sagas.spring.participant.SagaParticipantConfiguration; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Import; 7 | 8 | @Configuration 9 | @Import(SagaParticipantConfiguration.class) 10 | public class SpringParticipantAutoConfiguration { 11 | } 12 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-participant-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | io.eventuate.tram.sagas.spring.participant.autoconfigure.SpringParticipantAutoConfiguration -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-participant-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | io.eventuate.tram.sagas.spring.participant.autoconfigure.SpringParticipantAutoConfiguration 2 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-participant/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | 4 | api project(":eventuate-tram-sagas-spring-common") 5 | api project(":eventuate-tram-sagas-participant") 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-common/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-reactive-common") 4 | 5 | api "io.eventuate.common:eventuate-common-spring-reactive-jdbc:$eventuateCommonVersion" 6 | api "io.eventuate.tram.core:eventuate-tram-spring-reactive-commands:$eventuateTramVersion" 7 | 8 | testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 9 | } 10 | 11 | test { 12 | def profile = System.env['SPRING_PROFILES_ACTIVE'] 13 | if (profile != null && profile != "" && !profile.toLowerCase().contains("mysql")) { 14 | exclude '**/**' 15 | } 16 | } -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-common/src/main/java/io/eventuate/tram/sagas/spring/reactive/common/EventuateReactiveTramSagaCommonConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.spring.reactive.common; 2 | 3 | import io.eventuate.common.jdbc.EventuateSchema; 4 | import io.eventuate.common.reactive.jdbc.EventuateReactiveJdbcStatementExecutor; 5 | import io.eventuate.common.spring.jdbc.reactive.EventuateCommonReactiveDatabaseConfiguration; 6 | import io.eventuate.tram.sagas.reactive.common.ReactiveSagaLockManager; 7 | import io.eventuate.tram.sagas.reactive.common.ReactiveSagaLockManagerImpl; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Import; 11 | 12 | @Configuration 13 | @Import(EventuateCommonReactiveDatabaseConfiguration.class) 14 | public class EventuateReactiveTramSagaCommonConfiguration { 15 | 16 | @Bean 17 | public ReactiveSagaLockManager sagaLockManager(EventuateReactiveJdbcStatementExecutor eventuateJdbcStatementExecutor, 18 | EventuateSchema eventuateSchema) { 19 | return new ReactiveSagaLockManagerImpl(eventuateJdbcStatementExecutor, eventuateSchema); 20 | } 21 | } -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-common/src/test/java/io/eventuate/tram/sagas/spring/reactive/common/ReactiveSagaLockManagerIntegrationTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.spring.reactive.common; 2 | 3 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Import; 6 | 7 | @Configuration 8 | @EnableAutoConfiguration 9 | @Import(EventuateReactiveTramSagaCommonConfiguration.class) 10 | public class ReactiveSagaLockManagerIntegrationTestConfiguration { 11 | } 12 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-common/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | eventuate.reactive.db.driver=mysql 2 | eventuate.reactive.db.host=${DOCKER_HOST_IP:localhost} 3 | eventuate.reactive.db.port=3306 4 | eventuate.reactive.db.username=mysqluser 5 | eventuate.reactive.db.password=mysqlpw 6 | eventuate.reactive.db.database=eventuate -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-orchestration-simple-dsl-starter/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | 4 | api "io.eventuate.tram.core:eventuate-tram-spring-reactive-commands-starter:$eventuateTramVersion" 5 | api "io.eventuate.tram.core:eventuate-tram-spring-reactive-events-starter:$eventuateTramVersion" 6 | 7 | api project(":eventuate-tram-sagas-reactive-orchestration-simple-dsl") 8 | api project(":eventuate-tram-sagas-spring-reactive-orchestration") 9 | } 10 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-orchestration-simple-dsl-starter/src/main/java/io/eventuate/tram/sagas/spring/reactive/orchestration/autoconfigure/SpringReactiveOrchestratorSimpleDslAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.spring.reactive.orchestration.autoconfigure; 2 | 3 | import io.eventuate.tram.sagas.spring.reactive.orchestration.ReactiveSagaOrchestratorConfiguration; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Import; 6 | 7 | @Configuration 8 | @Import(ReactiveSagaOrchestratorConfiguration.class) 9 | public class SpringReactiveOrchestratorSimpleDslAutoConfiguration { 10 | } 11 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-orchestration-simple-dsl-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | io.eventuate.tram.sagas.spring.reactive.orchestration.autoconfigure.SpringReactiveOrchestratorSimpleDslAutoConfiguration -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-orchestration-simple-dsl-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | io.eventuate.tram.sagas.spring.reactive.orchestration.autoconfigure.SpringReactiveOrchestratorSimpleDslAutoConfiguration 2 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-orchestration/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-reactive-orchestration") 4 | api project(":eventuate-tram-sagas-spring-reactive-common") 5 | 6 | api "io.eventuate.tram.core:eventuate-tram-spring-reactive-events:$eventuateTramVersion" 7 | api "io.eventuate.common:eventuate-common-spring-reactive-jdbc:$eventuateCommonVersion" 8 | 9 | testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 10 | testImplementation "io.eventuate.common:eventuate-common-id:$eventuateCommonVersion" 11 | } 12 | 13 | test { 14 | def profile = System.env['SPRING_PROFILES_ACTIVE'] 15 | if (profile != null && profile != "" && !profile.toLowerCase().contains("mysql")) { 16 | exclude '**/**' 17 | } 18 | } -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-orchestration/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | eventuatelocal.kafka.bootstrap.servers=${DOCKER_HOST_IP:localhost}:9092 2 | eventuatelocal.zookeeper.connection.string=${DOCKER_HOST_IP:localhost}:2181 3 | 4 | eventuate.reactive.db.driver=mysql 5 | eventuate.reactive.db.host=${DOCKER_HOST_IP:localhost} 6 | eventuate.reactive.db.port=3306 7 | eventuate.reactive.db.username=mysqluser 8 | eventuate.reactive.db.password=mysqlpw 9 | eventuate.reactive.db.database=eventuate -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-participant-starter/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api "io.eventuate.tram.core:eventuate-tram-spring-reactive-messaging-starter:$eventuateTramVersion" 4 | 5 | api project(":eventuate-tram-sagas-spring-reactive-participant") 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-participant-starter/src/main/java/io/eventuate/tram/sagas/spring/reactive/participant/autoconfigure/ReactiveSpringParticipantAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.spring.reactive.participant.autoconfigure; 2 | 3 | import io.eventuate.tram.sagas.spring.reactive.participant.ReactiveSagaParticipantConfiguration; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Import; 6 | 7 | @Configuration 8 | @Import(ReactiveSagaParticipantConfiguration.class) 9 | public class ReactiveSpringParticipantAutoConfiguration { 10 | } 11 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-participant-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | io.eventuate.tram.sagas.spring.reactive.participant.autoconfigure.ReactiveSpringParticipantAutoConfiguration -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-participant-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | io.eventuate.tram.sagas.spring.reactive.participant.autoconfigure.ReactiveSpringParticipantAutoConfiguration 2 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-participant/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api project(":eventuate-tram-sagas-spring-reactive-common") 3 | api project(":eventuate-tram-sagas-reactive-participant") 4 | } 5 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-reactive-participant/src/main/java/io/eventuate/tram/sagas/spring/reactive/participant/ReactiveSagaParticipantConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.spring.reactive.participant; 2 | 3 | import io.eventuate.tram.consumer.common.reactive.ReactiveMessageConsumer; 4 | import io.eventuate.tram.reactive.commands.consumer.ReactiveCommandReplyProducer; 5 | import io.eventuate.tram.sagas.reactive.common.ReactiveSagaLockManager; 6 | import io.eventuate.tram.sagas.reactive.participant.ReactiveSagaCommandDispatcherFactory; 7 | import io.eventuate.tram.sagas.spring.reactive.common.EventuateReactiveTramSagaCommonConfiguration; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Import; 11 | 12 | @Configuration 13 | @Import(EventuateReactiveTramSagaCommonConfiguration.class) 14 | public class ReactiveSagaParticipantConfiguration { 15 | @Bean 16 | public ReactiveSagaCommandDispatcherFactory sagaCommandDispatcherFactory(ReactiveMessageConsumer messageConsumer, 17 | ReactiveSagaLockManager sagaLockManager, 18 | ReactiveCommandReplyProducer commandReplyProducer) { 19 | return new ReactiveSagaCommandDispatcherFactory(messageConsumer, sagaLockManager, commandReplyProducer); 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-testing-support-cloud-contract/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath "io.spring.gradle:dependency-management-plugin:$springDependencyManagementPluginVersion" 7 | } 8 | } 9 | 10 | apply plugin: "io.spring.dependency-management" 11 | 12 | dependencyManagement { 13 | imports { 14 | mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:$springCloudContractDependenciesVersion" 15 | } 16 | } 17 | 18 | 19 | dependencies { 20 | api "io.eventuate.common:eventuate-common-id:$eventuateCommonVersion" 21 | api "io.eventuate.tram.core:eventuate-tram-spring-testing-support-cloud-contract:$eventuateTramVersion" 22 | api project(":eventuate-tram-sagas-spring-orchestration-simple-dsl") 23 | api 'org.springframework.cloud:spring-cloud-starter-contract-verifier' 24 | } 25 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-testing-support-cloud-contract/src/main/java/io/eventuate/tram/sagas/spring/testing/contract/EventuateTramSagasSpringCloudContractSupportConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.spring.testing.contract; 2 | 3 | import io.eventuate.common.id.ApplicationIdGenerator; 4 | import io.eventuate.tram.sagas.orchestration.SagaCommandProducer; 5 | import io.eventuate.tram.sagas.spring.orchestration.SagaOrchestratorConfiguration; 6 | import io.eventuate.tram.spring.cloudcontractsupport.EventuateContractVerifierConfiguration; 7 | import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Import; 11 | 12 | @Configuration 13 | @Import({SagaOrchestratorConfiguration.class, EventuateContractVerifierConfiguration.class}) 14 | public class EventuateTramSagasSpringCloudContractSupportConfiguration { 15 | 16 | @Bean 17 | public SagaMessagingTestHelper sagaMessagingTestHelper(ContractVerifierMessaging contractVerifierMessaging, SagaCommandProducer sagaCommandProducer) { 18 | return new SagaMessagingTestHelper(contractVerifierMessaging, sagaCommandProducer, new ApplicationIdGenerator()); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-spring-testing-support/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-testing-support") 4 | 5 | api "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-testing-support/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-unit-testing-support") 4 | 5 | api "io.eventuate.tram.core:eventuate-tram-testing-support:$eventuateTramVersion" 6 | } 7 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-testing-support/src/main/java/io/eventuate/tram/sagas/testing/SagaParticipantChannels.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.testing; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | public class SagaParticipantChannels { 8 | 9 | private Set channels; 10 | 11 | public SagaParticipantChannels(String... channels) { 12 | this.channels = new HashSet<>(Arrays.asList(channels)); 13 | } 14 | 15 | public Set getChannels() { 16 | return channels; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /eventuate-tram-sagas-unit-testing-support/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api project(":eventuate-tram-sagas-orchestration") 3 | api "junit:junit:4.12" 4 | } -------------------------------------------------------------------------------- /eventuate-tram-sagas-unit-testing-support/src/main/java/io/eventuate/tram/sagas/testing/MessageWithDestination.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.tram.sagas.testing; 2 | 3 | import io.eventuate.tram.messaging.common.Message; 4 | import org.apache.commons.lang.builder.EqualsBuilder; 5 | import org.apache.commons.lang.builder.ToStringBuilder; 6 | 7 | public class MessageWithDestination { 8 | private final String destination; 9 | private final Message message; 10 | 11 | @Override 12 | public String toString() { 13 | return ToStringBuilder.reflectionToString(this); 14 | } 15 | 16 | public MessageWithDestination(String destination, Message message) { 17 | this.destination = destination; 18 | this.message = message; 19 | } 20 | 21 | public String getDestination() { 22 | return destination; 23 | } 24 | 25 | public Message getMessage() { 26 | return message; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | deployUrl=file:///Users/cer/.m2/testdeploy 2 | 3 | dockerImageTag=latest 4 | 5 | eventuateMavenRepoUrl=file:///Users/cer/.m2/testdeploy,https://snapshots.repositories.eventuate.io/repository 6 | 7 | springBootVersion=2.6.11 8 | 9 | springCloudContractDependenciesVersion=2.0.0.RELEASE 10 | springDependencyManagementPluginVersion=1.0.3.RELEASE 11 | dockerComposePluginVersion=0.17.7 12 | eventuateUtilVersion=0.19.0.BUILD-SNAPSHOT 13 | eventuateTramVersion=0.37.0.BUILD-SNAPSHOT 14 | eventuateLocalVersion=0.44.0.BUILD-SNAPSHOT 15 | eventuateCommonImageVersion=0.21.0.BUILD-SNAPSHOT 16 | eventuateCdcImageVersion=0.19.0.BUILD-SNAPSHOT 17 | eventuateCommonVersion=0.21.0.BUILD-SNAPSHOT 18 | eventuatePluginsGradleVersion=0.14.0.BUILD-SNAPSHOT 19 | eventuateMessagingKafkaImageVersion=0.21.0.BUILD-SNAPSHOT 20 | micronautVersion=2.4.1 21 | 22 | reactorVersion=3.4.5 23 | 24 | version=0.26.0-SNAPSHOT 25 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eventuate-tram/eventuate-tram-sagas/802db395dc8c0b1c3e130bd207094aa1f11bbd3e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Aug 17 21:50:31 PDT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip -------------------------------------------------------------------------------- /mssql/5.tram-saga-schema.sql: -------------------------------------------------------------------------------- 1 | USE eventuate; 2 | 3 | DROP Table IF Exists eventuate.saga_instance_participants; 4 | GO 5 | 6 | DROP Table IF Exists eventuate.saga_instance; 7 | GO 8 | 9 | DROP Table IF Exists eventuate.saga_lock_table; 10 | GO 11 | 12 | DROP Table IF Exists eventuate.saga_stash_table; 13 | GO 14 | 15 | CREATE TABLE eventuate.saga_instance_participants ( 16 | saga_type VARCHAR(255) NOT NULL, 17 | saga_id VARCHAR(100) NOT NULL, 18 | destination VARCHAR(100) NOT NULL, 19 | resource VARCHAR(100) NOT NULL, 20 | PRIMARY KEY(saga_type, saga_id, destination, resource) 21 | ); 22 | 23 | CREATE TABLE eventuate.saga_instance( 24 | saga_type VARCHAR(255) NOT NULL, 25 | saga_id VARCHAR(100) NOT NULL, 26 | state_name VARCHAR(100) NOT NULL, 27 | last_request_id VARCHAR(100), 28 | end_state TINYINT, 29 | compensating TINYINT, 30 | failed TINYINT, 31 | saga_data_type VARCHAR(1000) NOT NULL, 32 | saga_data_json VARCHAR(1000) NOT NULL, 33 | PRIMARY KEY(saga_type, saga_id) 34 | ); 35 | 36 | create table eventuate.saga_lock_table( 37 | target VARCHAR(100) PRIMARY KEY, 38 | saga_type VARCHAR(255) NOT NULL, 39 | saga_Id VARCHAR(100) NOT NULL 40 | ); 41 | 42 | create table eventuate.saga_stash_table( 43 | message_id VARCHAR(100) PRIMARY KEY, 44 | target VARCHAR(100) NOT NULL, 45 | saga_type VARCHAR(255) NOT NULL, 46 | saga_id VARCHAR(100) NOT NULL, 47 | message_headers VARCHAR(1000) NOT NULL, 48 | message_payload VARCHAR(1000) NOT NULL 49 | ); 50 | -------------------------------------------------------------------------------- /mssql/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG EVENTUATE_COMMON_VERSION 2 | FROM eventuateio/eventuate-mssql:$EVENTUATE_COMMON_VERSION 3 | COPY 5.tram-saga-schema.sql /usr/src/app 4 | -------------------------------------------------------------------------------- /mssql/build-docker.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | docker build -t test-eventuate-tram-sagas-mssql . -------------------------------------------------------------------------------- /mysql-cli.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | docker run ${1:--it} \ 4 | --name mysqlterm --network=${PWD##*/}_default --rm \ 5 | -e MYSQL_HOST=mysql \ 6 | mysql/mysql-server:8.0.27-1.2.6-server \ 7 | sh -c 'exec mysql -h"$MYSQL_HOST" -uroot -prootpassword -o eventuate' 8 | -------------------------------------------------------------------------------- /mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG EVENTUATE_COMMON_VERSION 2 | FROM eventuateio/eventuate-mysql8:$EVENTUATE_COMMON_VERSION 3 | COPY tram-saga-schema.sql /docker-entrypoint-initdb.d 4 | -------------------------------------------------------------------------------- /mysql/build-docker-multi-arch.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | SCRIPT_DIR=$(cd $( dirname "${BASH_SOURCE[0]}" ) ; pwd) 4 | 5 | docker-compose -f $SCRIPT_DIR/../docker-compose-registry.yml --project-name eventuate-common-registry up -d registry 6 | 7 | IMAGE="${MYSQL_MULTI_ARCH_IMAGE:-${DOCKER_HOST_NAME:-host.docker.internal}:5002/eventuate-tram-sagas-mysql:multi-arch-local-build}" 8 | OPTS="${BUILDX_PUSH_OPTIONS:---output=type=image,push=true,registry.insecure=true}" 9 | 10 | echo IMAGE=$IMAGE 11 | echo OPTS=$OPTS 12 | 13 | docker buildx build --platform linux/amd64,linux/arm64 \ 14 | -t $IMAGE \ 15 | -f $SCRIPT_DIR/Dockerfile \ 16 | --build-arg=EVENTUATE_COMMON_VERSION=${EVENTUATE_COMMON_VERSION?} \ 17 | $OPTS \ 18 | $SCRIPT_DIR 19 | -------------------------------------------------------------------------------- /mysql/build-docker.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | docker build -t test-eventuate-tram-sagas-mysql . -------------------------------------------------------------------------------- /mysql/tram-saga-schema.sql: -------------------------------------------------------------------------------- 1 | USE eventuate; 2 | 3 | DROP Table IF Exists saga_instance_participants; 4 | DROP Table IF Exists saga_instance; 5 | DROP Table IF Exists saga_lock_table; 6 | DROP Table IF Exists saga_stash_table; 7 | 8 | CREATE TABLE saga_instance_participants ( 9 | saga_type VARCHAR(255) NOT NULL, 10 | saga_id VARCHAR(100) NOT NULL, 11 | destination VARCHAR(100) NOT NULL, 12 | resource VARCHAR(100) NOT NULL, 13 | PRIMARY KEY(saga_type, saga_id, destination, resource) 14 | ); 15 | 16 | 17 | CREATE TABLE saga_instance( 18 | saga_type VARCHAR(255) NOT NULL, 19 | saga_id VARCHAR(100) NOT NULL, 20 | state_name VARCHAR(100) NOT NULL, 21 | last_request_id VARCHAR(100), 22 | end_state INT(1), 23 | compensating INT(1), 24 | failed INT(1), 25 | saga_data_type VARCHAR(1000) NOT NULL, 26 | saga_data_json VARCHAR(1000) NOT NULL, 27 | PRIMARY KEY(saga_type, saga_id) 28 | ); 29 | 30 | create table saga_lock_table( 31 | target VARCHAR(100) PRIMARY KEY, 32 | saga_type VARCHAR(255) NOT NULL, 33 | saga_Id VARCHAR(100) NOT NULL 34 | ); 35 | 36 | create table saga_stash_table( 37 | message_id VARCHAR(100) PRIMARY KEY, 38 | target VARCHAR(100) NOT NULL, 39 | saga_type VARCHAR(255) NOT NULL, 40 | saga_id VARCHAR(100) NOT NULL, 41 | message_headers VARCHAR(1000) NOT NULL, 42 | message_payload VARCHAR(1000) NOT NULL 43 | ); 44 | -------------------------------------------------------------------------------- /orders-and-customers-micronaut-in-memory-integration-tests/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "io.spring.dependency-management" version "1.0.6.RELEASE" 3 | } 4 | 5 | dependencyManagement { 6 | imports { 7 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion" 8 | } 9 | } 10 | 11 | 12 | dependencies { 13 | testImplementation project(":orders-and-customers-micronaut") 14 | testImplementation project(":eventuate-tram-sagas-micronaut-in-memory") 15 | testImplementation "io.eventuate.tram.core:eventuate-tram-micronaut-in-memory:$eventuateTramVersion" 16 | 17 | annotationProcessor "io.micronaut:micronaut-inject-java" 18 | annotationProcessor "io.micronaut:micronaut-validation" 19 | annotationProcessor "io.micronaut.configuration:micronaut-openapi" 20 | annotationProcessor "javax.persistence:javax.persistence-api:2.2" 21 | api "io.micronaut:micronaut-inject" 22 | api "io.micronaut:micronaut-validation" 23 | api "io.micronaut:micronaut-runtime" 24 | 25 | testRuntimeOnly 'io.micronaut.sql:micronaut-jdbc-hikari' 26 | testAnnotationProcessor "io.micronaut:micronaut-inject-java" 27 | testImplementation "org.junit.jupiter:junit-jupiter-api" 28 | testImplementation "io.micronaut.test:micronaut-test-junit5" 29 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine" 30 | } 31 | 32 | // use JUnit 5 platform 33 | test { 34 | useJUnitPlatform() 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /orders-and-customers-micronaut-in-memory-integration-tests/src/test/java/io/eventuate/examples/tram/sagas/ordersandcustomers/integrationtests/micronaut/OrdersAndCustomersInMemoryIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.integrationtests.micronaut; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.micronaut.tests.AbstractOrdersAndCustomersIntegrationTest; 4 | import io.micronaut.test.annotation.MicronautTest; 5 | 6 | @MicronautTest(transactional = false) 7 | public class OrdersAndCustomersInMemoryIntegrationTest extends AbstractOrdersAndCustomersIntegrationTest { 8 | } 9 | -------------------------------------------------------------------------------- /orders-and-customers-micronaut-in-memory-integration-tests/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | default: 3 | url: jdbc:h2:mem:testdb 4 | username: sa 5 | password: test 6 | driverClassName: org.h2.Driver 7 | jpa: 8 | default: 9 | packages-to-scan: 10 | - 'io.eventuate.examples.tram.sagas.ordersandcustomers' 11 | properties: 12 | hibernate: 13 | hbm2ddl: 14 | auto: update 15 | show_sql: true 16 | transactional: 17 | noop: 18 | duplicate: 19 | message: 20 | detector: 21 | factory: 22 | enabled: true -------------------------------------------------------------------------------- /orders-and-customers-micronaut-integration-tests/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "io.spring.dependency-management" version "1.0.6.RELEASE" 3 | } 4 | 5 | 6 | 7 | dependencyManagement { 8 | imports { 9 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion" 10 | } 11 | } 12 | 13 | 14 | dependencies { 15 | testImplementation project(":orders-and-customers-micronaut") 16 | 17 | annotationProcessor "io.micronaut:micronaut-inject-java" 18 | annotationProcessor "io.micronaut:micronaut-validation" 19 | annotationProcessor "io.micronaut.configuration:micronaut-openapi" 20 | api "io.micronaut:micronaut-inject" 21 | api "io.micronaut:micronaut-validation" 22 | api "io.micronaut:micronaut-runtime" 23 | annotationProcessor "javax.persistence:javax.persistence-api:2.2" 24 | testRuntimeOnly 'io.micronaut.sql:micronaut-jdbc-hikari' 25 | testAnnotationProcessor "io.micronaut:micronaut-inject-java" 26 | testImplementation "org.junit.jupiter:junit-jupiter-api" 27 | testImplementation "io.micronaut.test:micronaut-test-junit5" 28 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine" 29 | } 30 | 31 | // use JUnit 5 platform 32 | test { 33 | useJUnitPlatform() 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /orders-and-customers-micronaut-integration-tests/src/test/java/io/eventuate/examples/tram/sagas/ordersandcustomers/integrationtests/micronaut/OrdersAndCustomersIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.integrationtests.micronaut; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.micronaut.tests.AbstractOrdersAndCustomersIntegrationTest; 4 | import io.micronaut.test.annotation.MicronautTest; 5 | 6 | @MicronautTest(transactional = false) 7 | public class OrdersAndCustomersIntegrationTest extends AbstractOrdersAndCustomersIntegrationTest { 8 | } 9 | -------------------------------------------------------------------------------- /orders-and-customers-micronaut-integration-tests/src/test/resources/application-mssql.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | default: 3 | url: jdbc:sqlserver://${$DOCKER_HOST_IP:localhost}:1433;databaseName=eventuate 4 | username: sa 5 | password: Eventuate123! 6 | driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver -------------------------------------------------------------------------------- /orders-and-customers-micronaut-integration-tests/src/test/resources/application-postgres.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | default: 3 | url: jdbc:postgresql://${$DOCKER_HOST_IP:localhost}/eventuate 4 | username: eventuate 5 | password: eventuate 6 | driver-class-name: org.postgresql.Driver -------------------------------------------------------------------------------- /orders-and-customers-micronaut-integration-tests/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | default: 3 | url: jdbc:mysql://${$DOCKER_HOST_IP:localhost}/eventuate 4 | username: mysqluser 5 | password: mysqlpw 6 | driverClassName: com.mysql.cj.jdbc.Driver 7 | driver-class-name: com.mysql.cj.jdbc.Driver 8 | 9 | 10 | 11 | eventuatelocal: 12 | kafka: 13 | bootstrap: 14 | servers: ${$DOCKER_HOST_IP:localhost}:9092 15 | 16 | jpa: 17 | default: 18 | packages-to-scan: 19 | - 'io.eventuate.examples.tram.sagas.ordersandcustomers' 20 | properties: 21 | hibernate: 22 | hbm2ddl: 23 | auto: update 24 | show_sql: true 25 | 26 | -------------------------------------------------------------------------------- /orders-and-customers-micronaut-local-saga-in-memory-integration-tests/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "io.spring.dependency-management" version "1.0.6.RELEASE" 3 | } 4 | 5 | 6 | 7 | dependencyManagement { 8 | imports { 9 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion" 10 | } 11 | } 12 | 13 | 14 | dependencies { 15 | testImplementation project(":orders-and-customers-micronaut") 16 | testImplementation project(":eventuate-tram-sagas-micronaut-in-memory") 17 | testImplementation "io.eventuate.tram.core:eventuate-tram-micronaut-in-memory:$eventuateTramVersion" 18 | 19 | annotationProcessor "io.micronaut:micronaut-inject-java" 20 | annotationProcessor "io.micronaut:micronaut-validation" 21 | annotationProcessor "io.micronaut.configuration:micronaut-openapi" 22 | annotationProcessor "javax.persistence:javax.persistence-api:2.2" 23 | api "io.micronaut:micronaut-inject" 24 | api "io.micronaut:micronaut-validation" 25 | api "io.micronaut:micronaut-runtime" 26 | 27 | testRuntimeOnly 'io.micronaut.sql:micronaut-jdbc-hikari' 28 | testAnnotationProcessor "io.micronaut:micronaut-inject-java" 29 | testImplementation "org.junit.jupiter:junit-jupiter-api" 30 | testImplementation "io.micronaut.test:micronaut-test-junit5" 31 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine" 32 | } 33 | 34 | // use JUnit 5 platform 35 | test { 36 | useJUnitPlatform() 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /orders-and-customers-micronaut-local-saga-in-memory-integration-tests/src/test/java/io/eventuate/examples/tram/sagas/ordersandcustomers/integrationtests/micronaut/OrdersAndCustomersLocalSagaInMemoryIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.integrationtests.micronaut; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.commondomain.Money; 4 | import io.eventuate.examples.tram.sagas.ordersandcustomers.customers.domain.Customer; 5 | import io.eventuate.examples.tram.sagas.ordersandcustomers.micronaut.tests.AbstractOrdersAndCustomersIntegrationTest; 6 | import io.eventuate.examples.tram.sagas.ordersandcustomers.orders.domain.Order; 7 | import io.eventuate.examples.tram.sagas.ordersandcustomers.orders.service.OrderDetails; 8 | import io.micronaut.test.annotation.MicronautTest; 9 | 10 | @MicronautTest(transactional = false) 11 | public class OrdersAndCustomersLocalSagaInMemoryIntegrationTest extends AbstractOrdersAndCustomersIntegrationTest { 12 | 13 | protected Order createOrder(Customer customer) { 14 | return orderService.localCreateOrder(new OrderDetails(customer.getId(), new Money("123.40"))); 15 | } 16 | 17 | 18 | @Override 19 | protected void assertCreateOrderSagaCompletedSuccesfully(Order order) { 20 | // do nothing 21 | } 22 | 23 | @Override 24 | protected void assertCreateOrderSagaRolledBack(Order order) { 25 | // do nothing 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /orders-and-customers-micronaut-local-saga-in-memory-integration-tests/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | default: 3 | url: jdbc:h2:mem:testdb 4 | username: sa 5 | password: test 6 | driverClassName: org.h2.Driver 7 | jpa: 8 | default: 9 | packages-to-scan: 10 | - 'io.eventuate.examples.tram.sagas.ordersandcustomers' 11 | properties: 12 | hibernate: 13 | hbm2ddl: 14 | auto: update 15 | show_sql: true 16 | transactional: 17 | noop: 18 | duplicate: 19 | message: 20 | detector: 21 | factory: 22 | enabled: true 23 | -------------------------------------------------------------------------------- /orders-and-customers-micronaut/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/micronaut/customers/domain/CustomerDaoImpl.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.micronaut.customers.domain; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.customers.domain.Customer; 4 | import io.eventuate.examples.tram.sagas.ordersandcustomers.customers.domain.CustomerDao; 5 | import io.micronaut.transaction.annotation.TransactionalAdvice; 6 | 7 | import javax.inject.Singleton; 8 | import javax.persistence.EntityManager; 9 | import javax.persistence.PersistenceContext; 10 | 11 | @Singleton 12 | public class CustomerDaoImpl implements CustomerDao { 13 | 14 | @PersistenceContext 15 | private EntityManager entityManager; 16 | 17 | @Override 18 | public Customer findById(long id) { 19 | return entityManager.find(Customer.class, id); 20 | } 21 | 22 | @Override 23 | @TransactionalAdvice 24 | public Customer save(Customer customer) { 25 | entityManager.persist(customer); 26 | return customer; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /orders-and-customers-micronaut/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/micronaut/orders/domain/OrderDaoImpl.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.micronaut.orders.domain; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.orders.domain.Order; 4 | import io.eventuate.examples.tram.sagas.ordersandcustomers.orders.domain.OrderDao; 5 | 6 | import javax.inject.Singleton; 7 | import javax.persistence.EntityManager; 8 | import javax.persistence.PersistenceContext; 9 | 10 | @Singleton 11 | public class OrderDaoImpl implements OrderDao { 12 | 13 | @PersistenceContext 14 | private EntityManager entityManager; 15 | 16 | @Override 17 | public Order findById(long id) { 18 | return entityManager.find(Order.class, id); 19 | } 20 | 21 | @Override 22 | public Order save(Order order) { 23 | entityManager.persist(order); 24 | return order; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /orders-and-customers-micronaut/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/micronaut/tests/TramCommandsAndEventsIntegrationData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.micronaut.tests; 2 | 3 | public class TramCommandsAndEventsIntegrationData { 4 | 5 | private long now = System.currentTimeMillis(); 6 | private String commandDispatcherId = "command-dispatcher-" + now; 7 | private String commandChannel = "command-channel-" + now; 8 | private String aggregateDestination = "aggregate-destination-" + now; 9 | private String eventDispatcherId = "event-dispatcher-" + now; 10 | private String sagaEventsChannel = "sagaEventsChannel-" + now; 11 | 12 | public String getAggregateDestination() { 13 | return aggregateDestination; 14 | } 15 | 16 | 17 | public String getCommandDispatcherId() { 18 | return commandDispatcherId; 19 | } 20 | 21 | public String getCommandChannel() { 22 | return commandChannel; 23 | } 24 | 25 | public String getEventDispatcherId() { 26 | return eventDispatcherId; 27 | } 28 | 29 | public String getSagaEventsChannel() { 30 | return sagaEventsChannel; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api "io.eventuate.tram.core:eventuate-tram-spring-reactive-jdbc-kafka:${eventuateTramVersion}" 4 | api "io.eventuate.tram.core:eventuate-tram-spring-reactive-commands:${eventuateTramVersion}" 5 | api project(":eventuate-tram-sagas-spring-reactive-participant-starter") 6 | api project(":eventuate-tram-sagas-spring-reactive-orchestration-simple-dsl-starter") 7 | api "org.springframework.boot:spring-boot-starter:${springBootVersion}" 8 | 9 | testImplementation "io.eventuate.util:eventuate-util-test:$eventuateUtilVersion" 10 | testImplementation "org.springframework.boot:spring-boot-starter-test:${springBootVersion}" 11 | } 12 | 13 | test { 14 | def profile = System.env['SPRING_PROFILES_ACTIVE'] 15 | if (profile != null && profile != "" && (!profile.toLowerCase().contains("mysql") || profile.toLowerCase().contains("activemq"))) { 16 | exclude '**/**' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/customers/commands/ReleaseCreditCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.customers.commands; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class ReleaseCreditCommand implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/customers/commands/ReserveCreditCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.customers.commands; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.common.Money; 4 | import io.eventuate.tram.commands.common.Command; 5 | 6 | public class ReserveCreditCommand implements Command { 7 | private Long orderId; 8 | private Money orderTotal; 9 | private long customerId; 10 | 11 | public ReserveCreditCommand() { 12 | } 13 | 14 | public ReserveCreditCommand(Long customerId, Long orderId, Money orderTotal) { 15 | this.customerId = customerId; 16 | this.orderId = orderId; 17 | this.orderTotal = orderTotal; 18 | } 19 | 20 | public Money getOrderTotal() { 21 | return orderTotal; 22 | } 23 | 24 | public void setOrderTotal(Money orderTotal) { 25 | this.orderTotal = orderTotal; 26 | } 27 | 28 | public Long getOrderId() { 29 | 30 | return orderId; 31 | } 32 | 33 | public void setOrderId(Long orderId) { 34 | 35 | this.orderId = orderId; 36 | } 37 | 38 | public long getCustomerId() { 39 | return customerId; 40 | } 41 | 42 | public void setCustomerId(long customerId) { 43 | this.customerId = customerId; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/customers/domain/CreditReservation.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.customers.domain; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.relational.core.mapping.Table; 5 | 6 | import java.math.BigDecimal; 7 | 8 | @Table("credit_reservation") 9 | public class CreditReservation { 10 | 11 | @Id 12 | private Long id; 13 | 14 | private Long customerId; 15 | 16 | private long orderId; 17 | 18 | private BigDecimal reservation; 19 | 20 | public CreditReservation() { 21 | } 22 | 23 | public CreditReservation(Long customerId, long orderId, BigDecimal reservation) { 24 | this.customerId = customerId; 25 | this.orderId = orderId; 26 | this.reservation = reservation; 27 | } 28 | 29 | public BigDecimal getReservation() { 30 | return reservation; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/customers/domain/CreditReservationRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.customers.domain; 2 | 3 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 4 | import reactor.core.publisher.Flux; 5 | import reactor.core.publisher.Mono; 6 | 7 | public interface CreditReservationRepository extends ReactiveCrudRepository { 8 | Flux findAllByCustomerId(Long customerId); 9 | Mono deleteByOrderId(String orderId); 10 | } 11 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/customers/domain/Customer.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.customers.domain; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.annotation.Version; 5 | import org.springframework.data.relational.core.mapping.Table; 6 | 7 | import java.math.BigDecimal; 8 | 9 | @Table("customer") 10 | public class Customer { 11 | 12 | @Id 13 | private Long id; 14 | private String name; 15 | 16 | private BigDecimal creditLimit; 17 | 18 | private Long creationTime; 19 | 20 | @Version 21 | private Long version; 22 | 23 | public Customer() { 24 | } 25 | 26 | public Customer(String name, BigDecimal creditLimit) { 27 | this.name = name; 28 | this.creditLimit = creditLimit; 29 | this.creationTime = System.currentTimeMillis(); 30 | } 31 | 32 | public Long getId() { 33 | return id; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | public BigDecimal getCreditLimit() { 41 | return creditLimit; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/customers/domain/CustomerCreditLimitExceededException.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.customers.domain; 2 | 3 | public class CustomerCreditLimitExceededException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/customers/domain/CustomerNotFoundException.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.customers.domain; 2 | 3 | public class CustomerNotFoundException extends RuntimeException { 4 | public CustomerNotFoundException(long customerId) { 5 | super("Customer not found: " + customerId); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/customers/domain/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.customers.domain; 2 | 3 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 4 | 5 | public interface CustomerRepository extends ReactiveCrudRepository { 6 | } 7 | 8 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/customers/replies/CustomerCreditLimitExceeded.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.customers.replies; 2 | 3 | public class CustomerCreditLimitExceeded implements ReserveCreditResult { 4 | } 5 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/customers/replies/CustomerCreditReserved.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.customers.replies; 2 | 3 | public class CustomerCreditReserved implements ReserveCreditResult { 4 | } 5 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/customers/replies/CustomerNotFound.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.customers.replies; 2 | 3 | public class CustomerNotFound implements ReserveCreditResult { 4 | } 5 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/customers/replies/ReserveCreditResult.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.customers.replies; 2 | 3 | public interface ReserveCreditResult { 4 | } 5 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/orders/commandsandreplies/CancelOrderCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.orders.commandsandreplies; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class CancelOrderCommand implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/orders/common/OrderDetails.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.orders.common; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.common.Money; 4 | 5 | public class OrderDetails { 6 | 7 | private Long customerId; 8 | private Money orderTotal; 9 | 10 | public OrderDetails() { 11 | } 12 | 13 | public OrderDetails(Long customerId, Money orderTotal) { 14 | this.customerId = customerId; 15 | this.orderTotal = orderTotal; 16 | } 17 | 18 | public Long getCustomerId() { 19 | return customerId; 20 | } 21 | 22 | public Money getOrderTotal() { 23 | return orderTotal; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/orders/common/OrderState.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.orders.common; 2 | 3 | public enum OrderState { PENDING, APPROVED, REJECTED } 4 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/orders/common/RejectionReason.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.orders.common; 2 | 3 | public enum RejectionReason { INSUFFICIENT_CREDIT, UNKNOWN_CUSTOMER} 4 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/orders/createorder/CreateOrderSagaData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.orders.createorder; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.orders.common.OrderDetails; 4 | import io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.orders.common.RejectionReason; 5 | 6 | public class CreateOrderSagaData { 7 | 8 | private OrderDetails orderDetails; 9 | private Long orderId; 10 | private RejectionReason rejectionReason; 11 | 12 | public CreateOrderSagaData(OrderDetails orderDetails) { 13 | this.orderDetails = orderDetails; 14 | } 15 | 16 | public CreateOrderSagaData() { 17 | } 18 | 19 | public Long getOrderId() { 20 | return orderId; 21 | } 22 | 23 | public OrderDetails getOrderDetails() { 24 | return orderDetails; 25 | } 26 | 27 | public void setOrderId(Long orderId) { 28 | this.orderId = orderId; 29 | } 30 | 31 | public void setRejectionReason(RejectionReason rejectionReason) { 32 | this.rejectionReason = rejectionReason; 33 | } 34 | 35 | public RejectionReason getRejectionReason() { 36 | return rejectionReason; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/orders/domain/OrderIsTooBigException.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.orders.domain; 2 | 3 | public class OrderIsTooBigException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/reactive/orders/domain/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.reactive.orders.domain; 2 | 3 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 4 | import reactor.core.publisher.Flux; 5 | 6 | public interface OrderRepository extends ReactiveCrudRepository { 7 | Flux findAllByCustomerId(Long customerId); 8 | } -------------------------------------------------------------------------------- /orders-and-customers-spring-reactive/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | eventuatelocal.kafka.bootstrap.servers=${DOCKER_HOST_IP:localhost}:9092 2 | eventuatelocal.zookeeper.connection.string=${DOCKER_HOST_IP:localhost}:2181 3 | 4 | eventuate.reactive.db.driver=mysql 5 | eventuate.reactive.db.host=${DOCKER_HOST_IP:localhost} 6 | eventuate.reactive.db.port=3306 7 | eventuate.reactive.db.username=mysqluser 8 | eventuate.reactive.db.password=mysqlpw 9 | eventuate.reactive.db.database=eventuate 10 | 11 | spring.r2dbc.url=r2dbc:mysql://${DOCKER_HOST_IP:localhost}:3306/eventuate 12 | spring.r2dbc.username=mysqluser 13 | spring.r2dbc.password=mysqluser 14 | spring.r2dbc.initialization-mode=always 15 | -------------------------------------------------------------------------------- /orders-and-customers-spring/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-spring-orchestration-simple-dsl-starter") 4 | api project(":eventuate-tram-sagas-spring-participant-starter") 5 | api project(":orders-and-customers") 6 | 7 | api "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" 8 | 9 | api "io.eventuate.tram.core:eventuate-tram-spring-optimistic-locking:$eventuateTramVersion" 10 | testImplementation "io.eventuate.tram.core:eventuate-tram-spring-jdbc-kafka:$eventuateTramVersion" 11 | testImplementation "io.eventuate.tram.core:eventuate-tram-jdbc-activemq:$eventuateTramVersion" 12 | testImplementation "io.eventuate.tram.core:eventuate-tram-spring-in-memory:$eventuateTramVersion" 13 | testImplementation "org.springframework.boot:spring-boot-starter-test:${springBootVersion}" 14 | testImplementation "io.eventuate.util:eventuate-util-test:$eventuateUtilVersion" 15 | 16 | testImplementation project(":eventuate-tram-sagas-testing-support") 17 | testImplementation project(":eventuate-tram-sagas-spring-in-memory") 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/customers/domain/CustomerDaoImpl.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.customers.domain; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.customers.domain.Customer; 4 | import io.eventuate.examples.tram.sagas.ordersandcustomers.customers.domain.CustomerDao; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class CustomerDaoImpl implements CustomerDao { 10 | @Autowired 11 | private CustomerRepository customerRepository; 12 | 13 | @Override 14 | public Customer findById(long id) { 15 | return customerRepository 16 | .findById(id) 17 | .orElseThrow(() -> 18 | new IllegalArgumentException(String.format("Customer with id=%s is not found", id))); 19 | } 20 | 21 | @Override 22 | public Customer save(Customer customer) { 23 | return customerRepository.save(customer); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/customers/domain/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.customers.domain; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.customers.domain.Customer; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface CustomerRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/orders/SagaFailedEvent.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.orders; 2 | 3 | public class SagaFailedEvent extends SagaLifecycleEvent { 4 | 5 | public SagaFailedEvent(Object source, String sagaId) { 6 | super(source, sagaId); 7 | } 8 | 9 | 10 | } 11 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/orders/SagaLifecycleEvent.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.orders; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | public class SagaLifecycleEvent extends ApplicationEvent { 6 | protected String sagaId; 7 | 8 | public SagaLifecycleEvent(Object source, String sagaId) { 9 | super(source); 10 | this.sagaId = sagaId; 11 | } 12 | 13 | public String getSagaId() { 14 | return sagaId; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/orders/SagaStartedEvent.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.orders; 2 | 3 | public class SagaStartedEvent extends SagaLifecycleEvent { 4 | 5 | public SagaStartedEvent(Object source, String sagaId) { 6 | super(source, sagaId); 7 | } 8 | 9 | 10 | } 11 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/orders/domain/OrderDaoImpl.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.orders.domain; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.orders.domain.Order; 4 | import io.eventuate.examples.tram.sagas.ordersandcustomers.orders.domain.OrderDao; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class OrderDaoImpl implements OrderDao { 10 | 11 | @Autowired 12 | private OrderRepository orderRepository; 13 | 14 | @Override 15 | public Order findById(long id) { 16 | return orderRepository 17 | .findById(id) 18 | .orElseThrow(() -> 19 | new IllegalArgumentException(String.format("Order with id=%s is not found", id))); 20 | } 21 | 22 | @Override 23 | public Order save(Order order) { 24 | return orderRepository.save(order); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/orders/domain/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.orders.domain; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.orders.domain.Order; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface OrderRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/test/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/integrationtests/ActiveMQConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.integrationtests; 2 | 3 | import io.eventuate.tram.jdbcactivemq.TramJdbcActiveMQConfiguration; 4 | import org.springframework.context.annotation.Import; 5 | import org.springframework.context.annotation.Profile; 6 | 7 | @Import(TramJdbcActiveMQConfiguration.class) 8 | @Profile("ActiveMQ") 9 | public class ActiveMQConfiguration { 10 | } 11 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/test/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/integrationtests/KafkaConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.integrationtests; 2 | 3 | import io.eventuate.tram.spring.jdbckafka.TramJdbcKafkaConfiguration; 4 | import org.springframework.context.annotation.Import; 5 | import org.springframework.context.annotation.Profile; 6 | 7 | @Import(TramJdbcKafkaConfiguration.class) 8 | @Profile("!ActiveMQ") 9 | public class KafkaConfiguration { 10 | } 11 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/test/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/integrationtests/OrdersAndCustomersInMemoryIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.integrationtests; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | import org.springframework.test.context.junit4.SpringRunner; 6 | 7 | @RunWith(SpringRunner.class) 8 | @SpringBootTest(classes = OrdersAndCustomersInMemoryIntegrationTestConfiguration.class) 9 | public class OrdersAndCustomersInMemoryIntegrationTest extends AbstractOrdersAndCustomersIntegrationTest { 10 | } 11 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/test/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/integrationtests/OrdersAndCustomersInMemoryIntegrationTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.integrationtests; 2 | 3 | import io.eventuate.common.spring.jdbc.EventuateCommonJdbcOperationsConfiguration; 4 | import io.eventuate.tram.spring.inmemory.TramInMemoryConfiguration; 5 | import io.eventuate.tram.sagas.spring.inmemory.TramSagaInMemoryConfiguration; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.Import; 8 | 9 | @Configuration 10 | @Import({OrdersAndCustomersIntegrationCommonIntegrationTestConfiguration.class, 11 | TramInMemoryConfiguration.class, 12 | TramSagaInMemoryConfiguration.class, 13 | EventuateCommonJdbcOperationsConfiguration.class}) 14 | public class OrdersAndCustomersInMemoryIntegrationTestConfiguration { 15 | } 16 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/test/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/integrationtests/OrdersAndCustomersIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.integrationtests; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.context.annotation.ComponentScan; 7 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | 10 | @RunWith(SpringRunner.class) 11 | @SpringBootTest(classes = OrdersAndCustomersIntegrationTestConfiguration.class) 12 | public class OrdersAndCustomersIntegrationTest extends AbstractOrdersAndCustomersIntegrationTest { 13 | } 14 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/test/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/integrationtests/OrdersAndCustomersIntegrationTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.integrationtests; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.context.annotation.Import; 5 | 6 | @Configuration 7 | @Import({OrdersAndCustomersIntegrationCommonIntegrationTestConfiguration.class, KafkaConfiguration.class, ActiveMQConfiguration.class}) 8 | public class OrdersAndCustomersIntegrationTestConfiguration { 9 | } 10 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/test/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/integrationtests/SagaLifecycleEventListener.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.integrationtests; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.spring.orders.SagaLifecycleEvent; 4 | import io.eventuate.examples.tram.sagas.ordersandcustomers.spring.orders.SagaStartedEvent; 5 | import org.springframework.context.event.EventListener; 6 | 7 | import java.util.concurrent.LinkedBlockingDeque; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class SagaLifecycleEventListener { 11 | 12 | private LinkedBlockingDeque events = new LinkedBlockingDeque<>(); 13 | 14 | @EventListener(classes = {SagaStartedEvent.class, SagaStartedEvent.class}) 15 | public void handleSagaStartedEvent(SagaLifecycleEvent event) { 16 | events.add(event); 17 | } 18 | 19 | public void clear() { 20 | events.clear(); 21 | } 22 | 23 | public T expectEvent(Class eventClass) { 24 | try { 25 | return (T) events.poll(500, TimeUnit.MILLISECONDS); 26 | } catch (InterruptedException e) { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/test/java/io/eventuate/examples/tram/sagas/ordersandcustomers/spring/integrationtests/TramCommandsAndEventsIntegrationData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.spring.integrationtests; 2 | 3 | public class TramCommandsAndEventsIntegrationData { 4 | 5 | private long now = System.currentTimeMillis(); 6 | private String commandDispatcherId = "command-dispatcher-" + now; 7 | private String commandChannel = "command-channel-" + now; 8 | private String aggregateDestination = "aggregate-destination-" + now; 9 | private String eventDispatcherId = "event-dispatcher-" + now; 10 | private String sagaEventsChannel = "sagaEventsChannel-" + now; 11 | 12 | public String getAggregateDestination() { 13 | return aggregateDestination; 14 | } 15 | 16 | 17 | public String getCommandDispatcherId() { 18 | return commandDispatcherId; 19 | } 20 | 21 | public String getCommandChannel() { 22 | return commandChannel; 23 | } 24 | 25 | public String getEventDispatcherId() { 26 | return eventDispatcherId; 27 | } 28 | 29 | public String getSagaEventsChannel() { 30 | return sagaEventsChannel; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/test/resources/application-mssql.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:sqlserver://${$DOCKER_HOST_IP:localhost}:1433;databaseName=eventuate 2 | spring.datasource.username=sa 3 | spring.datasource.password=Eventuate123! 4 | spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver 5 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/test/resources/application-postgres.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://${DOCKER_HOST_IP:localhost}/eventuate 2 | spring.datasource.username=eventuate 3 | spring.datasource.password=eventuate 4 | spring.datasource.driver-class-name=org.postgresql.Driver 5 | -------------------------------------------------------------------------------- /orders-and-customers-spring/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.main.allow-bean-definition-overriding=true 2 | 3 | spring.jpa.generate-ddl=true 4 | logging.level.org.springframework.orm.jpa=INFO 5 | logging.level.org.hibernate.SQL=DEBUG 6 | logging.level.io.eventuate=DEBUG 7 | 8 | eventuatelocal.kafka.bootstrap.servers=${DOCKER_HOST_IP:localhost}:9092 9 | eventuatelocal.cdc.db.user.name=root 10 | eventuatelocal.cdc.db.password=rootpassword 11 | eventuatelocal.zookeeper.connection.string=${DOCKER_HOST_IP:localhost}:2181 12 | 13 | activemq.url=tcp://${$DOCKER_HOST_IP:localhost}:61616 14 | 15 | spring.datasource.url=jdbc:mysql://${DOCKER_HOST_IP:localhost}/eventuate 16 | spring.datasource.username=mysqluser 17 | spring.datasource.password=mysqlpw 18 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 19 | -------------------------------------------------------------------------------- /orders-and-customers/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":eventuate-tram-sagas-participant") 4 | api project(":eventuate-tram-sagas-orchestration") 5 | api project(":eventuate-tram-sagas-orchestration-simple-dsl") 6 | api 'javax.persistence:javax.persistence-api:2.2' 7 | 8 | testImplementation project(":eventuate-tram-sagas-unit-testing-support") 9 | 10 | } 11 | 12 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/customers/domain/CustomerCreditLimitExceededException.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.customers.domain; 2 | 3 | public class CustomerCreditLimitExceededException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/customers/domain/CustomerDao.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.customers.domain; 2 | 3 | public interface CustomerDao { 4 | Customer findById(long id); 5 | Customer save(Customer customer); 6 | } 7 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/customers/service/CustomerCreditReservationFailed.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.customers.service; 2 | 3 | public class CustomerCreditReservationFailed implements ResultCreditResult { 4 | } 5 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/customers/service/CustomerCreditReserved.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.customers.service; 2 | 3 | public class CustomerCreditReserved implements ResultCreditResult { 4 | } 5 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/customers/service/CustomerService.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.customers.service; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.commondomain.Money; 4 | import io.eventuate.examples.tram.sagas.ordersandcustomers.customers.domain.Customer; 5 | import io.eventuate.examples.tram.sagas.ordersandcustomers.customers.domain.CustomerDao; 6 | 7 | public class CustomerService { 8 | 9 | private CustomerDao customerDao; 10 | 11 | public CustomerService(CustomerDao customerDao) { 12 | this.customerDao = customerDao; 13 | } 14 | 15 | public Customer createCustomer(String name, Money creditLimit) { 16 | Customer customer = new Customer(name, creditLimit); 17 | return customerDao.save(customer); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/customers/service/ResultCreditResult.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.customers.service; 2 | 3 | public interface ResultCreditResult { 4 | } 5 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/domain/OrderDao.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.domain; 2 | 3 | public interface OrderDao { 4 | Order findById(long id); 5 | Order save(Order order); 6 | } 7 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/domain/OrderState.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.domain; 2 | 3 | public enum OrderState { PENDING, APPROVED, REJECTED } 4 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/sagas/cancelorder/ReleaseCreditCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.cancelorder; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class ReleaseCreditCommand implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/sagas/createorder/CreateOrderSagaCompletedSuccesfully.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.createorder; 2 | 3 | import io.eventuate.tram.events.common.DomainEvent; 4 | 5 | public class CreateOrderSagaCompletedSuccesfully implements DomainEvent { 6 | private long orderId; 7 | 8 | public CreateOrderSagaCompletedSuccesfully() { 9 | } 10 | 11 | public CreateOrderSagaCompletedSuccesfully(long orderId) { 12 | this.orderId = orderId; 13 | } 14 | 15 | public long getOrderId() { 16 | return orderId; 17 | } 18 | 19 | public void setOrderId(long orderId) { 20 | this.orderId = orderId; 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/sagas/createorder/CreateOrderSagaData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.createorder; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.orders.service.OrderDetails; 4 | 5 | public class CreateOrderSagaData { 6 | 7 | private Long orderId; 8 | 9 | private OrderDetails orderDetails; 10 | 11 | public CreateOrderSagaData() { 12 | } 13 | 14 | public CreateOrderSagaData(Long orderId, OrderDetails orderDetails) { 15 | this.orderId = orderId; 16 | this.orderDetails = orderDetails; 17 | } 18 | 19 | public CreateOrderSagaData(OrderDetails orderDetails) { 20 | this.orderDetails = orderDetails; 21 | } 22 | 23 | public Long getOrderId() { 24 | return orderId; 25 | } 26 | 27 | public OrderDetails getOrderDetails() { 28 | return orderDetails; 29 | } 30 | 31 | public void setOrderId(Long orderId) { 32 | this.orderId = orderId; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/sagas/createorder/CreateOrderSagaRolledBack.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.createorder; 2 | 3 | import io.eventuate.tram.events.common.DomainEvent; 4 | 5 | public class CreateOrderSagaRolledBack implements DomainEvent { 6 | 7 | private long orderId; 8 | 9 | public CreateOrderSagaRolledBack() { 10 | } 11 | 12 | public CreateOrderSagaRolledBack(long orderId) { 13 | this.orderId = orderId; 14 | } 15 | 16 | public long getOrderId() { 17 | return orderId; 18 | } 19 | 20 | public void setOrderId(long orderId) { 21 | this.orderId = orderId; 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/sagas/createorder/LocalCreateOrderSagaData.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.createorder; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.orders.service.OrderDetails; 4 | 5 | public class LocalCreateOrderSagaData { 6 | 7 | private Long orderId; 8 | 9 | private OrderDetails orderDetails; 10 | 11 | public LocalCreateOrderSagaData() { 12 | } 13 | 14 | public LocalCreateOrderSagaData(OrderDetails orderDetails) { 15 | this.orderDetails = orderDetails; 16 | } 17 | 18 | public Long getOrderId() { 19 | return orderId; 20 | } 21 | 22 | public OrderDetails getOrderDetails() { 23 | return orderDetails; 24 | } 25 | 26 | public void setOrderId(Long orderId) { 27 | this.orderId = orderId; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/sagas/participants/ApproveOrderCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.participants; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class ApproveOrderCommand implements Command { 6 | private long orderId; 7 | 8 | private ApproveOrderCommand() { 9 | } 10 | 11 | 12 | public ApproveOrderCommand(long orderId) { 13 | 14 | this.orderId = orderId; 15 | } 16 | 17 | public long getOrderId() { 18 | return orderId; 19 | } 20 | 21 | public void setOrderId(long orderId) { 22 | this.orderId = orderId; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/sagas/participants/CancelOrderCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.participants; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class CancelOrderCommand implements Command { 6 | } 7 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/sagas/participants/ReserveCreditCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.participants; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.commondomain.Money; 4 | import io.eventuate.tram.commands.common.Command; 5 | 6 | public class ReserveCreditCommand implements Command { 7 | private Long orderId; 8 | private Money orderTotal; 9 | private long customerId; 10 | 11 | public ReserveCreditCommand() { 12 | } 13 | 14 | public ReserveCreditCommand(Long customerId, Long orderId, Money orderTotal) { 15 | this.customerId = customerId; 16 | this.orderId = orderId; 17 | this.orderTotal = orderTotal; 18 | } 19 | 20 | public Money getOrderTotal() { 21 | return orderTotal; 22 | } 23 | 24 | public void setOrderTotal(Money orderTotal) { 25 | this.orderTotal = orderTotal; 26 | } 27 | 28 | public Long getOrderId() { 29 | 30 | return orderId; 31 | } 32 | 33 | public void setOrderId(Long orderId) { 34 | 35 | this.orderId = orderId; 36 | } 37 | 38 | public long getCustomerId() { 39 | return customerId; 40 | } 41 | 42 | public void setCustomerId(long customerId) { 43 | this.customerId = customerId; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/sagas/participants/proxy/CustomerServiceProxy.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.participants.proxy; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.participants.ReserveCreditCommand; 4 | import io.eventuate.tram.commands.common.Success; 5 | import io.eventuate.tram.sagas.simpledsl.CommandEndpoint; 6 | import io.eventuate.tram.sagas.simpledsl.CommandEndpointBuilder; 7 | 8 | 9 | /* 10 | 11 | public class CreateOrderSaga implements SimpleSaga { 12 | 13 | CustomerServiceProxy customerService; 14 | 15 | private SagaDefinition sagaDefinition = 16 | step() 17 | .withCompensation(this::reject) 18 | .step() 19 | .invokeParticipant(customerService, this::makeReserveCreditCommand) 20 | .onReply(CustomerServiceProxy.success, handler) <<< Build time checking???? 21 | .step() 22 | .invokeParticipant(this::approve) 23 | .build(); 24 | */ 25 | 26 | public class CustomerServiceProxy { 27 | 28 | public final CommandEndpoint reserveCredit = CommandEndpointBuilder 29 | .forCommand(ReserveCreditCommand.class) 30 | .withChannel("customerService") 31 | .withReply(Success.class) 32 | .build(); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/sagas/participants/proxy/OrderServiceProxy.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.participants.proxy; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.orders.sagas.participants.ApproveOrderCommand; 4 | import io.eventuate.examples.tram.sagas.ordersandcustomers.orders.service.RejectOrderCommand; 5 | import io.eventuate.tram.commands.common.Success; 6 | import io.eventuate.tram.sagas.simpledsl.CommandEndpoint; 7 | import io.eventuate.tram.sagas.simpledsl.CommandEndpointBuilder; 8 | 9 | 10 | public class OrderServiceProxy { 11 | 12 | public final CommandEndpoint reject = CommandEndpointBuilder 13 | .forCommand(RejectOrderCommand.class) 14 | .withChannel("orderService") 15 | .withReply(Success.class) 16 | .build(); 17 | 18 | public final CommandEndpoint approve = CommandEndpointBuilder 19 | .forCommand(ApproveOrderCommand.class) 20 | .withChannel("orderService") 21 | .withReply(Success.class) 22 | .build(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/service/OrderDetails.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.service; 2 | 3 | import io.eventuate.examples.tram.sagas.ordersandcustomers.commondomain.Money; 4 | 5 | import javax.persistence.Embeddable; 6 | import javax.persistence.Embedded; 7 | 8 | @Embeddable 9 | public class OrderDetails { 10 | 11 | private Long customerId; 12 | 13 | @Embedded 14 | private Money orderTotal; 15 | 16 | public OrderDetails() { 17 | } 18 | 19 | public OrderDetails(Long customerId, Money orderTotal) { 20 | this.customerId = customerId; 21 | this.orderTotal = orderTotal; 22 | } 23 | 24 | public Long getCustomerId() { 25 | return customerId; 26 | } 27 | 28 | public Money getOrderTotal() { 29 | return orderTotal; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /orders-and-customers/src/main/java/io/eventuate/examples/tram/sagas/ordersandcustomers/orders/service/RejectOrderCommand.java: -------------------------------------------------------------------------------- 1 | package io.eventuate.examples.tram.sagas.ordersandcustomers.orders.service; 2 | 3 | import io.eventuate.tram.commands.common.Command; 4 | 5 | public class RejectOrderCommand implements Command { 6 | 7 | private long orderId; 8 | 9 | private RejectOrderCommand() { 10 | } 11 | 12 | public void setOrderId(long orderId) { 13 | this.orderId = orderId; 14 | } 15 | 16 | public RejectOrderCommand(long orderId) { 17 | 18 | this.orderId = orderId; 19 | } 20 | 21 | public long getOrderId() { 22 | return orderId; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /postgres-cli.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | docker run $* \ 4 | --name postgresterm --rm \ 5 | -e POSTGRES_ENV_POSTGRES_USER=eventuate -e POSTGRES_ENV_POSTGRES_PASSWORD=eventuate -e POSTGRES_HOST=$DOCKER_HOST_IP \ 6 | postgres:9.6.5 \ 7 | sh -c 'export PGPASSWORD="$POSTGRES_ENV_POSTGRES_PASSWORD"; exec psql -h $POSTGRES_HOST -U "$POSTGRES_ENV_POSTGRES_USER" ' 8 | -------------------------------------------------------------------------------- /postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG EVENTUATE_COMMON_VERSION 2 | FROM eventuateio/eventuate-postgres:$EVENTUATE_COMMON_VERSION 3 | COPY tram-saga-schema.sql /docker-entrypoint-initdb.d 4 | -------------------------------------------------------------------------------- /postgres/build-docker.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | docker build -t test-eventuate-tram-sagas-postgres . -------------------------------------------------------------------------------- /postgres/tram-saga-schema.sql: -------------------------------------------------------------------------------- 1 | DROP Table IF Exists eventuate.saga_instance_participants; 2 | DROP Table IF Exists eventuate.saga_instance; 3 | DROP Table IF Exists eventuate.saga_lock_table; 4 | DROP Table IF Exists eventuate.saga_stash_table; 5 | 6 | CREATE TABLE eventuate.saga_instance_participants ( 7 | saga_type VARCHAR(255) NOT NULL, 8 | saga_id VARCHAR(100) NOT NULL, 9 | destination VARCHAR(100) NOT NULL, 10 | resource VARCHAR(100) NOT NULL, 11 | PRIMARY KEY(saga_type, saga_id, destination, resource) 12 | ); 13 | 14 | CREATE TABLE eventuate.saga_instance( 15 | saga_type VARCHAR(255) NOT NULL, 16 | saga_id VARCHAR(100) NOT NULL, 17 | state_name VARCHAR(100) NOT NULL, 18 | last_request_id VARCHAR(100), 19 | end_state BOOLEAN, 20 | compensating BOOLEAN, 21 | failed BOOLEAN, 22 | saga_data_type VARCHAR(1000) NOT NULL, 23 | saga_data_json VARCHAR(1000) NOT NULL, 24 | PRIMARY KEY(saga_type, saga_id) 25 | ); 26 | 27 | create table eventuate.saga_lock_table( 28 | target VARCHAR(100) PRIMARY KEY, 29 | saga_type VARCHAR(255) NOT NULL, 30 | saga_Id VARCHAR(100) NOT NULL 31 | ); 32 | 33 | create table eventuate.saga_stash_table( 34 | message_id VARCHAR(100) PRIMARY KEY, 35 | target VARCHAR(100) NOT NULL, 36 | saga_type VARCHAR(255) NOT NULL, 37 | saga_id VARCHAR(100) NOT NULL, 38 | message_headers VARCHAR(1000) NOT NULL, 39 | message_payload VARCHAR(1000) NOT NULL 40 | ); 41 | -------------------------------------------------------------------------------- /publish-docker-images.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | VERSION=${1?} 4 | 5 | docker tag test-eventuate-tram-sagas-mysql eventuateio/eventuate-tram-sagas-mysql:${VERSION?} 6 | 7 | docker push eventuateio/eventuate-tram-sagas-mysql:${VERSION?} 8 | -------------------------------------------------------------------------------- /schema-for-testing-reactive-framework.sql: -------------------------------------------------------------------------------- 1 | USE eventuate; 2 | 3 | CREATE TABLE customer ( 4 | id BIGINT AUTO_INCREMENT PRIMARY KEY, 5 | name VARCHAR(1024), 6 | version BIGINT, 7 | creation_time BIGINT, 8 | credit_limit DECIMAL 9 | ); 10 | 11 | CREATE TABLE credit_reservation ( 12 | id BIGINT AUTO_INCREMENT PRIMARY KEY, 13 | customer_id BIGINT, 14 | order_id BIGINT, 15 | reservation DECIMAL 16 | ); 17 | 18 | CREATE TABLE orders ( 19 | id BIGINT AUTO_INCREMENT PRIMARY KEY, 20 | state VARCHAR(32), 21 | rejection_reason VARCHAR(32), 22 | customer_id BIGINT, 23 | order_total DECIMAL 24 | ); -------------------------------------------------------------------------------- /set-multi-arch-image-env-vars.sh: -------------------------------------------------------------------------------- 1 | export MULTI_ARCH_TAG=test-build-${CIRCLE_SHA1?} 2 | export BUILDX_PUSH_OPTIONS=--push 3 | 4 | export MYSQL_MULTI_ARCH_IMAGE=eventuateio/eventuate-tram-sagas-mysql:$MULTI_ARCH_TAG 5 | --------------------------------------------------------------------------------