Fix bean definition conflicts in transactional engine#14
Conversation
- Remove @primary from sagaObjectMapper to prevent conflict with eventsourcing's @primary ObjectMapper when both are on classpath - Remove redundant simple tccEvents bean that prevented the composite TccEvents from being created due to @ConditionalOnMissingBean ordering - Remove @ConditionalOnMissingBean from tccEventsComposite so the composite pattern is always active (consistent with sagaEventsComposite) - Add @ConditionalOnMissingBean to webClientBuilder to avoid conflict with Spring Boot's auto-configured WebClient.Builder
ancongui
left a comment
There was a problem hiding this comment.
Code Review: Fix bean definition conflicts in transactional engine
Summary
This PR makes three changes:
- Removes
@PrimaryfromsagaObjectMapper()to prevent conflict with eventsourcing's@PrimaryObjectMapper - Removes the redundant simple
tccEvents()bean and removes@ConditionalOnMissingBean(TccEvents.class)fromtccEventsComposite() - Adds
@ConditionalOnMissingBeantowebClientBuilder()
CI Status
❌ BUILD FAILS — 3 test errors in TccEngineIntegrationTest
Findings
1. 🔴 CRITICAL: Build failure — dual @Primary conflict in tests (Severity: Critical)
The build fails with:
No qualifying bean of type 'TccEvents' available: more than one 'primary' bean found
among candidates: [tccEventsComposite, testEvents]
The root cause: removing @ConditionalOnMissingBean(TccEvents.class) from tccEventsComposite() means it is ALWAYS registered. But TccEngineIntegrationTest likely defines its own testEvents bean (also @Primary), creating a conflict.
Fix options:
- Option A: Keep
@ConditionalOnMissingBean(TccEvents.class)ontccEventsComposite()— but then the test'stestEventsbean would shadow the composite, which may not be desired. - Option B: Remove
@PrimaryfromtccEventsComposite()and use@Qualifierwhere needed. - Option C: Update the test configuration to not use
@Primaryon itstestEventsbean (use@Qualifierinstead).
2. ✅ Removing @Primary from sagaObjectMapper() (Severity: None — correct)
This is a good change. The @ConditionalOnMissingBean guard ensures the bean only registers when no other ObjectMapper exists, and removing @Primary prevents conflicts with the eventsourcing module's ObjectMapper. Clean and correct.
3. ✅ Adding @ConditionalOnMissingBean to webClientBuilder() (Severity: None — correct)
This is a good addition. Spring Boot auto-configures a WebClient.Builder when WebFlux is on the classpath, and this guard prevents the conflict. Standard Spring Boot practice.
4. tccEvents() bean (Severity: Medium)
Removing the simple tccEvents() bean that wraps only the LoggingTransactionalObserver is fine IF the composite always being active is the desired behavior. However, this changes behavior for existing consumers who might have relied on the simple bean being the default when micrometer/tracing weren't on the classpath. The composite should still work fine in that case (it uses ObjectProvider for optional observers), but verify this doesn't change the behavior path.
Recommendation
REQUEST CHANGES — The build is failing due to the @Primary conflict in TccEngineIntegrationTest. This must be fixed before merging. The ObjectMapper and WebClient.Builder changes are correct and ready.
Make tccEventsComposite aggregate all TccEvents beans from the ApplicationContext (consistent with sagaEventsComposite pattern). This allows test and custom TccEvents beans to be collected by the composite without requiring @primary, preventing the dual-primary conflict that caused build failures.
Summary
@PrimaryfromsagaObjectMapper()inSagaPersistenceAutoConfigurationto prevent conflict with eventsourcing's@PrimaryObjectMapper when both modules are on the classpath. The@ConditionalOnMissingBeanguard remains as fallback.tccEvents()bean that blocked the compositetccEventsComposite()from being created due to@ConditionalOnMissingBeanordering. Removed@ConditionalOnMissingBean(TccEvents.class)fromtccEventsComposite()so the composite pattern is always active (consistent withsagaEventsComposite).@ConditionalOnMissingBeantowebClientBuilder()to avoid conflict with Spring Boot's auto-configuredWebClient.Builder.Test plan