Skip to content
Job Contractstable

Async write and job contract

The rules for work moved off the request path: 202 means accepted, not applied; jobs carry identifiers, not hydrated models; every job is idempotent, retried with backoff, and visible when it dies.

Moving work off the request path trades immediacy for throughput, and that trade has a contract. This spec defines it: what an acknowledgement means, what a job is allowed to carry, how it behaves on retry, and how it fails loudly. Break any clause and you get stale reads, lost writes, or duplicated effects.

Contract

Acknowledgement semantics

A 202 means the work was accepted onto the queue, not that it has been applied. The client must not render the submitted value as settled truth; for data the user will act on, it confirms by reading back from the system of record. Fields whose wrongness is expensive are written synchronously, not queued.

ResponseMeansClient must
202 AcceptedEnqueued, not yet appliedShow pending; confirm via a read
200 + bodyApplied synchronouslyTrust the returned value
4xxRejected, never enqueuedSurface the error
What each acknowledgement guarantees

Definition

Job payload rule

A job carries the identifiers it needs to find its data, never a hydrated model. It re-loads current state at execution time, so it acts on what is true when it runs — not on a snapshot frozen at dispatch, which under backlog may be minutes stale. Re-loading also forces the job to handle a record that was deleted in between.

IDs in the constructor; re-hydrate in handle()
class SendInvoice implements ShouldQueue
{
    public function __construct(public int $tenantId, public int $invoiceId) {}

    public function handle(): void
    {
        $invoice = Invoice::find($this->invoiceId);   // current state
        if ($invoice === null) return;                // superseded — don't act
        Mail::to($invoice->customer)->queue(new InvoiceIssued($invoice));
    }
}

Contract

Idempotency and retry

Queues deliver at least once, so every job must be safe to run more than once — idempotent on a stable key. Transient failures retry with exponential backoff; they never retry immediately into a struggling dependency.

PropertyRule
IdempotencyEffect keyed on a stable ID; re-run = no-op
RetriesBounded (`tries`), with exponential backoff
OrderingNot assumed; jobs tolerate out-of-order delivery
Poison jobsLand in the failed store after max tries
Retry contract

Definition

Dead-letter and observability

A job that exhausts its retries is recorded in a failed-jobs store and pages someone — a silently vanished job is worse than a loudly failed one. A replay path keyed on the job's identity lets you re-drive it safely once fixed, because idempotency makes replay a no-op if it already succeeded.

Invariants this spec guarantees

  • A 202 never causes the client to assert a write as applied; critical values are confirmed by a read or written synchronously.
  • Jobs carry identifiers and re-load current state; they never act on a snapshot frozen at dispatch.
  • Every job is idempotent on a stable key and safe under at-least-once delivery.
  • A job that exhausts its retries is visible and alertable — never silently lost.

Want this specified for your system?

We turn definitions like these into the actual schema, policies, and contracts your system runs on. Fixed scope, fixed price, defined delivery date.

Request a Fixed-Scope Architecture Blueprint