Skip to content

Latest commit

 

History

History
237 lines (201 loc) · 15.6 KB

CHANGELOG.md

File metadata and controls

237 lines (201 loc) · 15.6 KB

Changelog

All notable changes to this project are documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning. Until 1.0.0, breaking changes may appear in any release and are flagged with BREAKING below.

Added

  • x-outbox-id header on every delivery. Both KafkaMessageDeliverer and HttpMessageDeliverer now attach the entry's UUID under OutboxHeaders.OUTBOX_ID (okapi-core, so consumers can reference the name without depending on a transport module). The value is stable across retries of an entry, giving consumers something to deduplicate redeliveries on. okapi sets it last, so it overrides a header of the same name supplied via DeliveryInfo — replaced outright over HTTP; appended after yours in Kafka, whose headers are multi-valued, which is why consumers must read it with lastHeader(...). It does not deduplicate at the publish() level: two publish() calls for the same business event are two entries with two ids. Previously the README stated that okapi did not send the OutboxId; that is no longer the case, and the "Deduplicating on x-outbox-id" section documents the recipe and both caveats. (KOJAK-78)

Changed

  • CompositeMessageDeliverer.deliverBatch now dispatches transport groups concurrently — one virtual thread per group, with the last group run on the calling thread — instead of blocking on each group in turn. A batch spanning N transports costs ~max(Tᵢ) rather than sum(Tᵢ); a batch with a single delivery type takes a fast path that starts no thread and allocates no executor, so homogeneous workloads are unchanged. MessageDeliverer implementations must therefore be thread-safe and must not depend on caller thread-locals (MDC, TransactionSynchronizationManager, security context). The transaction scope is unchanged — the scheduler still wraps the whole OutboxProcessor.processNext cycle (claim, deliver, update) in one transaction, so it remains open across delivery — but a transport group dispatched to a virtual thread no longer inherits the caller's transaction-bound resources, so a deliverer that implicitly joined the outbox transaction (e.g. via Spring's DataSourceUtils) now gets a fresh connection. Deliverers that cannot satisfy that can restore the previous behaviour with the new TransportDispatch.SEQUENTIAL constructor argument, exposed in okapi-spring-boot as okapi.processor.transport-dispatch=sequential. For a heterogeneous batch, parallel dispatch also shortens how long the transaction stays open, from sum(Tᵢ) to ~max(Tᵢ). (KOJAK-81)
  • CompositeMessageDeliverer now upholds deliverBatch's "must not throw" contract even when a transport does not. A deliverer that throws, or that returns no result for some of its entries, previously propagated the exception (or an IllegalStateException from the result-assembly step) and aborted the whole batch; it now fails only its own entries, as RetriableFailure, leaving the other transports' results intact. Such entries are retried under the configured RetryPolicy rather than rolled back and re-claimed indefinitely. (KOJAK-81)

Changed (BREAKING)

  • OutboxProcessorProperties gained a transportDispatch constructor parameter (see the CompositeMessageDeliverer entry above). @JvmOverloads preserves the previous four-argument JVM constructor, so Java callers are unaffected, but adding a property to a Kotlin data class necessarily changes the generated copy/copy$default and default-argument constructor signatures. Kotlin code compiled against an earlier release and not recompiled will fail with NoSuchMethodError if it calls copy(...) on this class or constructs it using default arguments — recompile consumers against this release. Binding from application.yml / application.properties is unaffected, as is CompositeMessageDeliverer, whose new parameter is covered by @JvmOverloads for previously compiled Kotlin and Java callers alike. (#113)
  • ExposedConnectionProvider now requires a database: Database constructor argument and reads the active transaction from database.transactionManager.currentOrNull() instead of the global TransactionManager.currentOrNull(). Previously, in a multi-database Exposed app, the provider would silently return whichever transaction happened to be innermost-active on the calling thread — regardless of which Database it belonged to — since every OutboxStore/OutboxPublisher operation routes through withConnection. Construct one instance per Database, matching ExposedTransactionRunner and ExposedTransactionContextValidator. (#96)

1.0.0 — 2026-07-28

First stable release. The public API now follows semantic versioning — breaking changes will only ship in a new major version.

Added

  • OutboxSchedulerConfig.concurrency — fans out each scheduler tick to N parallel workers via a configurable workerExecutorFactory (fixed platform-thread pool by default; a virtual-thread factory is also provided). Each worker claims its own disjoint batch via FOR UPDATE SKIP LOCKED, so no app-level coordination is needed, and ticks never overlap. concurrency = 1 (default) preserves the original single-worker, zero-overhead behavior. Benchmarked at 3.6×–6.6× throughput scaling (concurrency 4→64); virtual threads showed no advantage over platform threads in that range. Wired into okapi-spring-boot via okapi.processor.concurrency. (#73)
  • HttpMessageDeliverer.deliverBatch now fires all requests concurrently via HttpClient.sendAsync() instead of blocking sequentially on HttpClient.send() per entry — 5×–15× throughput improvement depending on batch size and webhook latency. (#77)
  • KafkaMessageDeliverer.deliverBatch — fire-flush-await pattern replaces N sequential blocking producer.send().get() round-trips with one batched producer.flush() — 13×–41× throughput improvement. (#40)
  • OutboxStore.updateAfterProcessingBatch(entries) — batches a processed batch's DB write into a single JDBC executeBatch() call instead of N individual updateAfterProcessing() round-trips (~10× faster in isolation). Default implementation loops the existing per-entry method, so custom OutboxStore implementations keep working unmodified; PostgresOutboxStore / MysqlOutboxStore override it. (#71)

Fixed

  • okapi-spring-boot startup crash with 2+ PlatformTransactionManager beans. OkapiMicrometerAutoConfiguration now honours okapi.transaction-manager-qualifier and falls back gracefully (metrics run without a read-only snapshot transaction) instead of throwing NoUniqueBeanDefinitionException, matching how OutboxAutoConfiguration already resolved the PTM. (#81, #80)
  • OutboxProcessor constructor missing @JvmOverloads — Java callers could no longer omit the listener/clock parameters and had to pass all four explicitly; restored. (#75, #74)

0.3.0 — 2026-06-08

Changed (BREAKING)

  • Domain table renamed outbox → okapi_outbox (indexes idx_outbox_* → idx_okapi_outbox_*). okapi now owns a prefixed table, so a pre-existing outbox no longer collides. Not configurable. (#37)
  • Liquibase tracking tables default to okapi_databasechangelog / okapi_databasechangeloglock instead of sharing the app's defaults; override via the new properties to keep the old layout. (#37)
  • okapi's Liquibase migrations consolidated into a single 001__create_okapi_outbox_table.sql per database. Resulting schema is unchanged, but the outbox:001 checksum changed — upgraders must start on a fresh okapi schema or clear okapi's rows from okapi_databasechangelog. (#50)
  • OutboxScheduler / OutboxPurger (okapi-core) now require a non-null TransactionRunner. The old nullable default silently ran non-transactionally, letting FOR UPDATE SKIP LOCKED drop its lock under JDBC auto-commit and deliver entries more than once. Spring Boot users unaffected; direct users (Ktor, manual wiring, Java/Kotlin) must supply one. (#51)
  • OutboxProcessorScheduler / OutboxPurgerScheduler now require a non-null TransactionRunner (was a nullable TransactionTemplate?). Spring autoconfig derives it from any PlatformTransactionManager; direct constructor users must pass SpringTransactionRunner(template) or a thin wrapper. (#49)
  • PostgresOutboxStore / MysqlOutboxStore no longer take a clock parameter — it became unused after the lag-gauge fix (#58). Drop the second constructor argument; Spring Boot users unaffected. (#59)
  • okapi-spring-boot autoconfig fails fast when it cannot verify the PlatformTransactionManager↔outbox-DataSource binding in a multi-DataSource context with no okapi.transaction-manager-qualifier set. Name the PTM via that qualifier, or supply an explicit @Bean TransactionRunner to bypass. (#49)

Added

  • MessageDeliverer.deliverBatch(entries) — batch-aware delivery method with a sequential default impl (loops deliver(), preserving order and per-entry result classification). Existing deliverers need no change; transports can override it for concurrent I/O, and CompositeMessageDeliverer routes batches by delivery type. (#35)
  • okapi.liquibase.changelog-table / okapi.liquibase.changelog-lock-table — Spring Boot properties to override okapi's Liquibase tracking-table names (defaults okapi_databasechangelog / okapi_databasechangeloglock).

Fixed

  • HTTP delivery exception classification. HttpMessageDeliverer previously caught every exception as RetriableFailure, so corrupt delivery metadata or an unknown service wasted the whole retry budget before being marked FAILED instead of failing fast. JsonProcessingException and other non-IO errors (malformed URI, unknown service) are now PermanentFailure; IOException / InterruptedException stay retriable. (#44)
  • okapi.transaction-manager-qualifier is now honoured even when TransactionAutoConfiguration registers a unique TransactionTemplate. Previously the qualifier was silently ignored in multi-PTM setups, defaulting to the @Primary PTM. Rule is now: explicit qualifier > auto-wired TT. (#49)
  • okapi-kafka now exposes kafka-clients and okapi-core as api dependencies. KafkaMessageDeliverer's public constructor takes Producer<String, String>, so those types belong on the consumer's compile classpath transitively — no more adding kafka-clients by hand or hitting surprising okapi-core classpath failures. (#47)
  • Startup NoClassDefFoundError on Spring Boot 3.5.x without liquibase-core (e.g. Flyway-only apps) — okapi's Liquibase beans are now guarded by class-level @ConditionalOnClass(SpringLiquibase). Also stops okapi's SpringLiquibase bean from shadowing the host application's own changelog — okapi's auto-config is now ordered after Spring Boot's LiquibaseAutoConfiguration. (#42, #38)
  • okapi-micrometer auto-config ordering on Spring Boot 3.5.x. @AutoConfigureAfter now lists both the 3.5.x and 4.0.x metrics-package locations, so the listener / metrics / refresher are no longer silently skipped when MeterRegistry registers later. (#41)
  • OutboxPurger error log preserves partial-batch progress. A mid-loop failure now reports how many entries / batches were already purged this tick, so operators can tell an early outage from a late transient hiccup. (#55)

Migration from 0.2.x

Existing deployments upgrading directly from 0.2.x to 1.0.0 must complete the steps in UPGRADING.md before starting the new version.

0.2.0 — 2026-04-29

Added

  • Observability: OutboxProcessorListener API and the okapi-micrometer module (counters, timers, gauges; Spring Boot Actuator integration). (#27)
  • Multi-datasource transaction validation in okapi-spring-boot (SpringTransactionContextValidator, okapi.datasource-qualifier property). (#17)
  • @JvmOverloads / @JvmStatic annotations across the public API for Java interop. (#24)
  • Maven Central release pipeline. (#18)

Changed

  • OutboxStore migrated from JetBrains Exposed to plain JDBC in okapi-postgres and okapi-mysql. The Exposed-based path remains available via the optional okapi-exposed module. (#26)
  • Configuration unification: Duration types throughout, dedicated OutboxPurgerConfig and OutboxSchedulerConfig. (#16)
  • OutboxProcessorScheduler and OutboxPurger v2 — configurable interval, batch size, retention; reliable shutdown via SmartLifecycle. (#11, #14)

Fixed

  • Actionable error message in ExposedConnectionProvider when no transaction is bound to the current thread. (#32)
  • okapi-micrometer artifact published to Maven Central; the okapi.metrics.refresh-interval property documented. (#29)

0.1.0 — 2026-04-07

Initial public release.

Added

  • Transactional outbox pattern for Kotlin/JVM with PostgreSQL and MySQL stores.
  • okapi-http and okapi-kafka deliverers; pluggable MessageDeliverer API.
  • OutboxProcessor with configurable RetryPolicy and delivery-result classification (Success / RetriableFailure / PermanentFailure).
  • okapi-spring-boot autoconfiguration for stores, transports, scheduler, and purger.
  • okapi-exposed integration (transaction runner, connection provider, validator).
  • Concurrent processing via FOR UPDATE SKIP LOCKED.