Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Outbox Laravel Package

ianfoxdev/outbox

A transactional outbox for Laravel: write messages or events to a database table inside your existing transaction, then relay them to queues or external services afterward, ensuring reliable delivery and no lost events if a job fails or the app crashes mid-request.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit This package implements the transactional outbox pattern: business writes and outbox rows commit in the same database transaction, and a separate relay process publishes those rows to the downstream broker. That is the correct shape for any Laravel service where "write to DB" and "publish event" must not diverge — order placement, payment capture, inventory reservation, notification fan-out. It fits as an infrastructure-layer dependency beneath domain code, not as a domain concern, so it should be adopted as a platform capability rather than per-team.

GA confirms one architectural fact the rc1 notes did not surface: the relay is a Go binary, not a PHP worker. The release notes name franz-go (a Kafka client) and prometheus/client_golang as its build dependencies, and ship it as a container image with a Kubernetes base. So the integration is genuinely two runtimes — a PHP/Laravel library embedded in the app, plus a separately deployed Go process with its own image, registry, scrape endpoint, and broker credentials. That is broader than a typical Laravel package and widens the set of operational owners: schema migrations, a containerized long-running process, observability, and possibly an HTTP surface. Four distinct owners, as noted at RC, and now a fifth concern — container image lifecycle.

The Kafka inference matters for fit. franz-go is a Kafka client; there is no indication of AMQP, SQS, or NATS support in the packaged relay. If our downstream broker is Kafka, this is a clean fit. If it is not, the relay is not the piece we adopt — we would be adopting the outbox table and PHP API and writing our own publisher, which changes the value proposition substantially.

Integration feasibility Feasible for a team already on Laravel with a supported relational database and Kafka downstream. The work is not package installation but process supervision (now: container orchestration rather than a supervisor entry), schema management (a new table plus partition maintenance if partitioning is enabled), observability wiring (Prometheus scraping of a Go exporter), and image supply chain (registry access, pull policy, scanning). None of that is exotic, but each is a discrete task with a distinct owner.

The GA event itself is close to a no-op on the contract: the PHP API, table, relay settings, lock key, message layout, partitioning, metrics, and endpoints are explicitly unchanged from rc1, and ADR 0008's freeze now converts to a permanent 1.x guarantee — those surfaces change only in a major version. The only deltas in 1.0.0 are build-level: franz-go 1.22.2, prometheus/client_golang 1.25.0, an additional floating image tag 1, and a Kubernetes base pinned to 1.0. Nothing to re-validate on the contract; the RC spike results carry forward intact.

Technical risk

  • RC maturity — largely retired. No bugs were reported against rc1, and a public six-day demo instance processed 775,045 paid orders with 25 leader kills, producer and consumer reconciling to the cent. That is meaningful evidence for failover behavior and end-to-end correctness, and it is the kind of field report the maintainer solicited. It is not a throughput proof: ~775k orders over six days averages under two events per second, which says nothing about the relay's ceiling. Note also that the entire code path has been unchanged since 0.5.0 — the defect population is whatever 0.5.0 carried, now with more exposure rather than fewer lines.
  • Two-runtime footprint. A Go sidecar/container is a new artifact class for a Laravel shop: registry credentials, image vulnerability scanning, pull-policy decisions, and a deploy pipeline that is not composer update. Modest, but it is new surface for a team that thinks in PHP releases.
  • Floating tag policy. The 1 tag follows the latest 1.x release and the Kubernetes base references 1.0. A floating major tag means relay patches arrive without an application deploy — convenient, and directly in tension with the "pin exactly" mitigation below. Choose deliberately: 1.0.0 for control, 1 for patch uptake, and never both by accident.
  • At-least-once delivery. Outbox relays retry; consumers must be idempotent. The demo's cent-level reconciliation suggests the packaged semantics behave, but idempotency remains a consumer-side obligation and the most common source of production incidents in outbox rollouts.
  • Lock key and relay concurrency. The lock key is frozen for 1.x, so the concurrency model is now a contract. The demo exercised 25 leader kills, which is reassuring for failover, but leader kills are not the same as sustained high throughput through a single active relay.
  • Partitioning as a schema commitment. Partitioning is frozen for 1.x. Choosing wrong (or skipping it) creates a migration project later, not a config change.
  • Maintainer concentration. Still a single-maintainer GitHub project holding a frozen public contract on the order/payment path. GA does not change this; the release notes say nothing about additional maintainers or a support commitment.
  • Governance risk. The BREAKING-labeling convention depends on the maintainer honoring it, and there is still no stated deprecation window for urgent breaking fixes within 1.x.
  • Unverified framework support. Laravel and PHP compatibility matrices remain unstated in the release notes and must be confirmed against composer constraints. This was open at RC and is still open.

Key questions

  1. Which Laravel and PHP versions are supported, and does that match our fleet's current and next-year baseline?
  2. Which databases are supported, and does the partitioning scheme require a specific engine/version (e.g. MySQL 8 partitioned InnoDB, PostgreSQL declarative partitioning)?
  3. Which broker does the packaged relay target? franz-go implies Kafka. If our downstream is RabbitMQ/SQS/NATS, the relay is not reusable as-is and we must scope a custom publisher.
  4. What is the relay's throughput ceiling and how does the frozen lock key constrain horizontal scaling — single active relay, or sharded by lock key? The demo volume does not answer this.
  5. What guarantees does the message layout imply for consumers — ordering per aggregate, deduplication keys, headers?
  6. Is there a documented retention/pruning story, or is partition dropping the only deletion mechanism?
  7. What is the deprecation policy within 1.x for changes that are breaking but urgent?
  8. Are the metrics and endpoints exposed by default, and what is the auth posture on the endpoint surface? How does the relay authenticate to Kafka (TLS/SASL), and where do those credentials live?
  9. Is the relay image published to a public registry with provenance/signing, and what is our scanning and update cadence for it?
  10. Who besides the maintainer has commit rights, and is there a stated support commitment now that 1.0.0 has shipped?

Integration Approach

Stack fit Natural fit: Laravel service, relational database, Kafka downstream, and now a container platform that can run the relay image. The package slots in as a composer dependency plus one migration for the app side, and a pinned container image plus scrape config for the relay side. Domain code keeps writing outbox rows inside existing transactions; no framework-level surgery. The blast radius stays confined to infrastructure, but "infrastructure" now spans two deployment artifacts.

Migration path

  1. Contract spike (short). Install in a non-production environment, publish and run the migration, run the relay image against a test Kafka cluster, and validate the Prometheus scrape and endpoint surface. Because GA is contract-identical to rc1, this is confirmation rather than discovery — the window for influencing the design has closed with the freeze.
  2. Compatibility validation. Confirm composer constraints against our Laravel/PHP matrix, confirm partitioning on our actual database engine and version, and confirm broker compatibility and auth mode against our Kafka estate.
  3. Consumer readiness. Implement idempotency and deduplication on at least one consumer before any producer writes to the outbox in production. This is the gate most rollouts skip and most incidents trace back to.
  4. Pilot producer. Enable outbox writes for a single low-risk, high-volume event type. Observe relay lag, table growth, and consumer error rates for a full business cycle.
  5. Broaden. Expand producer coverage event type by event type, not service by service.

Compatibility Three axes now: framework/runtime versions (verify via composer, do not assume), database engine capabilities (partitioning and lock implementation are the likely MySQL/PostgreSQL divergence), and broker protocol (Kafka via franz-go). Treat the frozen table and partitioning scheme as a schema contract we adopt permanently — review the migration before it runs in production rather than after. Pin the image by digest or exact tag in our manifests; the upstream 1 and 1.0 tags are conveniences for their demo, not a policy recommendation for us.

Sequencing The RC-versus-GA sequencing question is now moot: 1.0.0 is out and the contract is locked, so there is no remaining advantage to waiting and no remaining lever on the frozen surfaces. Sequence: spike and compatibility validation first, consumer idempotency second, pilot producer third. The demo's six-day, 25-leader-kill run gives reasonable confidence to move faster on the pilot than the RC assessment implied — but the throughput ceiling and the broker-compatibility answer should be known before we commit a payment-path producer, not after.

Operational Impact

Maintenance Three recurring obligations now, not two: partition maintenance (creating and dropping partitions on a schedule, or the table grows unbounded), relay image lifecycle (version tracking, vulnerability scanning, pull-policy, and a decision about the floating 1 tag), and relay supervision (restart policy, log aggregation, alerting on process death — now in the orchestrator rather than supervisor). All are platform-team-shaped work. Schema evolution of the outbox table should be treated as a platform change, not an application change, once multiple producers depend on it.

Support Still the weakest dimension. Single-maintainer upstream, no stated SLA, no stated post-1.0.0 support commitment in the GA notes. Mitigations: pin the image and the composer version exactly, vendor a fork or maintain an internal patch branch if the package lands on a critical path, and budget engineering time to own the relay if upstream stalls. The frozen contract helps — it bounds how much upstream drift we absorb — and GA plus a clean six-day demo lowers the probability of needing an emergency patch in the first quarter.

Scaling Two independent levers and one hard constraint. Lever one is partition pruning and archival, keeping the table and index working set bounded. Lever two is relay batching and poll frequency. The hard constraint remains the frozen lock key: whatever concurrency the relay supports today is what it supports through 1.x. The demo's ~775k orders over six days is a correctness datapoint, not a capacity datapoint — it exercises failover, not sustained load. Validate the ceiling against our peak event volume during the spike. If the answer is "not enough," the escape hatch is sharding the outbox by lock key or multiple relays with distinct keys, and that must be designed in from the start rather than retrofitted.

Failure modes

  • Relay down. Events accumulate in the outbox; business transactions still succeed. Degradation is delayed delivery, not lost data — but silent unless lag is alerted on. Alert on relay liveness and outbox row age, not just row count.
  • Relay crash mid-publish. Duplicate delivery on restart. The 25 leader kills in the demo with cent-level reconciliation are evidence that duplicates are handled downstream, but that reflects the demo consumer's idempotency, not a package guarantee. Consumers must be idempotent.
  • Stuck lock. If a relay dies without releasing the lock, publication halts until the lock expires or is cleared. Understand the timeout and the manual recovery procedure before go-live.
  • Poison message. A row that reliably fails publication can block progress depending on retry semantics. Confirm whether the relay skips, retries indefinitely, or dead-letters, and monitor for rows with high attempt counts.
  • Unbounded table growth. If partition maintenance is not automated, the table becomes a query and vacuum/bloat problem months after launch — a slow-burn failure that will not appear in a pilot.
  • Consumer non-idempotency. Manifests as duplicate downstream records or double-charges. Highest business cost, least visibility from the package's own metrics.
  • Image drift. With the floating 1 tag in play, a relay pulled on restart can differ from the one validated in the spike. Mitigate by digest-pinning in our own manifests and treating relay upgrades as deliberate changes.

Ramp-up Low learning curve for engineers fluent in Laravel and the outbox pattern; the package implements a well-known pattern rather than a novel abstraction. The ramp-up cost sits with on-call and platform staff, who now need to reason about both a PHP library and a Go container: recognizing relay lag, distinguishing a stuck lock from a slow consumer, reading a Go Prometheus exporter rather than PHP instrumentation, and executing the lock-clearing runbook. Budget for a written runbook and one game-day exercise covering relay death, stuck lock, and poison message before the pilot producer goes live — the demo's leader-kill resilience is encouraging, but it validates the maintainer's consumer, not our runbook.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource