Choose a Pulsar Event delivery guarantee
UseCases emit immutable, versioned Domain Events; the generated Event dispatches after commit by default. That timing is not durable delivery, atomic broker enqueue, authorization, tenant isolation, invariant validation, or exactly-once effect execution.
Define a Domain Event
An Event is a final readonly fact about something that already happened. The generated stub implements ShouldDispatchAfterCommit, exposes VERSION = 1, and accepts only immutable IDs, scalars, DTOs, or Value Objects. Actions and Operations never emit Events or own transactions.
app/Pulsar/Domain/{Domain}/Events/{Name}.php — Generated after-commit Event interface.
final readonly class OrderPlaced implements ShouldDispatchAfterCommit
{
public const int VERSION = 1;
public function __construct(
// Immutable facts only — IDs, scalars, DTOs, Value Objects.
) {}
}Choose a delivery guarantee
| Tier | Transaction and crash window | Queue behavior | Use and prohibited claim |
|---|---|---|---|
| Synchronous | Runs in-process; inside a transaction it sees uncommitted state. A later rollback cannot undo its effect, and a crash interrupts it. | No broker, retry, latency isolation, durable record, or delivery-order contract. | Use only a pure local or reversible reaction. Never call it after-commit, durable, or retryable. |
| After-commit | The generated default runs after the current commit, immediately with no transaction, and rollback discards it. A crash after commit before the in-memory callback loses it. | No enqueue, retry, isolated latency, durable handoff, or ordering guarantee. | Use for a committed fact whose loss is acceptable or reconciled. Never call it durable, atomic with the write, or exactly-once effect execution. |
| Queued after-commit | Rollback prevents deferred enqueue. Commit followed by callback or enqueue failure leaves a write-to-broker gap. | A worker is asynchronous and at-least-once; retries and interruption can repeat handling. No universal worker order or durable external effect follows. | Use for expensive, external, or cross-aggregate work. Never claim atomic enqueue, FIFO business order, one attempt, or exactly-once effect. |
| Conceptual outbox/inbox | Business data and an outbox record persist together; rollback removes both. Relay, publish, and acknowledgement windows can duplicate. | Durable delivery intent across write and relay; asynchronous ordering is application-defined. | Use for integration intent. Do not infer a schema, relay, retention, broker recipe, or exactly-once effect. |
Interpretation. The tier changes timing and failure windows only. Authorization, tenant scope, Domain invariants, and idempotency remain independent decisions at every tier.
Read the crash and rollback windows
After-commit improves transaction visibility: Laravel discards the callback on rollback and invokes it after commit. It remains an in-memory callback, so it is not a durable handoff. A queued Listener implements both queue interfaces; its enqueue occurs after commit but is not atomic with the business write. The integration test observes rollback discard and post-commit invocation only; it does not prove crash survival, broker durability, retry, authorization, tenant filtering, or idempotency.
Evolve an immutable payload
Keep existing consumers intentional when a payload changes. Add a compatible field, or publish a deliberately new versioned Event. VERSION = 1 is current generated evidence, not a schema registry.
Context: ❌ Prohibited queue payload — A live Eloquent Model is mutable worker input.
event(new OrderPlaced($order)); Context: ✅ Correct queue payload — Carry an ID or immutable value, then re-fetch tenant-scoped state.
event(new OrderPlaced(orderId: $order->getKey(), tenantId: $tenantId)); Keep the boundary honest
Never carry a Request or auth object, injected dependency, closure, mutable aggregate, or live Model across an Event or queue boundary. Reconstruct actor and tenant information on the receiving Adapter, scope the lookup, explicitly authorize, validate the Domain invariant, and reuse a stable idempotency key on retry. Read Listeners for reaction form and Authorization for permission ownership.