Interface OutboxStore
- All Known Implementing Classes:
InMemoryOutboxStore
Outbox writer fills and an OutboxRelay drains.
The whole point of the pattern is to remove the dual write a plain producer makes: "commit the business row" and "publish to the broker" are two systems that can disagree on a crash. Instead the message is written into the same database, inside the same transaction as the business data — so it commits or rolls back atomically with it — and a separate relay publishes it afterwards. No distributed transaction, exactly-once handoff into the broker (then at-least-once on the wire, as always).
The transaction boundary is the CALLER'S. The core does not open, commit or roll
back anything: save(byte[], String) is invoked from inside a transaction
the caller already began (around its own INSERT INTO orders …), and the caller
commits both together. This keeps the core free of any DB driver (GR-7): the core defines
this contract; a concrete adapter (e.g. a JDBC one) binds it to a real connection. The
reference InMemoryOutboxStore is for tests and single-process demos.
The stored value is the frozen wire envelope, byte-for-byte unchanged (GR-1): the
EnvelopeCodec.encode(com.babelqueue.Envelope) output as UTF-8 bytes.
The outbox adds its own bookkeeping columns (id, status, attempts, timestamps) around
the envelope; it never adds a field to it. What the relay publishes is the same bytes
that were stored — trace_id is preserved end-to-end (GR-4), byte-compatible (GR-5).
-
Method Summary
Modifier and TypeMethodDescriptionfetchUnpublished(int limit) Reserve up tolimitrows that are pending publish, oldest first, so a relay can forward them.voidmarkFailed(String id, String error) Record a failed publish attempt for one row: increment its attempt counter and store the last error, leaving it pending so a later relay pass retries it (at-least-once).voidmarkPublished(List<String> ids) Mark the given outbox rows as successfully published (so they are never relayed again).Persist one encoded envelope into the outbox, within the transaction the caller has already opened around its business write.
-
Method Details
-
save
Persist one encoded envelope into the outbox, within the transaction the caller has already opened around its business write. Returns the new row's outbox id (the store's own primary key — NOTmeta.id), which the caller may keep for correlation. The body is stored verbatim; an implementation must not re-encode or mutate it.- Parameters:
encoded- theEnvelopeCodec.encode(com.babelqueue.Envelope)output as UTF-8 bytesqueue- the logical target queue, captured for the relay- Returns:
- the outbox row id
-
fetchUnpublished
Reserve up tolimitrows that are pending publish, oldest first, so a relay can forward them. Implementations SHOULD lock/claim the rows they return (e.g.SELECT … FOR UPDATE SKIP LOCKED, or apicked_atclaim) so two concurrent relays do not both publish the same row; at-least-once still tolerates a rare double send. That claim/lock is the adapter's responsibility — the in-memory reference does not implement it.- Parameters:
limit- maximum rows to return (a positive batch size)- Returns:
- pending rows, oldest first; empty when the outbox is drained
-
markPublished
Mark the given outbox rows as successfully published (so they are never relayed again). Called by the relay only after the transport accepted the message.- Parameters:
ids- outbox row ids previously returned byfetchUnpublished(int)
-
markFailed
Record a failed publish attempt for one row: increment its attempt counter and store the last error, leaving it pending so a later relay pass retries it (at-least-once). A store MAY move a row that exceeds a max-attempts threshold to a terminal/parked state, but that policy is the adapter's, not the core's.- Parameters:
id- the outbox row iderror- a short, human-readable failure reason (never secrets)
-