Installation
composer require averor/event-sourcing
Ensure averor/message-bus is also installed (this package extends it).
Publish Config
php artisan vendor:publish --provider="Averor\EventSourcing\EventSourcingServiceProvider"
Review config/event-sourcing.php for default settings (e.g., event storage, projection paths).
Define an Event
namespace App\Events;
use Averor\EventSourcing\Event;
class UserRegistered implements Event
{
public function __construct(
public string $userId,
public string $email
) {}
}
First Event Sourcing Workflow
use Averor\EventSourcing\EventSourcedEntity;
use App\Events\UserRegistered;
class User extends EventSourcedEntity
{
protected $entityId;
public function register(string $email): void
{
$this->recordThat(new UserRegistered($this->entityId, $email));
}
}
Replay Events (Projection)
use Averor\EventSourcing\Projection;
class UserProjection implements Projection
{
public function __invoke(UserRegistered $event)
{
// Update your read model (e.g., database, cache)
}
}
EventSourcedEntity as the base class for domain aggregates.
class Order extends EventSourcedEntity
{
public function place(array $items): void
{
$this->recordThat(new OrderPlaced(
$this->entityId,
$items,
now()
));
}
}
$order = Order::retrieve($orderId); // Reconstructs state from stored events
class OrderProjection implements Projection
{
public function __invoke(OrderPlaced $event)
{
OrderReadModel::create([
'id' => $event->orderId,
'status' => 'placed',
'created_at' => $event->occurredOn,
]);
}
}
EventSourcing::addProjection(OrderPlaced::class, OrderProjection::new());
EventSourcing::publish(new UserRegistered($userId, $email));
EventSourcing::subscribeToStream($userId, function ($event) {
// Handle real-time events (e.g., WebSocket updates)
});
class UserRegistered implements Event
{
public static function version(): string
{
return '1.0';
}
}
EventSourcing::migrate() to handle schema changes in stored events.EventSourcing::publish(new UserRegistered($userId, $email))
->onQueue('events');
class ProcessEventsJob implements ShouldQueue
{
public function handle()
{
EventSourcing::process();
}
}
EventSourcing::setStorage(new DatabaseEventStorage(
new Connection('mysql')
));
$storage = Mockery::mock(StorageInterface::class);
EventSourcing::setStorage($storage);
$this->assertDatabaseHas('order_read_models', [
'id' => $orderId,
'status' => 'placed',
]);
Event Ordering
Projection Side Effects
EventSourcing::processWithTransaction() or wrap in a DB transaction.Event Versioning Mismatches
Memory Leaks
EventSourcing::setDebug(true);
$events = EventSourcing::getEventsForStream($streamId);
dd($events);
EventSourcing::addProjectionListener(OrderPlaced::class, function ($event) {
logger()->debug("Projection triggered for OrderPlaced", ['event' => $event]);
});
Default Storage The package defaults to an in-memory store. For production, configure a persistent store (e.g., database, Redis):
'storage' => [
'driver' => 'database',
'table' => 'event_store',
],
Projection Paths Auto-discovery of projections requires proper namespace configuration:
'projections' => [
'paths' => [
app_path('Projections'),
],
],
Custom Event Handlers
Extend EventHandlerInterface to add pre/post-processing:
class LoggingHandler implements EventHandlerInterface
{
public function handle($event)
{
logger()->info("Event published", ['event' => $event]);
}
}
EventSourcing::addEventHandler(new LoggingHandler());
Event Serialization Override serialization for complex events:
class ComplexEvent implements Event
{
public static function serialize($event): string
{
return json_encode([
'data' => $event->data,
'metadata' => $event->metadata,
]);
}
}
Custom Projection Storage
Implement ProjectionStorageInterface for non-database projections (e.g., Elasticsearch):
class ElasticsearchProjectionStorage implements ProjectionStorageInterface
{
public function save($projection, $event)
{
// Index event in Elasticsearch
}
}
How can I help you explore Laravel packages today?