Class OutboxAutoConfiguration
Activated when im2be.outbox.enabled=true AND
im2be.outbox.backend is PG (the default). The
enabled default is false — every consuming service opts in
explicitly per ADR-0014 §library-governance.
Backend gate (Wave A.2 — resolves the Phase 0a B2 deferred item).
This is the Postgres auto-configuration: it wires JPA-backed beans
(OutboxRecordRepository, OutboxPublishCompletion, a ShedLock
JDBC LockProvider) that require a DataSource + spring-jdbc on
the classpath. A Redis-only consumer (identity-service:
im2be.outbox.backend=REDIS) has neither, so this whole graph must NOT
activate for it — otherwise condition evaluation fails on the absent
JdbcTemplateLockProvider. The OnPgOutboxBackendCondition gates
the entire PG graph on enabled + backend == PG, compared
case-INSENSITIVELY (parity with @ConditionalOnProperty's
havingValue + Spring's relaxed enum binding, so enabled=TRUE
and a lower-case backend: pg still match; reviewer R1) by reading the
Environment directly in Java — NOT via a
@ConditionalOnExpression with ${…} interpolation, which embeds
the raw property value into the parsed SpEL string and is a SpEL-injection
surface (rule 1; reviewer R2 — full rationale in that condition's Javadoc).
The backend default is PG, so the 7 PG services with
enabled=true and no explicit backend are unchanged; the Redis
backend's own auto-configuration
(RedisOutboxAutoConfiguration, Phase 0b) activates on
backend=REDIS and supplies the OutboxBackend bean instead.
Wires:
OutboxRecord+OutboxRecordRepositoryvia theOutboxAutoConfigurationPackagesRegistrar(additive entity + repository-package registration that does NOT clobber the consumer's@SpringBootApplication-derived scan). Spring Boot'sJpaRepositoriesAutoConfigurationscans the packages registered throughAutoConfigurationPackages— both the consumer's own packages and the outbox library's package — so repositories on both sides are picked up without the library holding@EnableJpaRepositories(which would register aJpaRepositoryFactoryBeanbean and disable the consumer's autoconfig via its@ConditionalOnMissingBeanguard, leaving consumer repositories unscanned — R7 finding).OutboxBackend— storage SPI (Wave A.2 Phase 0a); the defaultPgOutboxBackendwraps the JPA repository + theREQUIRES_NEWcompletion helper. Selected byim2be.outbox.backend(defaultPG; only PG exists in Phase 0a).@ConditionalOnMissingBeanso a consumer can supply an alternative without re-declaring the publisher/poller.OutboxPublisher— hot AFTER_COMMIT relay (delegates storage to theOutboxBackend).OutboxPollerWorker— cold scheduled poller (conditional onim2be.outbox.poller.enabled=true, default true; delegates storage to theOutboxBackend).OutboxPublishCompletion— REQUIRES_NEW transactional helper (thePgOutboxBackend's transition collaborator).OutboxMetricsBinder— Micrometer gauges + per-topic timers/counters (no-op when noMeterRegistrybean is present, so consumers without Actuator can still bootstrap).- Resilience4j
CircuitBreakerRegistrywith theim2be-outbox-relaycircuit-breaker pre-registered. - ShedLock
LockProviderbacked by the application'sDataSource(JDBC template provider).
Schedules (@EnableScheduling) and distributed locking
(@EnableSchedulerLock) are activated only when the autoconfig is
loaded — so consumers that wire ShedLock for other purposes still need to
provide their own @EnableSchedulerLock when the outbox is
disabled.
ShedLock default: @EnableSchedulerLock is required by
ShedLock 6.x to carry defaultLockAtMostFor. The library binds
the attribute to ${im2be.outbox.scheduler-lock.default-at-most:PT1M}
via Spring's annotation-value property-placeholder resolution — consumers
that don't want the library's PT1M default leaking into their own
@SchedulerLock-annotated methods can override
im2be.outbox.scheduler-lock.default-at-most in their own
properties (or set per-method lockAtMostFor on each
@SchedulerLock; ShedLock prefers the per-method value over the
default). The library's own
OutboxPollerWorker.pollAndPublishPending() sets
lockAtMostFor + lockAtLeastFor explicitly via property
placeholders, so it never relies on the default (R8 fix; previous
hard-coded "PT1M" was unoverridable).
Registered via
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionoutboxBackend(OutboxRecordRepository repository, OutboxPublishCompletion completion, OutboxProperties properties) DefaultOutboxBackend— the Postgres backend (Wave A.2 Phase 0a).io.github.resilience4j.circuitbreaker.CircuitBreakeroutboxCircuitBreaker(OutboxProperties properties, io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry registry) The outbox-relayCircuitBreakerwith thresholds explicitly built fromOutboxProperties.getCircuitBreaker().io.github.resilience4j.circuitbreaker.CircuitBreakerRegistryDefaultCircuitBreakerRegistryfor consumers that haven't declared one.net.javacrumbs.shedlock.core.LockProvideroutboxLockProvider(DataSource dataSource) ShedLock provider backed by the applicationDataSource.outboxMetricsBinder(org.springframework.beans.factory.ObjectProvider<io.micrometer.core.instrument.MeterRegistry> meterRegistry, OutboxRecordRepository repository) Metrics binder.io.opentelemetry.api.OpenTelemetryFalls back to the OpenTelemetry no-op SDK when no realOpenTelemetrybean is present — ensures the autoconfig boots in test contexts and dev environments that haven't wired OTel yet, without compromising the production observability story.outboxPollerWorker(OutboxBackend backend, org.springframework.boot.autoconfigure.kafka.KafkaProperties kafkaProperties, org.springframework.beans.factory.ObjectProvider<org.springframework.boot.ssl.SslBundles> sslBundles, OutboxProperties properties, io.opentelemetry.api.OpenTelemetry openTelemetry, OutboxMetricsBinder metrics) Cold poller bean.outboxPublishCompletion(OutboxRecordRepository repository) REQUIRES_NEW completion helper.outboxPublisher(OutboxBackend backend, org.springframework.boot.autoconfigure.kafka.KafkaProperties kafkaProperties, org.springframework.beans.factory.ObjectProvider<org.springframework.boot.ssl.SslBundles> sslBundles, io.github.resilience4j.circuitbreaker.CircuitBreaker outboxCircuitBreaker, io.opentelemetry.api.OpenTelemetry openTelemetry, OutboxMetricsBinder metrics, OutboxProperties properties) AFTER_COMMIT hot relay bean.
-
Constructor Details
-
OutboxAutoConfiguration
public OutboxAutoConfiguration()
-
-
Method Details
-
outboxCircuitBreakerRegistry
@Bean @ConditionalOnMissingBean public io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry outboxCircuitBreakerRegistry()DefaultCircuitBreakerRegistryfor consumers that haven't declared one. VanillaofDefaults()— the library does NOT impose its own thresholds on the registry default, because the registry-default config would silently leak into every OTHER breaker a consumer registers in the same registry. The outbox-specific thresholds live onoutboxCircuitBreaker(OutboxProperties, CircuitBreakerRegistry)which uses an EXPLICITCircuitBreakerConfigat breaker-creation time so theim2be.outbox.circuit-breaker.*properties are honoured regardless of which registry is in context. -
outboxCircuitBreaker
@Bean @ConditionalOnMissingBean(name="outboxCircuitBreaker") public io.github.resilience4j.circuitbreaker.CircuitBreaker outboxCircuitBreaker(OutboxProperties properties, io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry registry) The outbox-relayCircuitBreakerwith thresholds explicitly built fromOutboxProperties.getCircuitBreaker(). R4 fix — previous wiring letoutboxPublisherpull the breaker from theCircuitBreakerRegistryvia name lookup, which silently used the registry's default config when a consumer-provided registry shadowed the library's. By creating the breaker here with an explicitCircuitBreakerConfig, theim2be.outbox.circuit-breaker.*properties are guaranteed to take effect regardless of which registry the consumer wired.- Parameters:
properties- source offailure-rate-threshold+wait-duration-in-open-state-msregistry- the registry the breaker is registered into; usesCircuitBreakerRegistry.circuitBreaker(String, CircuitBreakerConfig)so the registered breaker carries the explicit config rather than the registry's default
-
outboxLockProvider
@Bean @ConditionalOnMissingBean public net.javacrumbs.shedlock.core.LockProvider outboxLockProvider(DataSource dataSource) ShedLock provider backed by the applicationDataSource. Required byEnableSchedulerLock; the consuming service is expected to provide ashedlocktable (the library does NOT ship the migration; consuming services author their own). -
outboxOpenTelemetryFallback
@Bean @ConditionalOnMissingBean public io.opentelemetry.api.OpenTelemetry outboxOpenTelemetryFallback()Falls back to the OpenTelemetry no-op SDK when no realOpenTelemetrybean is present — ensures the autoconfig boots in test contexts and dev environments that haven't wired OTel yet, without compromising the production observability story. -
outboxMetricsBinder
@Bean public OutboxMetricsBinder outboxMetricsBinder(org.springframework.beans.factory.ObjectProvider<io.micrometer.core.instrument.MeterRegistry> meterRegistry, OutboxRecordRepository repository) Metrics binder. Registers the two gauges at startup when aMeterRegistrybean is present; otherwise becomes a no-op so consumers without Spring Boot Actuator (or Micrometer on the classpath) can still bootstrap. The publisher and poller both depend on this bean unconditionally; the no-op fallback keeps that wiring intact. -
outboxPublishCompletion
REQUIRES_NEW completion helper. Distinct bean so the Spring transaction proxy intercepts each call (self-invocation would bypass the proxy). ThePgOutboxBackend's transition collaborator.TODO(Phase 0b): this bean — together with
OutboxRecordRepositoryand thePG backend— is JPA-specific. When the Redis backend module lands, the entire PG bean graph (repository registration, this helper, and the PGOutboxBackend) must become conditional on the selected backend (e.g.@ConditionalOnProperty im2be.outbox.backend=PGor JPA-classpath presence) so a Redis-only consumer never triggers JPA wiring. Deferred to Phase 0b per the design (backend-selection wiring is explicitly Phase 0b scope — reviewer R1 B2). -
outboxBackend
@Bean @ConditionalOnMissingBean public OutboxBackend outboxBackend(OutboxRecordRepository repository, OutboxPublishCompletion completion, OutboxProperties properties) DefaultOutboxBackend— the Postgres backend (Wave A.2 Phase 0a). Wraps the JPAOutboxRecordRepository(persist + FIFO batch fetch) and theOutboxPublishCompletionREQUIRES_NEWhelper (the status-guarded transitions). Selected unconditionally for now —OutboxProperties.getBackend()defaults toOutboxProperties.Backend.PGand only PG exists in Phase 0a; the Redis backend (Phase 0b) plugs in via the same SPI in a separate module.@ConditionalOnMissingBeanlets a consumer override the backend (e.g. a test fake or the future Redis impl) without re-declaring the publisher + poller wiring.A non-PG value for
im2be.outbox.backendis honoured by Spring's relaxed enum binding but has no implementation in Phase 0a; rather than fail silently (reviewer R1 A1), a startupWARNrecords the fallback toPgOutboxBackendso the misconfiguration is visible in logs. The actual backend switch lands in Phase 0b. -
outboxPublisher
@Bean public OutboxPublisher outboxPublisher(OutboxBackend backend, org.springframework.boot.autoconfigure.kafka.KafkaProperties kafkaProperties, org.springframework.beans.factory.ObjectProvider<org.springframework.boot.ssl.SslBundles> sslBundles, io.github.resilience4j.circuitbreaker.CircuitBreaker outboxCircuitBreaker, io.opentelemetry.api.OpenTelemetry openTelemetry, OutboxMetricsBinder metrics, OutboxProperties properties) AFTER_COMMIT hot relay bean. Wraps the Kafka send in theOutboxPublisher.CIRCUIT_BREAKER_NAMEcircuit-breaker. The breaker is injected by type so its config is the explicit one built inoutboxCircuitBreaker(OutboxProperties, CircuitBreakerRegistry)(R4 fix — see that method's Javadoc). Storage is delegated to theOutboxBackendSPI (Wave A.2 Phase 0a) — the defaultPgOutboxBackendpreserves the prior PG behaviour exactly. -
outboxPollerWorker
@Bean @ConditionalOnProperty(prefix="im2be.outbox.poller", name="enabled", havingValue="true", matchIfMissing=true) public OutboxPollerWorker outboxPollerWorker(OutboxBackend backend, org.springframework.boot.autoconfigure.kafka.KafkaProperties kafkaProperties, org.springframework.beans.factory.ObjectProvider<org.springframework.boot.ssl.SslBundles> sslBundles, OutboxProperties properties, io.opentelemetry.api.OpenTelemetry openTelemetry, OutboxMetricsBinder metrics) Cold poller bean. Wires a@Scheduledsweep over PENDING rows. Conditional onim2be.outbox.poller.enabled=true(defaulttrue); consumers may disable to inspect PENDING rows by hand. Storage is delegated to theOutboxBackendSPI (Wave A.2 Phase 0a).
-