Skip to content

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}.phpGenerated after-commit Event interface.

php
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

TierTransaction and crash windowQueue behaviorUse and prohibited claim
SynchronousRuns 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-commitThe 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-commitRollback 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/inboxBusiness 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.
A UseCase transaction can dispatch synchronously before commit, discard an after-commit callback on rollback, lose an in-memory callback in a crash gap after commit, enqueue a queued after-commit reaction that retries with idempotency, or persist durable outbox intent for a relay.
Choose a Domain Event delivery guarantee. Choose visibility or durable intent deliberately; neither is an exactly-once effect guarantee.

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.

php
event(new OrderPlaced($order)); 

Context: ✅ Correct queue payload — Carry an ID or immutable value, then re-fetch tenant-scoped state.

php
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.