patchlevel/event-sourcing-bundle
Symfony integration for the patchlevel event-sourcing library, built on Doctrine DBAL. It bundles snapshots, stream splitting, versioned subscriptions, upcasting, personal-data crypto-shredding, schema/migration setup, and CLI commands for a smooth developer experience.
Architecture fit. The first thing to establish is a mismatch in the framing: this is a Symfony bundle, not a Laravel package. The value-add layer — DI extension, YAML/PHP config tree, console commands, tagged services — is Symfony-specific and will not load in a Laravel container. What is reusable is the underlying patchlevel/event-sourcing library plus Doctrine DBAL as the persistence substrate. So the realistic architecture is: adopt the core library, treat the bundle as a reference implementation for how to wire config, commands, and subscriptions, and write a thin Laravel service provider ourselves. Budget for that adapter work explicitly; it is not a composer require integration.
Integration feasibility. Moderate. The core library is well-scoped (aggregates, event store, snapshots, split streams, subscriptions/projections, upcasting, crypto-shredding) and "fully typed," which helps static analysis and onboarding. The friction points are: (1) Doctrine DBAL sits beside Eloquent rather than inside it, so we get a second connection and a second migration toolchain; (2) the bundle leans on Symfony Serializer and Symfony Uid conventions, which need mapping decisions against Laravel's serialization and ULID/UUID usage; (3) transactional consistency across the DBAL event store and Eloquent read models requires deliberate design, not defaults.
Technical risk. High, driven by adoption signals rather than code quality. 52 stars and 0 dependents means effectively no production reference base outside the vendor's own work. The dependency is close to single-vendor (Patchlevel), which is a bus-factor and roadmap-alignment risk. The positive counterweight is MIT licensing, published docs, an active release cadence, and a clean separation between core library and framework glue — meaning the exit cost is bounded if we keep our own adapter thin. Secondary risk: composer version pins on Symfony components (console, serializer, uid) can collide with Laravel's own pinned Symfony dependencies; this must be resolved in a spike before any commitment.
Key questions for the maintainer / spike.
Stack fit. Treat this as infrastructure, not application code. The event store lives on Doctrine DBAL; read models and the rest of the app stay on Eloquent. That means: a dedicated DBAL connection configured through Laravel's database.php (sharing credentials/DSN but separate connection object), Doctrine Migrations or a hand-rolled Laravel migration path for the event-store tables, and an explicit rule that only the event store writes through DBAL and only projections write through Eloquent. Do not let the two share transaction boundaries casually.
Migration path. There is no incremental migration from an existing Eloquent-based aggregate — event sourcing is a rewrite of the write model for whichever bounded context adopts it. The realistic path is a strangler pattern on a single, low-risk bounded context: keep the existing Eloquent model authoritative, run the event-sourced model in parallel behind a feature flag, dual-write briefly for validation, then cut reads over. Backfilling historical state means synthesizing an initial event stream from existing rows, which is a one-way, idempotent, well-tested job.
Compatibility. Verify in the spike: PHP version floor, Laravel major version, DBAL major version, and a resolved composer.lock that does not downgrade Laravel's Symfony component pins. Also confirm the DB platform — Postgres is the safer assumption than MySQL for advisory locks and concurrent subscription handling; validate explicitly if we are MySQL-standardized.
Sequencing.
Maintenance. We own the Laravel adapter — that is permanent, non-trivial maintenance: it must track both the upstream library's release cadence and Laravel's major-version churn. Pin to exact versions, keep the adapter under ~500 lines, and forbid leakage of package types into domain code so the library remains swappable. Upstream upgrades are not routine patch bumps: a change to serialization format or event-store schema on a populated store is a migration project.
Support. No commercial support, no SLA, small maintainer pool, no visible dependent base. Escalation path for a production defect is a GitHub issue. Mitigation: budget internal ownership from day one, and consider a support or consulting arrangement with the vendor given they are a commercial entity — treat that as a procurement line item, not a hope.
Scaling. Three cost curves to model. (1) Storage: append-only event tables grow monotonically; snapshots reduce replay cost but not table size, so partitioning or archival policy is a day-one design decision, not a quarter-three problem. (2) Replay: projection rebuilds are full scans; on a large store these become multi-hour operations that need a blue/green projection strategy. (3) Workers: subscriptions and processors are long-running daemons requiring supervision, memory-leak hygiene, restart policy, and per-subscription concurrency control — a materially different operational shape from queue workers.
Failure modes to design for.
Ramp-up. The team is signing up for two learning curves simultaneously: event sourcing as a paradigm (eventual consistency, replay, versioning, projection design) and Doctrine DBAL tooling in a Laravel shop. Realistic ramp: 2–3 weeks for a competent senior engineer to be productive in the spike, 6–8 weeks before the team can operate a projection in production without hand-holding. Mitigate with a written internal standard for event naming/versioning, a review checklist for upcasters, a replay exercise as part of onboarding, and vendor documentation reading as required pre-work.
Recommendation. Conditional go, scoped to a single bounded context with a proven rollback and a deliberately thin adapter. The library's feature completeness (snapshots, split streams, subscription lifecycle, crypto-shredding, upcasting) genuinely reduces build cost, and MIT licensing bounds the downside. But 0 dependents, no Laravel-native path, and single-vendor concentration mean this is an adopt-with-our-own-ownership decision, not a dependency we can lean on. Approve the spike; gate the pilot on a resolved dependency graph and a working subscription worker.
How can I help you explore Laravel packages today?