Package com.babelqueue.outbox
Class OutboxRelay
java.lang.Object
com.babelqueue.outbox.OutboxRelay
The read/publish side of the transactional outbox (ADR-0029): drain pending rows the
Outbox writer committed and forward each onto the broker through the OutboxTransport
seam, marking every row published or failed.
Run it on a short interval (a worker loop, a scheduled command) after the business transaction commits. Because the message was committed atomically with the business data, the relay is the only thing standing between "row exists" and "broker has it" — and it only ever reads already-durable rows, so it never invents work.
Semantics — at-least-once handoff:
- A row is marked published only after
OutboxTransport.publish(String, byte[])returns; if the process dies between publish andOutboxStore.markPublished(List), the row stays pending and is published again on the next pass. That is at-least-once: a downstream consumer must dedupe on the canonicalmeta.id(com.babelqueue.idempotency.Idempotentis exactly that guard, the consumer-side mirror of this producer-side helper — ADR-0022). - A publish that throws is caught,
OutboxStore.markFailed(String, String)records the error and bumps the attempt count, and the row stays pending for a later retry. One poison row never blocks the rest of the batch. trace_idis preserved end-to-end (GR-4): the relay publishes the stored bytes verbatim — it never decodes, rebuilds or re-encodes the envelope — so the body that reaches the broker is byte-identical to what was stored (GR-1/GR-5).
Backoff: between a failed publish and the next attempt within the same pass the relay
sleeps for a bounded, linearly-growing delay (capped), to avoid hammering a broker that is briefly
down. The OutboxRelay.Sleeper is injectable so tests stay instant.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interfaceSleeps for a number of milliseconds between a failed publish and the next attempt. -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intDefault upper bound on a single backoff sleep, in milliseconds.static final intDefault base backoff added per prior attempt, in milliseconds.static final intDefault number of rows reserved and published perflush().static final intHard safety ceiling ondrain(int)passes when the caller passes a non-positive value. -
Constructor Summary
ConstructorsConstructorDescriptionOutboxRelay(OutboxTransport transport, OutboxStore store) A relay with the default batch size and backoff budget, using a realThread.sleep(long).OutboxRelay(OutboxTransport transport, OutboxStore store, int batchSize, int backoffStepMs, int backoffCapMs, OutboxRelay.Sleeper sleeper) A fully configured relay. -
Method Summary
-
Field Details
-
DEFAULT_DRAIN_CEILING
public static final int DEFAULT_DRAIN_CEILINGHard safety ceiling ondrain(int)passes when the caller passes a non-positive value.- See Also:
-
DEFAULT_BATCH_SIZE
public static final int DEFAULT_BATCH_SIZEDefault number of rows reserved and published perflush().- See Also:
-
DEFAULT_BACKOFF_STEP_MS
public static final int DEFAULT_BACKOFF_STEP_MSDefault base backoff added per prior attempt, in milliseconds.- See Also:
-
DEFAULT_BACKOFF_CAP_MS
public static final int DEFAULT_BACKOFF_CAP_MSDefault upper bound on a single backoff sleep, in milliseconds.- See Also:
-
-
Constructor Details
-
OutboxRelay
A relay with the default batch size and backoff budget, using a realThread.sleep(long).- Parameters:
transport- where published rows go (the publish-only seam)store- the outbox to drain
-
OutboxRelay
public OutboxRelay(OutboxTransport transport, OutboxStore store, int batchSize, int backoffStepMs, int backoffCapMs, OutboxRelay.Sleeper sleeper) A fully configured relay.- Parameters:
transport- where published rows go (the same publish-only seam every framework-less producer uses)store- the outbox to drainbatchSize- how many rows to reserve and publish perflush()backoffStepMs- base backoff added per prior attempt, in millisecondsbackoffCapMs- upper bound on a single backoff sleep, in millisecondssleeper- sleeps the backoff between attempts;nullusesThread.sleep(long). Inject a no-op (or a recorder) in tests so they stay instant.
-
-
Method Details
-
flush
Publish one batch of pending rows. Each row the transport accepts is marked published; each that throws is marked failed (with a backoff before continuing) and left pending. Returns a per-pass tally. Call it repeatedly (a loop / cron) to drain the outbox;drain(int)loops until it is empty. -
drain
Drain the outbox by repeatedly callingflush()while each pass keeps making progress (publishes at least one row), then return the cumulative tally. The loop stops as soon as a pass publishes nothing — the outbox is empty, or only currently-failing rows remain (those are left pending for a futuredraincall once the broker recovers).maxPassesis a hard safety ceiling so a degenerate store can never spin forever (a non-positive value usesDEFAULT_DRAIN_CEILING).
-