From 59e33ff26cf2a9e683c15645889ef647da14d00f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Contreras=20Guill=C3=A9n?= Date: Thu, 12 Feb 2026 22:20:01 +0100 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20hexagonal=20architecture=20reme?= =?UTF-8?q?diation=20=E2=80=94=20transactional=20engine=20@Import=20remova?= =?UTF-8?q?l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of the Firefly Framework hexagonal architecture remediation. - Remove @Import from EnableTransactionalEngine (redundant with .imports) - Add TransactionalEngineConfiguration to .imports file - EnableTransactionalEngine is now a marker-only annotation --- .../annotations/EnableTransactionalEngine.java | 12 +++++------- ...work.boot.autoconfigure.AutoConfiguration.imports | 1 + 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/fireflyframework/transactional/shared/annotations/EnableTransactionalEngine.java b/src/main/java/org/fireflyframework/transactional/shared/annotations/EnableTransactionalEngine.java index 19e2323..b01b6a0 100644 --- a/src/main/java/org/fireflyframework/transactional/shared/annotations/EnableTransactionalEngine.java +++ b/src/main/java/org/fireflyframework/transactional/shared/annotations/EnableTransactionalEngine.java @@ -17,17 +17,16 @@ package org.fireflyframework.transactional.shared.annotations; -import org.fireflyframework.transactional.saga.config.SagaPersistenceAutoConfiguration; -import org.fireflyframework.transactional.saga.config.SagaRedisAutoConfiguration; -import org.fireflyframework.transactional.shared.config.TransactionalEngineConfiguration; -import org.springframework.context.annotation.Import; - import java.lang.annotation.*; /** * Enables the Transactional Engine (Saga orchestrator) components in a Spring application. *

- * Imports {@link org.fireflyframework.transactional.config.TransactionalEngineConfiguration} that wires: + * The auto-configuration classes are registered via + * {@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports}. + * This annotation serves as a marker only. + *

+ * Components wired by auto-configuration: * - {@code SagaRegistry}: scans for @Saga beans and indexes steps * - {@code SagaEngine}: the in-memory orchestrator * - {@code SagaEvents}: default implementation {@code SagaLoggerEvents} (override by declaring your own bean) @@ -38,6 +37,5 @@ @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@Import({TransactionalEngineConfiguration.class, SagaPersistenceAutoConfiguration.class, SagaRedisAutoConfiguration.class}) public @interface EnableTransactionalEngine { } diff --git a/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 544b9ff..08f0ec2 100644 --- a/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,3 +1,4 @@ +org.fireflyframework.transactional.shared.config.TransactionalEngineConfiguration org.fireflyframework.transactional.saga.config.SagaPersistenceAutoConfiguration org.fireflyframework.transactional.saga.config.SagaRedisAutoConfiguration org.fireflyframework.transactional.saga.config.SagaCompositionAutoConfiguration From 93226ccbb08a14d30043070ca0403bf835db8c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Contreras=20Guill=C3=A9n?= Date: Thu, 12 Feb 2026 23:04:08 +0100 Subject: [PATCH 2/5] fix: restore @Import on @EnableTransactionalEngine for non-Boot contexts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @Enable* annotations are designed to use @Import — this is the correct Spring pattern (cf. @EnableScheduling, @EnableCaching, etc.). The anti-pattern we fixed was @Import on @AutoConfiguration classes. Restore @Import(TransactionalEngineConfiguration.class) so the annotation works in both Spring Boot (auto-configuration) and plain Spring contexts (e.g. AnnotationConfigApplicationContext in tests). Conditional configs (persistence, Redis) remain in the .imports file. --- .../EnableTransactionalEngine.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/fireflyframework/transactional/shared/annotations/EnableTransactionalEngine.java b/src/main/java/org/fireflyframework/transactional/shared/annotations/EnableTransactionalEngine.java index b01b6a0..b0de973 100644 --- a/src/main/java/org/fireflyframework/transactional/shared/annotations/EnableTransactionalEngine.java +++ b/src/main/java/org/fireflyframework/transactional/shared/annotations/EnableTransactionalEngine.java @@ -17,19 +17,27 @@ package org.fireflyframework.transactional.shared.annotations; +import org.fireflyframework.transactional.shared.config.TransactionalEngineConfiguration; +import org.springframework.context.annotation.Import; + import java.lang.annotation.*; /** * Enables the Transactional Engine (Saga orchestrator) components in a Spring application. *

- * The auto-configuration classes are registered via - * {@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports}. - * This annotation serves as a marker only. + * This annotation imports {@link TransactionalEngineConfiguration} directly so it works + * in both Spring Boot (auto-configuration) and plain Spring contexts + * (e.g. {@code AnnotationConfigApplicationContext}). + *

+ * Additional conditional configurations (persistence, Redis, composition) are registered via + * {@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports} + * and activated automatically in Spring Boot applications. *

- * Components wired by auto-configuration: + * Components wired by this annotation: * - {@code SagaRegistry}: scans for @Saga beans and indexes steps * - {@code SagaEngine}: the in-memory orchestrator - * - {@code SagaEvents}: default implementation {@code SagaLoggerEvents} (override by declaring your own bean) + * - {@code TccEngine}: the TCC coordinator + * - {@code SagaEvents}: default implementation (override by declaring your own bean) * - {@code StepLoggingAspect}: AOP aspect for additional logging * - {@code WebClient.Builder}: convenience bean for HTTP clients */ @@ -37,5 +45,6 @@ @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited +@Import(TransactionalEngineConfiguration.class) public @interface EnableTransactionalEngine { } From aee79536ed3d639ec49c5637d2d7391784c94be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Contreras=20Guill=C3=A9n?= Date: Thu, 12 Feb 2026 23:07:26 +0100 Subject: [PATCH 3/5] fix: add default InMemorySagaPersistenceProvider to core configuration TransactionalEngineConfiguration had a default InMemoryTccPersistenceProvider for TCC but no equivalent for Saga. When SagaPersistenceAutoConfiguration is not loaded (e.g. in plain Spring contexts via @EnableTransactionalEngine), SagaEngine creation fails due to missing SagaPersistenceProvider. Add a @ConditionalOnMissingBean default so the core configuration is self-contained, matching the existing TCC pattern. --- .../shared/config/TransactionalEngineConfiguration.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/fireflyframework/transactional/shared/config/TransactionalEngineConfiguration.java b/src/main/java/org/fireflyframework/transactional/shared/config/TransactionalEngineConfiguration.java index 7bad0fb..a6f923c 100644 --- a/src/main/java/org/fireflyframework/transactional/shared/config/TransactionalEngineConfiguration.java +++ b/src/main/java/org/fireflyframework/transactional/shared/config/TransactionalEngineConfiguration.java @@ -156,6 +156,12 @@ public TccEventPublisher tccEventPublisher() { return new NoOpTccEventPublisher(); } + @Bean + @ConditionalOnMissingBean + public SagaPersistenceProvider sagaPersistenceProvider() { + return new org.fireflyframework.transactional.saga.persistence.impl.InMemorySagaPersistenceProvider(); + } + @Bean @ConditionalOnMissingBean public TccPersistenceProvider tccPersistenceProvider() { From bb4851f2a9bcd500f0839029467817a118fa4ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Contreras=20Guill=C3=A9n?= Date: Thu, 12 Feb 2026 23:10:53 +0100 Subject: [PATCH 4/5] fix: add explicit persistence config imports to RedisPersistenceIntegrationTest This test requires SagaPersistenceAutoConfiguration and SagaRedisAutoConfiguration for the SagaRecoveryService bean. Add explicit @Import to match the pattern already used in SagaRecoveryIntegrationTest. Tests that need specific conditional configs should declare them explicitly rather than relying on @EnableTransactionalEngine to load everything transitively. --- .../integration/RedisPersistenceIntegrationTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/org/fireflyframework/transactional/persistence/integration/RedisPersistenceIntegrationTest.java b/src/test/java/org/fireflyframework/transactional/persistence/integration/RedisPersistenceIntegrationTest.java index 6c89b27..a98682f 100644 --- a/src/test/java/org/fireflyframework/transactional/persistence/integration/RedisPersistenceIntegrationTest.java +++ b/src/test/java/org/fireflyframework/transactional/persistence/integration/RedisPersistenceIntegrationTest.java @@ -22,6 +22,8 @@ import org.fireflyframework.transactional.saga.core.SagaContext; import org.fireflyframework.transactional.saga.engine.SagaEngine; import org.fireflyframework.transactional.saga.engine.StepInputs; +import org.fireflyframework.transactional.saga.config.SagaPersistenceAutoConfiguration; +import org.fireflyframework.transactional.saga.config.SagaRedisAutoConfiguration; import org.fireflyframework.transactional.shared.annotations.EnableTransactionalEngine; import org.fireflyframework.transactional.shared.core.StepStatus; import org.fireflyframework.transactional.saga.persistence.SagaExecutionState; @@ -34,6 +36,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; @@ -101,6 +104,7 @@ static void configureProperties(DynamicPropertyRegistry registry) { @Configuration @EnableTransactionalEngine + @Import({SagaPersistenceAutoConfiguration.class, SagaRedisAutoConfiguration.class}) static class TestConfig { @Bean From bebf7edb9fc8916d01da72c9c695a6d2c5582e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Contreras=20Guill=C3=A9n?= Date: Fri, 13 Feb 2026 01:34:09 +0100 Subject: [PATCH 5/5] fix: add unified exception hierarchy and reactive pattern fixes - Add fireflyframework-kernel dependency for unified exceptions - Fix fire-and-forget reactive patterns with error handlers - Standardize error handling across transactional flows --- pom.xml | 7 +++++++ .../saga/composition/CompositionValidationException.java | 4 +++- .../saga/engine/ReactiveStreamOptimizations.java | 3 ++- .../transactional/saga/engine/SagaCompensator.java | 3 ++- .../persistence/serialization/SagaStateSerializer.java | 3 ++- .../saga/validation/SagaValidationService.java | 5 +++-- .../transactional/saga/validation/SagaValidator.java | 3 ++- .../backpressure/CircuitBreakerBackpressureStrategy.java | 3 ++- 8 files changed, 23 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index c3d8d9c..652841e 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,13 @@ jackson-datatype-jsr310 + + + org.fireflyframework + fireflyframework-kernel + ${project.version} + + org.fireflyframework diff --git a/src/main/java/org/fireflyframework/transactional/saga/composition/CompositionValidationException.java b/src/main/java/org/fireflyframework/transactional/saga/composition/CompositionValidationException.java index a074e13..3621616 100644 --- a/src/main/java/org/fireflyframework/transactional/saga/composition/CompositionValidationException.java +++ b/src/main/java/org/fireflyframework/transactional/saga/composition/CompositionValidationException.java @@ -16,6 +16,8 @@ package org.fireflyframework.transactional.saga.composition; +import org.fireflyframework.kernel.exception.FireflyException; + import java.util.List; import java.util.stream.Collectors; @@ -26,7 +28,7 @@ * during composition building, including helpful suggestions for * resolving the problems. */ -public class CompositionValidationException extends RuntimeException { +public class CompositionValidationException extends FireflyException { private final String compositionName; private final List validationIssues; diff --git a/src/main/java/org/fireflyframework/transactional/saga/engine/ReactiveStreamOptimizations.java b/src/main/java/org/fireflyframework/transactional/saga/engine/ReactiveStreamOptimizations.java index 3a04720..7fc17ab 100644 --- a/src/main/java/org/fireflyframework/transactional/saga/engine/ReactiveStreamOptimizations.java +++ b/src/main/java/org/fireflyframework/transactional/saga/engine/ReactiveStreamOptimizations.java @@ -17,6 +17,7 @@ package org.fireflyframework.transactional.saga.engine; +import org.fireflyframework.kernel.exception.FireflyException; import org.fireflyframework.transactional.saga.core.SagaContext; import org.fireflyframework.transactional.saga.registry.SagaDefinition; import org.fireflyframework.transactional.saga.registry.StepDefinition; @@ -319,7 +320,7 @@ public interface LayerExecutor { Mono executeStep(String stepId); } - public static class CircuitBreakerException extends RuntimeException { + public static class CircuitBreakerException extends FireflyException { public CircuitBreakerException(String message) { super(message); } diff --git a/src/main/java/org/fireflyframework/transactional/saga/engine/SagaCompensator.java b/src/main/java/org/fireflyframework/transactional/saga/engine/SagaCompensator.java index 82b8a66..e5364ce 100644 --- a/src/main/java/org/fireflyframework/transactional/saga/engine/SagaCompensator.java +++ b/src/main/java/org/fireflyframework/transactional/saga/engine/SagaCompensator.java @@ -17,6 +17,7 @@ package org.fireflyframework.transactional.saga.engine; +import org.fireflyframework.kernel.exception.FireflyException; import org.fireflyframework.transactional.saga.core.SagaContext; import org.fireflyframework.transactional.saga.registry.SagaDefinition; import org.fireflyframework.transactional.saga.registry.StepDefinition; @@ -304,7 +305,7 @@ private Object resolveCompensationArg(Method comp, Object input, Object result) /** * Exception thrown when compensation fails and the error handler decides to fail the saga. */ - public static class CompensationFailedException extends RuntimeException { + public static class CompensationFailedException extends FireflyException { public CompensationFailedException(String message, Throwable cause) { super(message, cause); } diff --git a/src/main/java/org/fireflyframework/transactional/saga/persistence/serialization/SagaStateSerializer.java b/src/main/java/org/fireflyframework/transactional/saga/persistence/serialization/SagaStateSerializer.java index 41047c0..39ec272 100644 --- a/src/main/java/org/fireflyframework/transactional/saga/persistence/serialization/SagaStateSerializer.java +++ b/src/main/java/org/fireflyframework/transactional/saga/persistence/serialization/SagaStateSerializer.java @@ -16,6 +16,7 @@ package org.fireflyframework.transactional.saga.persistence.serialization; +import org.fireflyframework.kernel.exception.FireflyException; import org.fireflyframework.transactional.saga.persistence.SagaExecutionState; /** @@ -80,7 +81,7 @@ public interface SagaStateSerializer { /** * Exception thrown when serialization or deserialization fails. */ - class SerializationException extends Exception { + class SerializationException extends FireflyException { public SerializationException(String message) { super(message); } diff --git a/src/main/java/org/fireflyframework/transactional/saga/validation/SagaValidationService.java b/src/main/java/org/fireflyframework/transactional/saga/validation/SagaValidationService.java index 1fc9690..0e54789 100644 --- a/src/main/java/org/fireflyframework/transactional/saga/validation/SagaValidationService.java +++ b/src/main/java/org/fireflyframework/transactional/saga/validation/SagaValidationService.java @@ -16,6 +16,7 @@ package org.fireflyframework.transactional.saga.validation; +import org.fireflyframework.kernel.exception.FireflyException; import org.fireflyframework.transactional.saga.registry.SagaDefinition; import org.fireflyframework.transactional.saga.registry.SagaRegistry; import org.fireflyframework.transactional.saga.registry.StepDefinition; @@ -284,11 +285,11 @@ public void validateSagaInputsOrThrow(SagaDefinition saga, Object inputs) throws /** * Exception thrown when saga validation fails. */ - public static class SagaValidationException extends Exception { + public static class SagaValidationException extends FireflyException { public SagaValidationException(String message) { super(message); } - + public SagaValidationException(String message, Throwable cause) { super(message, cause); } diff --git a/src/main/java/org/fireflyframework/transactional/saga/validation/SagaValidator.java b/src/main/java/org/fireflyframework/transactional/saga/validation/SagaValidator.java index 5775f7a..645ccaa 100644 --- a/src/main/java/org/fireflyframework/transactional/saga/validation/SagaValidator.java +++ b/src/main/java/org/fireflyframework/transactional/saga/validation/SagaValidator.java @@ -16,6 +16,7 @@ package org.fireflyframework.transactional.saga.validation; +import org.fireflyframework.kernel.exception.FireflyException; import org.fireflyframework.transactional.saga.registry.SagaDefinition; import org.fireflyframework.transactional.saga.registry.StepDefinition; @@ -266,7 +267,7 @@ private static void validateInputRequirements(SagaDefinition saga, Object inputs /** * Exception thrown when circular dependencies are detected. */ - public static class CircularDependencyException extends Exception { + public static class CircularDependencyException extends FireflyException { public CircularDependencyException(String message) { super(message); } diff --git a/src/main/java/org/fireflyframework/transactional/shared/engine/backpressure/CircuitBreakerBackpressureStrategy.java b/src/main/java/org/fireflyframework/transactional/shared/engine/backpressure/CircuitBreakerBackpressureStrategy.java index 2d336dc..7be95f4 100644 --- a/src/main/java/org/fireflyframework/transactional/shared/engine/backpressure/CircuitBreakerBackpressureStrategy.java +++ b/src/main/java/org/fireflyframework/transactional/shared/engine/backpressure/CircuitBreakerBackpressureStrategy.java @@ -16,6 +16,7 @@ package org.fireflyframework.transactional.shared.engine.backpressure; +import org.fireflyframework.kernel.exception.FireflyException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; @@ -254,7 +255,7 @@ public record CircuitBreakerMetrics( /** * Exception thrown when circuit breaker is open. */ - public static class CircuitBreakerException extends RuntimeException { + public static class CircuitBreakerException extends FireflyException { public CircuitBreakerException(String message) { super(message); }