Event Sourcing for Laravel
The official Laravel package for patchlevel/event-sourcing. An Eloquent-like aggregate API, facades for commands, queries, repositories, and the store, Artisan commands for the projection lifecycle, and read models built with the Schema and DB facades - event sourcing that feels like Laravel.
$ composer require patchlevel/laravel-event-sourcing $ php artisan vendor:publish --tag patchlevel-config $ php artisan vendor:publish --tag patchlevel-migrations $ php artisan migrate
Event sourcing, the Laravel way
Define events and aggregates as plain PHP, then work with them the way you work with Laravel: static load and save on the aggregate, facades for the services, and read models built with the Schema and DB facades.
Define an event
Pure PHP classes with a single attribute - no base class, no framework coupling. Your domain model stays clean and testable.
#[Event('hotel.guest_is_checked_in')] final class GuestIsCheckedIn { public function __construct( public readonly string $guestName, ) {} }
Model the aggregate
Extend the package's AggregateRoot and your aggregate gets static load and save helpers - the same mental model as an Eloquent model, backed by an event stream.
#[Aggregate(name: 'hotel')] final class Hotel extends AggregateRoot { #[Id] private Uuid $id; /** @var list<string> */ private array $guests = []; public function checkIn(string $guestName): void { if (in_array($guestName, $this->guests, true)) { throw new GuestAlreadyCheckedIn($guestName); } $this->recordThat(new GuestIsCheckedIn($guestName)); } #[Apply] protected function applyGuestIsCheckedIn(GuestIsCheckedIn $event): void { $this->guests[] = $event->guestName; } }
Load & save like Eloquent
No repository wiring needed in your controllers: load the aggregate by id, call a business method, save. The package replays the event history behind the scenes.
final class HotelController { public function checkIn(string $id, Request $request): JsonResponse { $hotel = Hotel::load(Uuid::fromString($id)); $hotel->checkIn($request->json('name')); $hotel->save(); return response()->json(); } }
Project with the DB facade
Projectors build read models the Laravel way: create tables with a Schema Blueprint in #[Setup], write rows with the DB facade. Rebuild any time from the event stream.
#[Projector('hotel')] final class HotelProjection { use SubscriberUtil; #[Subscribe(GuestIsCheckedIn::class)] public function handleGuestIsCheckedIn(Uuid $hotelId): void { DB::table($this->table()) ->where('id', $hotelId->toString()) ->increment('guests'); } #[Setup] public function create(): void { Schema::create($this->table(), static function (Blueprint $table): void { $table->uuid('id')->primary(); $table->string('name'); $table->integer('guests'); }); } }
A native Laravel feel
Not a generic library with a thin adapter. The package plugs event sourcing into the Laravel building blocks you already use every day.
Auto-Discovery
Facades
Artisan Commands
Publishable Config & Migrations
Works with Eloquent
Native Side Effects
CQRS with a Laravel API
Dispatch commands and queries through facades, reach the store and repositories the same way, and drive the whole event-sourcing lifecycle from Artisan.
CommandBus facade
Dispatch commands with CommandBus::dispatch - handled by the method you marked with the #[Handle] attribute, including aggregate loading and saving.
QueryBus facade
Retrieve data with QueryBus::dispatch - answered by services via the #[Answer] attribute.
Repository & Store facades
Grab the repository for any aggregate with Repository::get, or load and save raw messages through the Store facade.
Artisan all the way
Set up subscriptions, boot projections, and run workers with php artisan event-sourcing:* commands.
use Patchlevel\LaravelEventSourcing\Facade\CommandBus; use Patchlevel\LaravelEventSourcing\Facade\QueryBus; use Patchlevel\LaravelEventSourcing\Facade\Repository; // dispatch a command CommandBus::dispatch(new BookHotel($hotelId, $guestName)); // ask a question $count = QueryBus::dispatch(new HotelCountQuery()); // or talk to the repository directly $repository = Repository::get(Hotel::class); $hotel = $repository->load($hotelId);
$ php artisan event-sourcing:subscription:setup $ php artisan event-sourcing:subscription:boot $ php artisan event-sourcing:subscription:run
The full power of the core library
The integration ships everything from patchlevel/event-sourcing - production features you would otherwise build yourself.
Subscription Engine
Managed lifecycle for projections and processors: replay, rebuild, retry, gap detection, and blue-green versioning.
GDPR Crypto-Shredding
Mark fields as personal data, store keys separately, and make a subject unreadable forever - without rewriting history.
Snapshots
Automatic, cache-backed snapshots keep replays fast for long-lived aggregates.
Upcasting
Evolve old events on the fly, or rewrite them permanently when you retire legacy formats.
Split Stream
Archive past lifecycle phases and load only the active slice - full history preserved.
Built for Testing
Given-When-Then testing with in-memory stores for lightning-fast unit tests of business logic.
Frequently asked questions
Everything you need to know about event sourcing in Laravel with patchlevel.
How do I add event sourcing to a Laravel application?
Run composer require patchlevel/laravel-event-sourcing. The service provider is auto-discovered. Publish the config with php artisan vendor:publish --tag patchlevel-config, publish and run the migrations, and add the EventSourcingMiddleware in bootstrap/app.php - then you are ready to define your first aggregate.
Does the package feel like Laravel?
Yes. Aggregates extend an AggregateRoot base class with static load and save helpers similar to Eloquent models, facades like Repository, Store, CommandBus, and QueryBus give quick access to services, and Artisan picks up all CLI commands automatically.
Can I use event sourcing together with Eloquent?
Absolutely. Event-sourced aggregates and Eloquent models coexist in the same application. Projections typically build read models with the Schema and DB facades or with Eloquent models, so the read side stays plain Laravel.
Which databases does the event store support?
The event store is built on Doctrine DBAL and supports PostgreSQL, MySQL, MariaDB, and SQLite out of the box. Custom stores for other technologies can be implemented against clean interfaces.
How do projections work in Laravel?
Projectors subscribe to events with the #[Subscribe] attribute and write read models using the DB facade, the Schema builder, or Eloquent. The subscription engine manages their lifecycle - boot, replay, rebuild, retry - via Artisan commands.
Is patchlevel/laravel-event-sourcing production ready?
The package is the official Laravel integration of patchlevel/event-sourcing, which has over 300,000 Packagist installs. It is MIT licensed, fully typed, and includes production features like GDPR-compliant crypto-shredding, snapshots, and a managed subscription lifecycle.
Start event sourcing in Laravel
Install the package and build your first aggregate, event, and projection in minutes - the getting-started guide walks you through it.