Skip to content

v1.1.2

Choose a tag to compare

@dmitriy-iliyov dmitriy-iliyov released this 28 Jul 15:31
· 50 commits to main since this release

The library has been renamed from spring-outbox to oncebox. This release
is the rename plus everything that landed after v1.1.1: three dialect modules
split out of the core, and a long list of correctness fixes - most of them found
by widening the test matrix rather than reported.

Important

oncebox-* are brand-new coordinates on Maven Central. They carry no 1.1.0 or
1.1.1 history there - 1.1.2 is the first release under this name, and the number
only continues the spring-outbox-* line for the sake of continuity. The old
spring-outbox-* artifacts stay published but receive no further updates.

Breaking changes

1. Coordinates, packages and property prefix. Artifacts are now
io.github.dmitriy-iliyov:oncebox-*, packages are
io.github.dmitriyiliyov.oncebox.* (previously ...springoutbox.*), and the
configuration prefix is oncebox.* (previously outbox.*).

Deliberately not renamed: database table names, metric names, message headers,
the DLQ REST path (/api/outbox-dlq/...) and internal class and bean names.
Those name the pattern, not the product, and renaming them would have forced a
data and wire-level migration for no benefit.

2. A database dialect module is now required. All dialect-specific
repositories moved out of oncebox-core and oncebox-dlq-api into
oncebox-postgresql, oncebox-mysql and oncebox-oracle. oncebox-starter
depends on all three as optional, so you must add exactly one yourself —
without it the library has no DAO implementation and the context fails to start.

3. Consumer cache abstractions moved into the core. ConsumedOutboxCache
and ConsumedOutboxCacheListener moved from
io.github.dmitriyiliyov.oncebox.consumer.cache to
...oncebox.core.consumer.cache. oncebox-consumer-cache now holds only the
Spring CacheManager adapter, keeping spring-context isolated to that one
optional module.

4. Jackson is no longer inherited from oncebox-core. Core declares
jackson-databind as optional, so modules built directly on top of core no
longer get Jackson transitively. Users of oncebox-starter are unaffected — it
brings spring-boot-starter-json, which also keeps jackson-datatype-jsr310 on
the classpath so Instant payloads serialize exactly as before.

Migration

<dependency>                                                                                                                                                                                                                                                                                                                                        
    <groupId>io.github.dmitriy-iliyov</groupId>                                                                                                                                                                                                                                                                                                     
    <artifactId>oncebox-starter</artifactId>                                                                                                                                                                                                                                                                                                        
    <version>1.1.2</version>                                                                                                                                                                                                                                                                                                                        
</dependency>                                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                                                    
<!-- new in 1.1.2: pick exactly one matching your database -->                                                                                                                                                                                                                                                                                      
<dependency>                                                                                                                                                                                                                                                                                                                                        
    <groupId>io.github.dmitriy-iliyov</groupId>                                                                                                                                                                                                                                                                                                     
    <artifactId>oncebox-postgresql</artifactId>                                                                                                                                                                                                                                                                                                     
    <version>1.1.2</version>                                                                                                                                                                                                                                                                                                                        
</dependency>                                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                                                    
Then rename the outbox.* configuration keys to oncebox.* and update imports                                                                                                                                                                                                                                                                         
from io.github.dmitriyiliyov.springoutbox.* to io.github.dmitriyiliyov.oncebox.*.

Fixes                                                                                                                                                                                                                                                                                                                                               
                                                                                                                                                                                                                                                                                                                                                    
- @OutboxPublish combined with @Transactional failed with                                                                                                                                                                                                                                                                                           
IllegalTransactionStateException. The aspect's explicit                                                                                                                                                                                                                                                                                             
@Order(LOWEST_PRECEDENCE - 1) placed it strictly outside the transactional                                                                                                                                                                                                                                                                          
advisor, so it ran after the commit. The explicit order is gone; the caveat and                                                                                                                                                                                                                                                                     
the manual OutboxPublisher#publish() fallback are documented in the javadoc                                                                                                                                                                                                                                                                         
and the README.                                                                                                                                                                                                                                                                                                                                     
- Oracle starved concurrent pollers. The dequeue query applied                                                                                                                                                                                                                                                                                      
FETCH FIRST ? ROWS ONLY in an inner subquery, before FOR UPDATE SKIP LOCKED — so a second poller found its whole candidate set already locked and                                                                                                                                                                                                   
came back with nothing. The limit now goes through PreparedStatement#setMaxRows,                                                                                                                                                                                                                                                                    
after the skip, and each poller gets a full batch of distinct rows.                                                                                                                                                                                                                                                                                 
- MySQL requires READ COMMITTED for concurrent pollers; this is now                                                                                                                                                                                                                                                                                 
documented. An outbox poller never needs repeatable reads, so it is safe.                                                                                                                                                                                                                                                                           
PostgreSQL and Oracle are unaffected.                                                                                                                                                                                                                                                                                                               
- DLQ saveBatch() bound the wrong column: updated_at received                                                                                                                                                                                                                                                                                       
getMovedAt() instead of getUpdatedAt(), in the shared abstract repository —                                                                                                                                                                                                                                                                         
so it affected all three dialects.                                                                                                                                                                                                                                                                                                                  
- The DLQ REST API crashed on a single-dialect classpath. Enabling                                                                                                                                                                                                                                                                                  
oncebox.publisher.dlq.enabled=true with only one dialect module present threw                                                                                                                                                                                                                                                                       
NoClassDefFoundError for the absent dialects' IdHelper: configuration-class                                                                                                                                                                                                                                                                         
introspection resolved those classes before the conditions could skip them. The                                                                                                                                                                                                                                                                     
dialect beans are now split into separate @ConditionalOnClass-guarded                                                                                                                                                                                                                                                                               
configurations, covered by a dedicated postgres-only classpath-isolation test.                                                                                                                                                                                                                                                                      
- Publisher gauge cache crashed the context on the documented                                                                                                                                                                                                                                                                                       
oncebox.publisher.metrics.gauge.cache.enabled=false, with                                                                                                                                                                                                                                                                                           
IndexOutOfBoundsException, and threw an NPE when enabled was left null. The                                                                                                                                                                                                                                                                         
DLQ path already guarded this correctly; the publisher now does the same.                                                                                                                                                                                                                                                                           
- The consumer cache listener went missing when metrics were unavailable,                                                                                                                                                                                                                                                                           
leaving the decorator without a listener.                                                                                                                                                                                                                                                                                                           
- Schema creation now follows table ownership. A consumer-only application got                                                                                                                                                                                                                                                                      
an outbox_events table it never writes to, and outbox_jobs appeared even with                                                                                                                                                                                                                                                                       
every clean-up disabled.                                                                                                                                                                                                                                                                                                                            
- lockAtLeastFor was measured from unlock() rather than from lock                                                                                                                                                                                                                                                                                   
acquisition, causing drift in clean-up polling intervals.                                                                                                                                                                                                                                                                                           
- DLQ API repositories switched to pessimistic locking.                                                                                                                                                                                                                                                                                             
- Fallback when spring.datasource.url is not specified; corrected                                                                                                                                                                                                                                                                                   
ScheduledExecutorService configuration with a graceful-shutdown hook; removed a                                                                                                                                                                                                                                                                     
duplicate OutboxPublisherMetricsAutoConfiguration and a state mutation in                                                                                                                                                                                                                                                                           
finalizeBatch().                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                    
Testing                                                                                                                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                                                                                                                                    
The e2e and infrastructure suites now run the full matrix - PostgreSQL, MySQL and                                                                                                                                                                                                                                                                   
Oracle against both Kafka and RabbitMQ - plus concurrent-poller contract tests for                                                                                                                                                                                                                                                                  
every dialect and a classpath-isolation module that boots the library with a single                                                                                                                                                                                                                                                                 
dialect on the classpath.