dddominio/event-sourcing
Laravel package for event sourcing in DDD-style apps. Store and replay domain events to rebuild aggregates, keep an append-only event log, and track state changes over time. Useful for audit trails, projections, and CQRS-inspired architectures.
Installation
composer require dddominio/event-sourcing
Ensure your project uses PHP 8.0+ and Laravel 8+ (or Lumen).
Basic Setup
php artisan vendor:publish --provider="Dddominio\EventSourcing\EventSourcingServiceProvider"
config/app.php under providers (if not auto-discovered).Define an Aggregate Root
Create a class extending Dddominio\EventSourcing\AggregateRoot:
namespace App\Domain\Posts;
use Dddominio\EventSourcing\AggregateRoot;
class Post extends AggregateRoot
{
public function create(string $title, string $content)
{
$this->recordThat(new PostCreated($title, $content));
}
}
Define Events
Create a class implementing Dddominio\EventSourcing\DomainEvent:
namespace App\Domain\Posts\Events;
use Dddominio\EventSourcing\DomainEvent;
class PostCreated implements DomainEvent
{
public function __construct(
public string $title,
public string $content
) {}
}
First Use Case: Publishing an Event
$post = new Post();
$post->create("Hello World", "First post!");
$post->publish(); // Persists events to storage
Command Handling Use a service layer to trigger domain logic:
namespace App\Services;
use App\Domain\Posts\Post;
use App\Domain\Posts\Events\PostCreated;
class PostService
{
public function createPost(string $title, string $content)
{
$post = new Post();
$post->create($title, $content);
$post->publish();
}
}
Event Sourcing Storage
Configure storage (default: database) in config/event-sourcing.php:
'storage' => [
'driver' => 'database',
'table' => 'event_store',
],
For custom storage (e.g., Redis), implement Dddominio\EventSourcing\Storage\EventStorageInterface.
Event Projection (Read Models) Subscribe to events to update read models:
namespace App\Listeners;
use App\Domain\Posts\Events\PostCreated;
use App\Repositories\PostRepository;
class UpdatePostIndex
{
public function __invoke(PostCreated $event)
{
// Update Elasticsearch, cache, etc.
}
}
Register the listener in EventSourcingServiceProvider:
Event::listen(PostCreated::class, UpdatePostIndex::new());
Replaying Events (Recovering State) Reconstruct an aggregate from its event history:
$postId = 'post-123';
$post = Post::recover($postId); // Hydrates from event store
Snapshotting (Optimization) Use snapshots to reduce event replay time:
$post->snapshot(); // Stores current state to skip replaying old events
Event Ordering
ORDER BY).occurred_on timestamp.Concurrency Conflicts
expectedVersion in AggregateRoot:
$post->create($title, $content, expectedVersion: 2);
Storage Bloat
Event::cleanupOldEvents(Carbon::now()->subDays(30));
Event Versioning
event_version in the event store table to handle backward compatibility.Testing
$eventStore = Mockery::mock(EventStorageInterface::class);
$this->app->instance(EventStorageInterface::class, $eventStore);
config/event-sourcing.php:
'debug' => env('APP_DEBUG', false),
php artisan tinker
>>> \Dddominio\EventSourcing\Facades\Event::all();
Custom Storage
Implement EventStorageInterface for non-database backends (e.g., Kafka, DynamoDB):
class CustomEventStorage implements EventStorageInterface
{
public function append(string $aggregateId, array $events): void
{
// Custom logic (e.g., Kafka producer)
}
}
Event Serialization
Override serialization in AggregateRoot:
protected function serializeEvent(DomainEvent $event): string
{
return json_encode([
'event' => get_class($event),
'data' => $event,
]);
}
Domain Event Bus Extend the event bus to add middleware:
Event::extend(function ($bus) {
$bus->pipe(function ($event, $next) {
// Pre-process event (e.g., logging)
return $next($event);
});
});
Aggregate Factories Use factories to instantiate aggregates with preloaded events:
$post = Post::recover($postId, new PostFactory());
How can I help you explore Laravel packages today?