Skip to content

feat: property-driven template composition for Spring Boot and Ktor - #520

Merged
zantvoort merged 4 commits into
mainfrom
feat/orm-template-factory
Aug 21, 2026
Merged

feat: property-driven template composition for Spring Boot and Ktor#520
zantvoort merged 4 commits into
mainfrom
feat/orm-template-factory

Conversation

@zantvoort

@zantvoort zantvoort commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Motivation

A default application should not need Storm's core SPI. The starters auto-configure one ORMTemplate for a single-candidate DataSource; an application that defines its own template beans — for several data sources, or a single one whose composition it customizes — backed that off and hand-composed everything, importing ExceptionMapper, QueryObserver and SqlCommenter to wire by hand exactly what the starter wires from properties. The Ktor plugin had the mirrored gaps: OTel semantic conventions required constructing and registering a convention instance (one product for every database), and applying a template decorator meant composing a second template outside the plugin — which the plugin's repositories never used.

Spring Boot: OrmTemplateFactory

@Bean
fun orderTemplate(factory: OrmTemplateFactory) = factory.create(orderDataSource, "orders") {
    decorator { it.withTableNameResolver(...) }
}

Each created template carries the property-driven integration of the auto-configured one: Spring connection/transaction providers, SQL failure translation per storm.exception-translation.enabled with the mapper created against the template's own data source, observations per storm.observations.* with conventions resolved from the template's own JDBC URL and the template named as the low-cardinality storm.database key value, and the trace commenter per storm.tracing.sql-comments. The trailing block applies application-specific composition after the integration; the single-candidate condition moves from the auto-configuration class to the template bean, so the factory exists exactly where the single template backs off.

Ktor: configuration parity

  • storm.observations.semanticConventions = otel selects the OTel conventions from configuration, resolved per database from its own data source, overridable under storm.databases.<name>.observations.semanticConventions, validated at installation. A convention registered in the dependency container keeps taking precedence.
  • A customize slot on the plugin and per database block applies composition such as a template decorator after the integration is wired, inheriting the plugin-level block unless the database sets its own.

Shared composition

Observer composition lives in the new QueryObservers in storm-micrometer; storm-spring's StormQueryObservers and the Ktor plugin both delegate to it, so the stacks resolve conventions and the storm.database identity identically and cannot drift.

Documentation

spring-integration.md gains a Composing Your Own Templates section; ktor-integration.md's decorator section goes through customize and its observability section leads with the configuration key; configuration.md lists the Ktor key. All observation and factory docs describe storm.database as naming the template the observation came from, written from the composition's perspective rather than any application shape.

Testing

Both starters' StormAutoConfigurationTest cover the factory (single template backs off, distinct templates per data source, customize runs); StormObservabilityTest covers the configured conventions resolving from the database's own data source and customize composing after (and overriding) the wired integration. Full sweeps over storm-micrometer, storm-spring, storm-ktor, storm-ktor-test, both starters and the @DataStormTest parity slice pass.

…emplateFactory

The starters auto-configure one template for a single-candidate data
source; a multi-database application had to hand-compose its templates,
importing the core SPI to wire what the starter wires from properties.
The starters now expose an OrmTemplateFactory bean carrying the same
property-driven integration per created template: Spring-aware connection
and transaction providers, SQL failure translation with the mapper
created against each template's own data source, observations whose
semantic conventions resolve from each data source's JDBC URL and which
report the template's name as the storm.database key value, and the
trace-context SQL commenter. A customize block applies
application-specific composition on top.

Observer composition is shared through the new StormQueryObservers,
which the observation auto-configuration also builds the single
template's observer with. The single-candidate condition moves from
StormAutoConfiguration's class level to the template bean, so the
factory is available in applications where the single template backs
off.
…own template beans

Multi-database applications are the common case, but a single-data-source
application that customizes its template composition backs the
auto-configured template off just the same, and the factory keeps the
property-driven integration for it too.
…or the Ktor plugin

The Ktor plugin selects the OpenTelemetry database semantic conventions
from configuration: storm.observations.semanticConventions = otel
resolves the database product from each database's own data source, with
a per-database override under storm.databases.<name>. A convention
registered in the dependency container keeps taking precedence.
Previously a convention instance had to be registered in the container,
naming the product by hand and serving every database with one value.

A customize slot on the plugin and per database block applies
application-specific composition, such as a template decorator, after
the integration is wired; a database block inherits the plugin-level
block unless it sets its own. The decorator guidance in the Ktor
documentation composed a second template outside the plugin, which the
plugin's repositories never used; it now goes through the slot.

Observer composition is shared with the Spring Boot starters through
QueryObservers in storm-micrometer, so the stacks resolve conventions
and the storm.database identity the same way. The observation documents
describe the storm.database key value as naming the template the
observation came from, and the template factory's documentation reads
from the composition's perspective rather than the application shape's.
@zantvoort zantvoort changed the title feat: compose fully integrated templates per data source through OrmTemplateFactory feat: property-driven template composition for Spring Boot and Ktor Aug 21, 2026
@zantvoort
zantvoort merged commit 8cbfa8a into main Aug 21, 2026
10 checks passed
@zantvoort
zantvoort deleted the feat/orm-template-factory branch August 21, 2026 16:43
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.

1 participant