eventsauce/message-repository-table-schema
Install the Package
composer require eventsauce/message-repository-table-schema
Generate the Migration Run the Artisan command to create a migration file for the event repository table:
php artisan generate:message-repository
This creates a migration with a schema optimized for event sourcing, including columns for:
aggregate_id (UUID)message_type (string)payload (JSON)occurred_on (timestamp)metadata (JSON)Run the Migration
php artisan migrate
First Integration Use the repository in a Laravel service or command:
use EventSauce\EventSourcing\MessageRepository;
use EventSauce\MessageRepositoryTableSchema\MySqlMessageRepository;
$repository = new MySqlMessageRepository(
\DB::connection()->getPdo(),
config('message-repository-table-schema.table_name')
);
// Store an event
$event = new OrderCreated('order-123', ['user_id' => 1]);
$repository->save($event);
Service Provider Setup Register the repository in Laravel’s service container:
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton(MessageRepository::class, function ($app) {
return new MySqlMessageRepository(
$app['db']->connection()->getPdo(),
config('message-repository-table-schema.table_name')
);
});
}
Event Dispatching Bridge Laravel’s events to EventSauce events:
// Example: Dispatch a Laravel event and store it as an EventSauce event
Event::listen('order.created', function ($event) {
$domainEvent = new OrderCreated(
$event->orderId,
['user_id' => $event->userId]
);
app(MessageRepository::class)->save($domainEvent);
});
Event Replaying Replay events for aggregate reconstruction:
public function replayEventsForAggregate(string $aggregateId)
{
$events = app(MessageRepository::class)->getMessagesForAggregate($aggregateId);
foreach ($events as $event) {
$this->applyEvent($event);
}
}
Querying Events Use raw queries or a repository layer for filtering:
// Example: Find events for an aggregate
$events = DB::table('message_store')
->where('aggregate_id', $aggregateId)
->orderBy('occurred_on')
->get();
Aggregate Root Management Use the repository to load and persist aggregate roots:
$aggregate = AggregateRoot::fromHistory(
$repository->getMessagesForAggregate($aggregateId)
);
$aggregate->apply(new OrderCancelled());
$repository->save($aggregate->pullEvents());
Event Projections Materialize read models from stored events:
// Example: Project events to a Laravel model
$events = $repository->getMessagesForAggregate($aggregateId);
foreach ($events as $event) {
$this->projectEventToModel($event);
}
Metadata Handling Attach custom metadata to events:
$event = new OrderCreated('order-123', ['user_id' => 1]);
$event->setMetadata(['source' => 'web', 'version' => '1.0']);
$repository->save($event);
Schema Migrations
Schema::table() with caution, or implement zero-downtime migration strategies (e.g., adding nullable columns first).Event Serialization
__serialize()/__unserialize() or use a library like jms/serializer.Performance with Large Payloads
jsonb type (PostgreSQL) for efficient querying.Concurrency Conflicts
metadata (e.g., version field) or Laravel’s lockForUpdate().Laravel’s Eloquent Conflicts
failed_jobs).Verify Event Storage
Check the message_store table directly:
php artisan tinker
>>> \DB::table('message_store')->latest()->first();
Enable Query Logging Debug slow queries:
DB::enableQueryLog();
$repository->save($event);
dd(DB::getQueryLog());
Test Event Replay Validate replay logic with a small dataset:
$events = $repository->getMessagesForAggregate($aggregateId);
$this->assertCount(3, $events); // Example assertion
Custom Table Names Override the default table name in config:
'table_name' => 'custom_event_store',
Additional Columns Extend the schema via migrations:
Schema::table('message_store', function (Blueprint $table) {
$table->string('event_source')->nullable();
});
Event Filtering Create a repository decorator for custom queries:
class FilteredMessageRepository implements MessageRepository
{
public function getMessagesForAggregate(string $aggregateId, string $eventType = null)
{
// Add custom filtering logic
}
}
Laravel Event Bridge Automate event conversion:
// app/Listeners/ConvertLaravelEventToDomainEvent.php
public function handle($event)
{
$domainEvent = $this->convert($event);
app(MessageRepository::class)->save($domainEvent);
}
Doctrine DBAL Dependency The package uses Doctrine DBAL under the hood. Ensure your Laravel app includes it:
composer require doctrine/dbal
UUID Handling
The schema expects UUIDs. Use Laravel’s Ramsey\Uuid package for consistency:
composer require ramsey/uuid
Timezone Awareness
Ensure occurred_on timestamps use UTC to avoid timezone-related replay issues:
$event->setOccurredOn(new \DateTimeImmutable('now', new \DateTimeZone('UTC')));
How can I help you explore Laravel packages today?