Skip to content

feat!: instance-scoped integration points with lazily bound transaction scopes#218

Open
zantvoort wants to merge 4 commits into
mainfrom
feat/instance-scoped-integration
Open

feat!: instance-scoped integration points with lazily bound transaction scopes#218
zantvoort wants to merge 4 commits into
mainfrom
feat/instance-scoped-integration

Conversation

@zantvoort

Copy link
Copy Markdown
Collaborator

Makes the framework integration points instance-scoped configuration on the template, with ServiceLoader discovery demoted to fallback-only. Fixes #198.

Core

  • ORMTemplate.builder(dataSource | connection) (core, mirrored in the Java 21 and Kotlin APIs) with four instance-scoped strategy slots: connectionProvider, transactionTemplateProvider, exceptionMapper, queryObserver. All of(...) overloads stay and keep their behavior; templates built without explicit strategies fall back to ServiceLoader discovery.
  • Two new SPIs that Translate SQL exceptions to Spring's DataAccessException hierarchy #199 and Instrument query execution with Micrometer Observations #203/Add query observability to the Ktor integration #207 build on:
    • ExceptionMapper — maps execution failures to the exception thrown to the caller (SQL diagnostics are attached by the framework before mapping; mapper failures never mask the original error).
    • QueryObserver — observes query executions (operation, data type, execution kind, timing, outcome) with strict containment: observer failures never affect query execution.
  • Providers rework — the JVM-wide AtomicReference pin for the winning ConnectionProvider is gone, isEnabled() is re-evaluated per resolution instead of being frozen at first ServiceLoader load, and ambiguous winner-takes-all resolution now fails fast naming the candidates. QueryImpl no longer resolves a transaction template in static init.
  • Spring probes removed — the reflective DataSourceUtils / TransactionSynchronizationManager bridges are deleted from storm-core and storm-kotlin. Templates never silently enlist in Spring transactions based on classpath presence.

Transactions: lazily bound scopes

transaction { } / transactionBlocking { } keep their exact signatures and remain the only transaction API. Opening a block now records a pending TransactionScope (options only); the first template that executes inside the block materializes the scope through its own transaction provider. Scopes nest with full propagation semantics (enclosing scopes materialize outermost-down, preserving eager-frame behavior for REQUIRED/MANDATORY/NEVER/REQUIRES_NEW), join by provider instance identity, fail fast on cross-provider mixing, enforce timeouts from block entry, and propagate rollback-only marks including the joined-scope distinction behind UnexpectedRollbackException. The TransactionTemplate SPI is reshaped from callback-wrapping execute() to open()/complete() handles to make the lazy binding possible.

Integration modules as composition roots

  • storm-kotlin-springSpringTransactionConfiguration (the static, JVM-global transaction manager list) and @EnableTransactionIntegration are deleted. The providers become public, constructor-injected st.orm.spring.SpringConnectionProvider / SpringTransactionTemplateProvider carrying their own context's managers; springOrmTemplate(dataSource) { managers } is the canonical plain-Spring composition.
  • storm-spring (Java)TransactionAwareConnectionProviderImpl becomes the public SpringConnectionProvider, and the reflective Spring-linked context from core is ported as a real SpringTransactionTemplateProvider, so Java applications keep transaction-scoped entity caching under @Transactional.
  • StartersStormTransactionAutoConfiguration contributes the Spring-aware provider beans when a PlatformTransactionManager is present (user-defined beans win via @ConditionalOnMissingBean), and both StormAutoConfigurations consume the strategy beans plus optional ExceptionMapper/QueryObserver beans through the builder. Nothing is registered globally: each application context gets its own, correctly matched transaction integration.
  • storm-ktorinstall(Storm) gains connectionProvider, transactionTemplateProvider, exceptionMapper and queryObserver slots and composes the template with explicit plugin-scoped provider instances.
  • All Spring META-INF/services registrations are removed; storm-kotlin's platform-neutral registrations remain as the sanctioned fallback for standalone use.

Tests

  • DualContextIsolationTest proves the motivating scenario: two application contexts in one JVM with correctly matched transaction managers, isolation across context close, fail-fast on cross-provider mixing, and no silent enlistment of standalone templates.
  • IntegrationStrategiesTest covers builder precedence over ServiceLoader, the ExceptionMapper contract, and the QueryObserver lifecycle (stream-close, error path, containment).
  • The test suites of storm-core, the JSON modules and the dialect modules previously relied on the now-removed reflective probe for their transaction-per-test isolation under @DataJpaTest; they now register an explicit test-scoped Spring-aware ConnectionProvider via META-INF/services and run surefire on the classpath so ServiceLoader picks it up.

Behavior changes (release notes in CHANGELOG.md)

  • Plain templates (ORMTemplate.of(dataSource)) inside a Spring application no longer join Spring transactions; compose with springOrmTemplate or the builder to integrate. The old "programmatic and Spring managed transactions cannot be mixed" guard is superseded by the per-block provider check.
  • The suspending transaction { } variant remains unsupported with Spring-managed transactions and now fails at materialization with a clear message.
  • Docs updated (docs/ only; visible at /docs/next until the 1.13 release): Spring integration recipes, transaction binding semantics, module descriptions.

Full reactor build passes (mvn clean install), including the dialect suites against live Oracle/PostgreSQL/MySQL/MariaDB/MSSQL databases: ~10,500 tests, 0 failures.

zantvoort added 4 commits July 9, 2026 19:12
…nsaction scopes

Replace JVM-global ServiceLoader winner-takes-all resolution of ConnectionProvider
and TransactionTemplateProvider with instance-scoped strategies on a new
ORMTemplate.builder(), mirrored in the java21 and kotlin facades. ServiceLoader
remains the fallback for templates built without explicit strategies; provider
enablement is re-evaluated per resolution and ambiguous resolution fails fast
naming the candidates.

Add two new template-scoped SPIs: ExceptionMapper (maps execution failures to the
thrown exception, enabling platform hierarchies such as Spring's
DataAccessException) and QueryObserver (observes query executions for metrics and
tracing bindings).

Introduce TransactionScope: transaction { } blocks now open a provider-agnostic
pending scope that is materialized by the first template that executes inside it,
through that template's transaction provider. Scopes nest, join by provider
instance identity, fail fast on cross-provider mixing, enforce timeouts from
block entry, and propagate rollback-only marks including the joined-scope
distinction that surfaces UnexpectedRollbackException. The TransactionTemplate
SPI is reshaped from callback-wrapping execute() to open()/complete() handles to
support the lazy binding.

Remove all reflective Spring probes from storm-core and storm-kotlin: templates
never silently enlist in Spring transactions based on classpath presence. The
storm-core test suite now runs on the classpath with test-scoped Spring-aware
providers registered via META-INF/services, preserving transaction-per-test
isolation under @DataJpaTest.
…rategies

storm-spring: rename TransactionAwareConnectionProviderImpl to the public
st.orm.spring.SpringConnectionProvider and add SpringTransactionTemplateProvider,
a non-reflective port of the TransactionSynchronizationManager-linked context
that previously lived in storm-core, preserving transaction-scoped entity
caching for Spring-managed transactions. The ServiceLoader registration is
removed.

storm-kotlin-spring: delete SpringTransactionConfiguration's static transaction
manager list and @EnableTransactionIntegration. The providers become public,
constructor-injected classes (SpringConnectionProvider,
SpringTransactionTemplateProvider) that carry their application context's
transaction managers; SpringTransactionContext is restructured to the
open/complete SPI. springOrmTemplate() is the canonical plain-Spring
composition. ServiceLoader registrations are removed.

Starters: StormTransactionAutoConfiguration now contributes Spring-aware
ConnectionProvider and TransactionTemplateProvider beans (backed off via
@ConditionalOnMissingBean), and both StormAutoConfigurations consume the
strategy beans, plus optional ExceptionMapper and QueryObserver beans, through
the template builder. The Java starter gains transaction-scoped entity caching
under @transactional via the ported provider.

storm-ktor: install(Storm) gains connectionProvider,
transactionTemplateProvider, exceptionMapper and queryObserver slots and
composes the template with explicit plugin-scoped provider instances.
…ration

Add DualContextIsolationTest: two application contexts in one JVM bind
transactions to their own managers, survive each other's shutdown, fail fast
when templates with different transaction providers share one block, and
standalone templates no longer silently enlist in Spring-managed transactions.

Add IntegrationStrategiesTest covering builder precedence over ServiceLoader
discovery, ordered fallback resolution, the ExceptionMapper contract (context
propagation, mapper failures never mask the original error), and the
QueryObserver lifecycle (stream-close, error path, observer containment).

The test suites of storm-core, the JSON modules and the dialect modules relied
on the removed reflective Spring probe for their transaction-per-test isolation
under @DataJpaTest. They now register an explicit test-scoped Spring-aware
ConnectionProvider via META-INF/services and run surefire on the classpath so
ServiceLoader picks it up.
…n semantics

Replace the @EnableTransactionIntegration guidance with springOrmTemplate for
plain Spring and the starter's provider beans for Spring Boot, document that
transaction blocks bind to the first template that executes inside them, and
add the changelog entries for the integration-point rework.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make framework integration points instance-scoped

1 participant