eventsauce/message-outbox-for-illuminate
A Laravel/Illuminate outbox for EventSauce that persists domain messages transactionally alongside your business data, then relays them to consumers, avoiding dual-write problems and helping guarantee reliable, at-least-once event delivery.
Architecture fit. This package is the Illuminate binding for EventSauce's MessageOutbox abstraction — the transactional outbox pattern. The premise is well-understood and correct: domain messages are inserted into an outbox table inside the same database transaction that persists the aggregate, and a separate relay drains that table to a broker or queue with at-least-once semantics. It replaces the common Laravel anti-pattern of dispatching jobs inside a transaction or from model observers, where a post-commit crash silently drops messages.
The fit is strong only when two preconditions hold: the team already uses EventSauce contracts (Message, MessageDispatcher) or is willing to adopt them, and the write model and outbox share one relational database. If the team uses a different event-sourcing library, or the consumer can join the transaction, the package adds a dependency without removing a failure mode.
Integration feasibility. The package is a thin adapter, so its own surface is small — but it is not deployable on its own. Composition requires four moving parts: a published migration for the outbox table, a relay entry point (Artisan command or queue worker) running as a supervised process, a message serializer, and the downstream consumer. A TPM should treat the package as one line in a composer.json and the relay plus observability as the actual deliverable. Estimates that stop at "add the dependency" are wrong by an order of magnitude.
Technical risk. The material risks are organizational, not architectural:
[READ ONLY] subtree split of the EventSauce monorepo with no issue tracker and no PR flow. A security or compatibility fix requires an upstream release or a fork.Key questions to resolve before committing:
illuminate/* and PHP constraints are declared, and is there an explicit Laravel 11/12 support statement?FOR UPDATE SKIP LOCKED, advisory locks), and is ordering guaranteed per aggregate root?What this buys the roadmap. Two things worth naming explicitly to stakeholders. First, it enables a contract-level guarantee — "every confirmed order emits exactly one fulfillment event" — which is often worth more commercially than the feature itself. Second, it unblocks a class of event-driven work (partner webhooks, ledger events, read-model projection, cache invalidation) rather than a single feature. Price the alternative honestly: a competent engineer can hand-roll the shape in a day or two, and adopting the pattern without the package is a legitimate outcome.
Stack fit. Best case is a Laravel application on PostgreSQL or MySQL 8+ with existing EventSauce aggregates and a queue or broker downstream (SQS, RabbitMQ, Redis). The pattern is database-agnostic, but the relay's concurrency story is materially better on Postgres, where SKIP LOCKED is native. Confirm the engine before designing the relay, not after.
Migration path. Do not big-bang this. A staged adoption:
Compatibility. Because the split tracks monorepo tags, pin an exact version in composer.json rather than a floating constraint — an upstream release can move Illuminate constraints under you. Validate against the team's pinned Laravel and PHP versions in a throwaway branch before design work begins.
Sequencing. Roughly two to three sprints for a single bounded context, with explicit gates:
The TPM's lever here is the gates: each phase has a defined exit criterion, and "the outbox exists" is not one of them.
Maintenance. Low code volume, non-zero attention. You inherit a dependency you cannot patch locally, so every Illuminate upgrade is gated on an upstream release. Budget for a possible fork — realistically a few days a year, but catastrophic if unplanned during a framework upgrade window.
Support. No issue tracker on this repository. Support routes through the parent EventSauce project and community channels. Designate a named internal owner who reads the upstream changelog; nobody else will surface breaking changes before they land.
Scaling. The outbox table is the pressure point, on two dimensions:
dispatched_at, id) and expect index maintenance overhead.Pruning must be automated from day one. An unpruned outbox is the most common operational failure of this pattern, and it degrades the whole application, not just the messaging path.
Failure modes to design for, with named owners:
Ramp-up. A mid-level Laravel engineer can wire the package in under a week. Operating it correctly — idempotency, lag alerting, pruning, dead-letter handling — is closer to two to three weeks for the on-call engineer. That operational literacy, not the composer require, is the real cost of adoption.
Bottom line. The architecture is right and the code surface is small, but this repository is a low-signal distribution artifact: 0 stars, 0 dependents, read-only split, single maintainer. Adopt the pattern; treat this package as a replaceable implementation detail. Run a single-context pilot behind the observability and pruning gates above, and keep afterCommit dispatch as the answer for any consumer that lives inside your own Laravel queue.
How can I help you explore Laravel packages today?