Skip to content

feat: MessageDeliverer.deliverBatch interface + default impl (KOJAK-72)#35

Merged
endrju19 merged 1 commit intomainfrom
feature/kojak-72-deliver-batch-interface
May 4, 2026
Merged

feat: MessageDeliverer.deliverBatch interface + default impl (KOJAK-72)#35
endrju19 merged 1 commit intomainfrom
feature/kojak-72-deliver-batch-interface

Conversation

@endrju19
Copy link
Copy Markdown
Collaborator

Summary

Adds a batch-aware method to MessageDeliverer that transports can override for concurrent I/O. Pure interface introduction with sequential default implementation — zero behavior change today, but unlocks the next two PRs in the optimization chain.

KOJAK-72 — deliverBatch interface

Closes KOJAK-71processNext returns Int was folded in here since the same refactor naturally exposes the count.

Changes

MessageDeliverer.deliverBatch(entries)

Default sequential implementation — loops over single-entry deliver(), preserves input order and per-entry result classification. Backward compatible: existing implementations don't need to change.

CompositeMessageDeliverer.deliverBatch

Groups entries by deliveryType, delegates each sub-batch to the matching deliverer's deliverBatch. Fails permanently for unknown types (consistent with single-entry deliver). Results re-assembled in original input order.

OutboxEntryProcessor.processBatch(entries)

New method — calls deliverer.deliverBatch once, applies retry policy per result. Existing process(entry) preserved for single-entry callers (deduplicated via shared applyResult helper).

OutboxProcessor.processNext

  • Switches from per-entry forEach { entryProcessor.process(...) } to a single processBatch(claimed) call
  • Returns Int (count of processed entries) — subsumes KOJAK-71

Semantic note: per-entry duration in batch path

OutboxProcessingEvent.duration semantics change in the batch path: each entry receives the wall-clock duration of the whole batch, not its individual delivery. This is intentional — transports overriding deliverBatch (Kafka fire-flush-await, parallel HTTP sendAsync) overlap per-entry I/O internally, making per-entry timing meaningless.

MicrometerOutboxListener (KOJAK-44) is unaffected — it uses onBatchProcessed for batch timing and only counts per-outcome events.

Documented in OutboxProcessor KDoc.

Verification

  • All unit tests pass (./gradlew test -x :okapi-integration-tests:test)
  • All integration tests pass (./gradlew :okapi-integration-tests:test — Postgres/MySQL/Kafka/HTTP via Testcontainers)
  • ktlint clean
  • Smoke benchmark — Kafka throughput, batchSize=50:
    • Baseline (KOJAK-68): 8.665 ms/op (~115 msg/s)
    • This PR: 8.321 ms/op (~120 msg/s)
    • Difference within measurement noise → zero regression confirmed

What's next

This PR introduces the interface. The actual throughput improvements come when transports override deliverBatch:

  • KOJAK-73: KafkaMessageDeliverer.deliverBatch — fire-flush-await pattern, expected 10-100× Kafka throughput improvement
  • KOJAK-74: HttpMessageDeliverer.deliverBatch — parallel httpClient.sendAsync, expected 10-50× HTTP throughput at realistic webhook latency

Test plan

  • New unit tests: MessageDelivererTest (default deliverBatch behavior), CompositeMessageDelivererTest (grouping + permanent failure for unknown types + override usage)
  • Extended OutboxEntryProcessorTest with processBatch cases (mixed results, empty input)
  • Extended OutboxProcessorTest with return-value assertions

Adds a batch-aware method to MessageDeliverer that transports can override
for concurrent I/O. Backward compatible via default implementation that
loops over single-entry deliver().

Changes:

- MessageDeliverer.deliverBatch(entries) — default sequential implementation
  preserves input order and per-entry result classification.

- CompositeMessageDeliverer.deliverBatch — groups entries by deliveryType,
  delegates each sub-batch to the matching deliverer's deliverBatch, fails
  permanently for unknown types (consistent with single-entry deliver).

- OutboxEntryProcessor.processBatch(entries) — calls deliverer.deliverBatch
  once, applies retry policy per result, returns processed entries in input
  order. Existing process(entry) preserved for callers.

- OutboxProcessor.processNext switches from per-entry forEach to a single
  processBatch call, AND now returns Int (count of processed entries).
  This subsumes KOJAK-71 — the count is exposed naturally as part of this
  refactor.

Per-entry duration semantics in OutboxProcessingEvent change in batch path:
each entry receives the wall-clock duration of the whole batch (because
transports may overlap per-entry I/O internally — Kafka's record batching,
parallel HTTP sendAsync — making per-entry timing meaningless). Documented
in KDoc. MicrometerOutboxListener (KOJAK-44) is unaffected — it uses
onBatchProcessed for batch timing and only counts per-outcome events.

No throughput change expected (default impl is identical sequential loop).
Smoke benchmark on Kafka batchSize=50: 8.321 ms/op vs baseline 8.665 ms/op
— within measurement noise. Real throughput gain comes when transports
override deliverBatch (KOJAK-73 Kafka, KOJAK-74 HTTP).

Closes KOJAK-71 (processNext returns Int — folded in here as the same
refactor naturally exposes the count).
@endrju19 endrju19 merged commit c8c1ed5 into main May 4, 2026
8 checks passed
@endrju19 endrju19 deleted the feature/kojak-72-deliver-batch-interface branch May 4, 2026 09:28
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