Interface OutboxStore

All Known Implementing Classes:
InMemoryOutboxStore

public interface OutboxStore
The persistence seam for the transactional outbox (ADR-0029) — the durable "outbox" table that an 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 Type
    Method
    Description
    fetchUnpublished(int limit)
    Reserve up to limit rows that are pending publish, oldest first, so a relay can forward them.
    void
    markFailed(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).
    void
    Mark the given outbox rows as successfully published (so they are never relayed again).
    save(byte[] encoded, String queue)
    Persist one encoded envelope into the outbox, within the transaction the caller has already opened around its business write.
  • Method Details

    • save

      String save(byte[] encoded, String queue)
      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 — NOT meta.id), which the caller may keep for correlation. The body is stored verbatim; an implementation must not re-encode or mutate it.
      Parameters:
      encoded - the EnvelopeCodec.encode(com.babelqueue.Envelope) output as UTF-8 bytes
      queue - the logical target queue, captured for the relay
      Returns:
      the outbox row id
    • fetchUnpublished

      List<OutboxRecord> fetchUnpublished(int limit)
      Reserve up to limit rows 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 a picked_at claim) 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

      void markPublished(List<String> ids)
      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 by fetchUnpublished(int)
    • markFailed

      void markFailed(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). 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 id
      error - a short, human-readable failure reason (never secrets)