Installation & setup
laravel-event-sourcing can be installed via composer:
composer require spatie/laravel-event-sourcing
You need to publish and run the migrations to create the stored_events
table:
php artisan vendor:publish --provider="Spatie\EventSourcing\EventSourcingServiceProvider" --tag="event-sourcing-migrations" php artisan migrate
You must publish the config file with this command:
php artisan vendor:publish --provider="Spatie\EventSourcing\EventSourcingServiceProvider" --tag="event-sourcing-config"
This is the default content of the config file that will be published at config/event-sourcing.php
:
<?php return [ /* * These directories will be scanned for projectors and reactors. They * will be registered to Projectionist automatically. */ 'auto_discover_projectors_and_reactors' => [ app()->path(), ], /* * This directory will be used as the base path when scanning * for projectors and reactors. */ 'auto_discover_base_path' => base_path(), /* * Projectors are classes that build up projections. You can create them by performing * `php artisan event-sourcing:create-projector`. When not using auto-discovery, * Projectors can be registered in this array or a service provider. */ 'projectors' => [ // App\Projectors\YourProjector::class ], /* * Reactors are classes that handle side-effects. You can create them by performing * `php artisan event-sourcing:create-reactor`. When not using auto-discovery * Reactors can be registered in this array or a service provider. */ 'reactors' => [ // App\Reactors\YourReactor::class ], /* * A queue is used to guarantee that all events get passed to the projectors in * the right order. Here you can set of the name of the queue. */ 'queue' => env('EVENT_PROJECTOR_QUEUE_NAME', null), /* * When a Projector or Reactor throws an exception the event Projectionist can catch it * so all other projectors and reactors can still do their work. The exception will * be passed to the `handleException` method on that Projector or Reactor. */ 'catch_exceptions' => env('EVENT_PROJECTOR_CATCH_EXCEPTIONS', false), /* * This class is responsible for storing events in the EloquentStoredEventRepository. * To add extra behaviour you can change this to a class of your own. It should * extend the \Spatie\EventSourcing\StoredEvents\Models\EloquentStoredEvent model. */ 'stored_event_model' => Spatie\EventSourcing\StoredEvents\Models\EloquentStoredEvent::class, /* * This class is responsible for storing events. To add extra behaviour you * can change this to a class of your own. The only restriction is that * it should implement \Spatie\EventSourcing\StoredEvents\Repositories\EloquentStoredEventRepository. */ 'stored_event_repository' => Spatie\EventSourcing\StoredEvents\Repositories\EloquentStoredEventRepository::class, /* * This class is responsible for storing snapshots. To add extra behaviour you * can change this to a class of your own. The only restriction is that * it should implement \Spatie\EventSourcing\Snapshots\EloquentSnapshotRepository. */ 'snapshot_repository' => Spatie\EventSourcing\Snapshots\EloquentSnapshotRepository::class, /* * This class is responsible for storing events in the EloquentSnapshotRepository. * To add extra behaviour you can change this to a class of your own. It should * extend the \Spatie\EventSourcing\Snapshots\EloquentSnapshot model. */ 'snapshot_model' => Spatie\EventSourcing\Snapshots\EloquentSnapshot::class, /* * This class is responsible for handling stored events. To add extra behaviour you * can change this to a class of your own. The only restriction is that * it should implement \Spatie\EventSourcing\StoredEvents\HandleDomainEventJob. */ 'stored_event_job' => Spatie\EventSourcing\StoredEvents\HandleStoredEventJob::class, /* * Similar to Relation::enforceMorphMap() this option will make sure that every event has a * corresponding alias defined. Otherwise, an exception is thrown * if you try to persist an event without alias. */ 'enforce_event_class_map' => false, /* * Similar to Relation::morphMap() you can define which alias responds to which * event class. This allows you to change the namespace or class names * of your events but still handle older events correctly. */ 'event_class_map' => [], /* * This class is responsible for serializing events. By default an event will be serialized * and stored as json. You can customize the class name. A valid serializer * should implement Spatie\EventSourcing\EventSerializers\EventSerializer. */ 'event_serializer' => Spatie\EventSourcing\EventSerializers\JsonEventSerializer::class, /* * These classes normalize and restore your events when they're serialized. They allow * you to efficiently store PHP objects like Carbon instances, Eloquent models, and * Collections. If you need to store other complex data, you can add your own normalizers * to the chain. See https://symfony.com/doc/current/components/serializer.html#normalizers */ 'event_normalizers' => [ Spatie\EventSourcing\Support\CarbonNormalizer::class, Spatie\EventSourcing\Support\ModelIdentifierNormalizer::class, Symfony\Component\Serializer\Normalizer\DateTimeNormalizer::class, Symfony\Component\Serializer\Normalizer\ArrayDenormalizer::class, Spatie\EventSourcing\Support\ObjectNormalizer::class, ], /* * In production, you likely don't want the package to auto-discover the event handlers * on every request. The package can cache all registered event handlers. * More info: * https://spatie.be/docs/laravel-event-sourcing/v7/advanced-usage/discovering-projectors-and-reactors#content-caching-discovered-projectors-and-reactors * * Here you can specify where the cache should be stored. */ 'cache_path' => base_path('bootstrap/cache'), /* * When storable events are fired from aggregates roots, the package can fire off these * events as regular events as well. */ 'dispatch_events_from_aggregate_roots' => false, ];
The package will scan all classes of your project to automatically discover projectors and reactors. In a production environment you probably should cache auto discovered projectors and reactors.
It's recommended that you set up a queue. Specify the connection name in the queue
key of the event-sourcing
config file. This queue will be used to guarantee that the events will be processed by all projectors in the right order. You should make sure that the queue will process only one job at a time. In a local environment, where events have a very low chance of getting fired concurrently, it's probably ok to just use the sync
driver.
When using Laravel Horizon you can update your horizon.php
config file as such:
'environments' => [ 'production' => [ // ... 'event-sourcing-supervisor-1' => [ 'connection' => 'redis', 'queue' => [env('EVENT_PROJECTOR_QUEUE_NAME')], 'balance' => 'simple', 'processes' => 1, 'tries' => 3, ], ], 'local' => [ // ... 'event-sourcing-supervisor-1' => [ 'connection' => 'redis', 'queue' => [env('EVENT_PROJECTOR_QUEUE_NAME')], 'balance' => 'simple', 'processes' => 1, 'tries' => 3, ], ], ],