Skip to content

feat: OutboxProcessorScheduler — configurability, reliability, SmartLifecycle (KOJAK-62)#14

Merged
endrju19 merged 23 commits intomainfrom
process-scheduler
Apr 1, 2026
Merged

feat: OutboxProcessorScheduler — configurability, reliability, SmartLifecycle (KOJAK-62)#14
endrju19 merged 23 commits intomainfrom
process-scheduler

Conversation

@endrju19
Copy link
Copy Markdown
Collaborator

Summary

Apply the same production-ready pattern from KOJAK-56 (OutboxPurger v2) to OutboxScheduler and OutboxProcessorScheduler:

  • Fix silent death bug: OutboxScheduler.tick() had no try/catch — ScheduledExecutorService silently stops on uncaught exception. Added catch(Exception) with SLF4J error logging.
  • Add lifecycle guards: AtomicBoolean running flag prevents double-start, check(!scheduler.isShutdown) prevents restart-after-stop with a clear error message, isRunning() method.
  • Extract config value object: OutboxSchedulerConfig data class with require() validation in init block (make illegal states unrepresentable).
  • Migrate to SmartLifecycle: Replace SmartInitializingSingleton + DisposableBean with SmartLifecycle. Phase ordering: processor (MAX_VALUE - 2048) starts before purger (MAX_VALUE - 1024) and stops after it. stop(callback) with try-finally.
  • Spring properties binding: OutboxProcessorProperties with @ConfigurationProperties(prefix = "okapi.processor"), @Validated, @field:Min(1). @ConditionalOnProperty for enabled toggle.
  • Database index: (status, created_at) composite index for claimPending() query performance (Postgres + MySQL).
  • Configuration metadata: Processor properties added to spring-configuration-metadata.json.

Configuration

okapi:
  processor:
    enabled: true       # default
    interval-ms: 1000   # default
    batch-size: 10      # default

Commits (8)

Commit Scope
feat(core): add OutboxSchedulerConfig value object with validation okapi-core
feat(core): rewrite OutboxScheduler with error handling, guards, logging okapi-core
feat(spring): add OutboxProcessorProperties okapi-spring-boot
feat(spring): migrate OutboxProcessorScheduler to SmartLifecycle okapi-spring-boot
feat(spring): bind OutboxProcessorProperties in OutboxAutoConfiguration okapi-spring-boot
test(spring): add OutboxProcessorAutoConfigurationTest okapi-spring-boot
feat(db): add index (status, created_at) for processor claimPending query okapi-postgres, okapi-mysql
feat(spring): add processor properties to spring-configuration-metadata.json okapi-spring-boot

Test plan

  • OutboxSchedulerConfigTest — 6 tests (defaults, custom values, validation)
  • OutboxSchedulerTest — 7 tests (batchSize forwarding, error recovery, double-start, isRunning transitions, restart-after-stop, transactionRunner wrapping, no transactionRunner)
  • OutboxProcessorAutoConfigurationTest — 7 tests (bean creation, disabled toggle, properties binding, defaults, SmartLifecycle isRunning, validation, stop callback)
  • All existing tests pass (purger, publisher, E2E)
  • ./gradlew clean check — BUILD SUCCESSFUL
  • ./gradlew ktlintCheck — clean

Related

endrju19 added 22 commits March 26, 2026 14:28
…and return count

Breaking change: removeDeliveredBefore(Instant) -> removeDeliveredBefore(Instant, Int): Int
Store implementations temporarily ignore limit parameter.
…nding and conditional beans

Also fix nested PostgresStoreConfiguration to use proxyBeanMethods=false
(Kotlin classes are final; CGLIB proxying requires non-final classes)
and add assertj-core to version catalog (required by spring-boot-test's
ApplicationContextRunner).
The enabled flag is handled by @ConditionalOnProperty on the bean factory.
Having it also in the properties class was redundant — no code read it.
…, KDoc, test coverage

- OutboxPurger.start(): check(!scheduler.isShutdown) prevents restart after stop
- OutboxPurgerScheduler.stop(callback): try-finally guarantees callback invocation
- OutboxStore.removeDeliveredBefore: verbose KDoc with @param/@return contract
- OutboxAutoConfiguration.outboxStore(): restore concrete PostgresOutboxStore return type
- OutboxPurgerTest: assert batchSize is forwarded as limit, test start-after-stop
…ies, improve test assertions

- Rename OkapiPurgerProperties -> OutboxPurgerProperties (consistent with Outbox* naming convention)
- Add latch.await() shouldBe true assertions for better timeout diagnostics
- Add stop callback invocation test for SmartLifecycle contract
Add try/catch in tick() to prevent silent scheduler death on exception.
Add AtomicBoolean running guard and isShutdown restart check.
Accept OutboxSchedulerConfig instead of raw parameters.
Add SLF4J logging: INFO start/stop, DEBUG per tick, ERROR on failure.
Add isRunning() method.
Replace SmartInitializingSingleton + DisposableBean with SmartLifecycle.
Add stop(callback) with try-finally to prevent Spring shutdown hang.
Add phase ordering: processor (MAX_VALUE-2048) starts before purger
(MAX_VALUE-1024) and stops after it.
Accept OutboxSchedulerConfig instead of raw parameters.
Add @EnableConfigurationProperties for OutboxProcessorProperties.
Add @ConditionalOnProperty for okapi.processor.enabled toggle.
Map properties to OutboxSchedulerConfig in bean factory.
Test: bean creation, disabled toggle, properties binding, defaults,
SmartLifecycle isRunning, validation, stop callback safety.
…uery

Without this index, claimPending() does a full table scan on
SELECT ... WHERE status='PENDING' ORDER BY created_at.
Base automatically changed from feat102 to main April 1, 2026 08:09
- Changelogs: use upstream's *_claim_index names (same index, added independently)
- Remove duplicate *_processor_index migrations (identical to upstream's *_claim_index)
- PostgresOutboxStore: accept upstream's Exposed DSL refactor for claimPending
- AutoConfiguration: keep both purger + processor @EnableConfigurationProperties
- Metadata JSON: keep superset with processor properties
@endrju19 endrju19 merged commit c5d7ecc into main Apr 1, 2026
7 checks passed
@endrju19 endrju19 deleted the process-scheduler branch April 1, 2026 08:45
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.

2 participants