laravel-eventsource maintained by syeedalireza
Laravel EventSource
Enterprise-grade Event Sourcing and CQRS implementation for Laravel.
Build scalable, auditable applications with complete event history, temporal queries, and event-driven architecture. This package provides a production-ready implementation of Event Sourcing patterns with full CQRS support, projections, and snapshots.
🚀 Features
- Complete Event Store: Persist all domain events with full replay capability
- CQRS Pattern: Separate command and query responsibilities
- Aggregate Root: Build aggregates that maintain consistency boundaries
- Projections: Build read models from event streams
- Snapshots: Optimize aggregate reconstruction with automatic snapshots
- Temporal Queries: Query your data at any point in time
- Multiple Storage Drivers: PostgreSQL, MySQL, MongoDB support
- Event Versioning: Handle event schema evolution gracefully
- Async Projections: Process events asynchronously with Laravel Queue
- Event Replay: Rebuild projections from scratch
- Testing Helpers: Comprehensive testing utilities
📋 Requirements
- PHP 8.1 or higher
- Laravel 10.0 or higher
- PostgreSQL 12+ / MySQL 8.0+ / MongoDB 5.0+
📦 Installation
You can install the package via composer:
composer require syeedalireza/laravel-eventsource
Publish the configuration file:
php artisan vendor:publish --tag=eventsource-config
Run the migrations:
php artisan migrate
🎯 Quick Start
1. Define Your Events
use Syeedalireza\LaravelEventSource\Events\DomainEvent;
class AccountCreated extends DomainEvent
{
public function __construct(
public readonly string $accountId,
public readonly string $ownerName,
public readonly float $initialBalance
) {
parent::__construct();
}
}
2. Create an Aggregate
use Syeedalireza\LaravelEventSource\Aggregate\AggregateRoot;
class Account extends AggregateRoot
{
private string $ownerName;
private float $balance;
public static function create(string $accountId, string $ownerName, float $initialBalance): self
{
$account = new self($accountId);
$account->recordThat(new AccountCreated($accountId, $ownerName, $initialBalance));
return $account;
}
protected function applyAccountCreated(AccountCreated $event): void
{
$this->ownerName = $event->ownerName;
$this->balance = $event->initialBalance;
}
public function deposit(float $amount): void
{
$this->recordThat(new MoneyDeposited($this->aggregateId, $amount));
}
protected function applyMoneyDeposited(MoneyDeposited $event): void
{
$this->balance += $event->amount;
}
}
3. Use the Event Store
use Syeedalireza\LaravelEventSource\Facades\EventStore;
// Create and save an aggregate
$account = Account::create('acc-123', 'John Doe', 1000.0);
$account->deposit(500.0);
EventStore::save($account);
// Load an aggregate
$account = EventStore::load(Account::class, 'acc-123');
// Get event stream
$events = EventStore::getStream('acc-123');
4. Create a Projection
use Syeedalireza\LaravelEventSource\Projections\Projector;
class AccountBalanceProjector extends Projector
{
protected array $handles = [
AccountCreated::class,
MoneyDeposited::class,
MoneyWithdrawn::class,
];
public function onAccountCreated(AccountCreated $event): void
{
DB::table('account_balances')->insert([
'account_id' => $event->accountId,
'owner_name' => $event->ownerName,
'balance' => $event->initialBalance,
'created_at' => $event->occurredAt,
]);
}
public function onMoneyDeposited(MoneyDeposited $event): void
{
DB::table('account_balances')
->where('account_id', $event->accountId)
->increment('balance', $event->amount);
}
}
📖 Advanced Usage
Snapshots
Enable automatic snapshots for faster aggregate reconstruction:
use Syeedalireza\LaravelEventSource\Contracts\Snapshotable;
class Account extends AggregateRoot implements Snapshotable
{
public function createSnapshot(): array
{
return [
'owner_name' => $this->ownerName,
'balance' => $this->balance,
];
}
public static function restoreFromSnapshot(string $aggregateId, array $state, int $version): self
{
$account = new self($aggregateId);
$account->ownerName = $state['owner_name'];
$account->balance = $state['balance'];
$account->version = $version;
return $account;
}
}
Temporal Queries
Query your data at any point in time:
// Get account state at specific date
$account = EventStore::loadAtDate(Account::class, 'acc-123', '2024-01-15');
// Get account state at specific version
$account = EventStore::loadAtVersion(Account::class, 'acc-123', 10);
Event Versioning
Handle event schema evolution:
class AccountCreatedV2 extends DomainEvent
{
public int $version = 2;
// Upcaster for old events
public static function upcast(array $eventData): array
{
if ($eventData['version'] === 1) {
$eventData['currency'] = 'USD'; // Add new field
$eventData['version'] = 2;
}
return $eventData;
}
}
Async Projections
Process projections asynchronously:
// In config/eventsource.php
'projections' => [
'mode' => 'async', // or 'sync'
],
// Your projection will automatically be queued
🧪 Testing
composer test
Run with coverage:
composer test-coverage
Static analysis:
composer analyse
Code formatting:
composer format
📚 Documentation
For complete documentation, visit https://syeedalireza.github.io/laravel-eventsource
🔧 Configuration
The configuration file config/eventsource.php allows you to customize:
- Event store driver (PostgreSQL, MySQL, MongoDB)
- Snapshot settings and frequency
- Projection mode (sync/async)
- Event serialization
- And more...
🤝 Contributing
Please see CONTRIBUTING.md for details.
🔒 Security
If you discover any security-related issues, please email your.email@example.com instead of using the issue tracker.
📝 License
The MIT License (MIT). Please see License File for more information.