{"id":40669506,"url":"https://github.com/gosuperscript/lese","last_synced_at":"2026-01-21T09:30:55.193Z","repository":{"id":56968893,"uuid":"264903034","full_name":"gosuperscript/lese","owner":"gosuperscript","description":"Laravel Event Sourcing and Eventstore (lese) Bridge","archived":false,"fork":false,"pushed_at":"2020-05-21T09:11:43.000Z","size":70,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-04-02T17:05:21.390Z","etag":null,"topics":["aggregate","event-sourcing","eventstore","laravel","php","projection"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gosuperscript.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-18T10:17:24.000Z","updated_at":"2024-04-02T17:05:21.391Z","dependencies_parsed_at":"2022-08-21T10:50:38.383Z","dependency_job_id":null,"html_url":"https://github.com/gosuperscript/lese","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/gosuperscript/lese","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gosuperscript%2Flese","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gosuperscript%2Flese/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gosuperscript%2Flese/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gosuperscript%2Flese/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gosuperscript","download_url":"https://codeload.github.com/gosuperscript/lese/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gosuperscript%2Flese/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28631101,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-21T04:47:28.174Z","status":"ssl_error","status_checked_at":"2026-01-21T04:47:22.943Z","response_time":86,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aggregate","event-sourcing","eventstore","laravel","php","projection"],"created_at":"2026-01-21T09:30:50.402Z","updated_at":"2026-01-21T09:30:54.636Z","avatar_url":"https://github.com/gosuperscript.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Laravel Event Sourcing and Eventstore (lese) Bridge\n\nOr German for `read` which is somewhat applicable to Event Sourcing. It's almost a good name.\n\nThis package swaps out the Event and Snapshot storage model for [Laravel Event Souring](https://docs.spatie.be/laravel-event-sourcing/v1/getting-familiar-with-event-sourcing/introduction) with [EventStore](https://eventstore.com/). EventStore has a few advantages over a database in that it is purpose built for event sourcing. \n\nThe package also includes a subscribe command so that you may listen to events origination from other services in your system. \n\n## Installation\n\nFirst of all let's bring in the package and Laravel Event Sourcing into our Laravel app. \n\n```bash\ncomposer require digitalrisks/lese\n```\n\nThen publish the Laravel Event Sourcing and Lese configuration files.\n\n```bash\nphp artisan vendor:publish --provider=\"Spatie\\EventSourcing\\EventSourcingServiceProvider\" --tag=\"config\"\nphp artisan vendor:publish --provider=\"DigitalRisks\\Lese\\LeseServiceProvider\" --tag=\"config\"\n```\n\nThen jump into `config/event-sourcing.php` to configure EventStore as our event and snapshot storage repositories.\n\n```php\n    /*\n     * This class is responsible for storing events. To add extra behaviour you\n     * can change this to a class of your own. The only restriction is that\n     * it should implement \\Spatie\\EventSourcing\\StoredEventRepository.\n     */\n    'stored_event_repository' =\u003e \\DigitalRisks\\Lese\\EventStoreStoredEventRepository::class,\n\n    /*\n     * This class is responsible for storing snapshots. To add extra behaviour you\n     * can change this to a class of your own. The only restriction is that\n     * it should implement \\Spatie\\EventSourcing\\StoredEventRepository.\n     */\n    'snapshot_repository' =\u003e \\DigitalRisks\\Lese\\EventStoreSnapshotRepository::class,\n```\n\n## Configuration\n\nThis is the default content of the config file that will be published at `config/lese.php`\n\n```php\n\u003c?php\n\nreturn [\n    /**\n     * The EventStore connection to use when subscribing to events from external\n     * services. Works with TCP or TLS connections.\n     */\n    'tcp_url' =\u003e env('EVENTSTORE_TCP_URL', 'tcp://admin:changeit@localhost:1113'),\n\n    /**\n     * The EventStore connection to use when publishing and reconstituting\n     * aggregates. Supports HTTP or HTTPS.\n     */\n    'http_url' =\u003e env('EVENTSTORE_HTTP_URL', 'http://admin:changeit@localhost:2113'),\n\n    /**\n     * Listen to these streams when running `event-sourcing:subscribe`. Uses\n     * a comma delimetered list from the environment as default.\n     */\n    'subscription_streams' =\u003e array_filter(explode(',', env('EVENTSTORE_SUBSCRIPTION_STREAMS'))),\n\n    /**\n     * Used as the group when connecting to an EventStore persisten subscription.\n     */\n    'group' =\u003e env('EVENTSTORE_SUBSCRIPTION_GROUP', env('APP_NAME', 'laravel')),\n\n    /**\n     * By default Aggregate classes are mapped to a category name based on their\n     * class name. Example App\\Aggregates\\AccountAggregate would be published\n     * to an account-uuid stream. This allows you to implicitly map classes\n     * to categories so that it could be published to account_v2-uuid.\n     */\n    'aggregate_category_map' =\u003e [],\n\n    /**\n     * If not using aggregates, events need to mapped to streams to be\n     * published. An example would be the AccoutCreated event\n     * could be published on to the accounts stream.\n     */\n    'event_stream_map' =\u003e [],\n\n    /**\n     * If the event is not mapped to a stream,\n     * publish to this stream by default.\n     */\n    'default_stream' =\u003e env('EVENTSTORE_DEFAULT_STREAM', 'events'),\n\n    /**\n     * The stream to listen to when replaying all events. Instead of using\n     * $all, it is recommended to setup a project which emits events\n     * from various streams into a stream specific for your app.\n     */\n    'all' =\u003e env('EVENTSTORE_ALL', '$all'),\n\n    /**\n     * Number of events to read in a single API\n     * call when reconstituting events.\n     */\n    'read_size' =\u003e env('EVENTSTORE_READ_SIZE', 4096),\n\n    /**\n     * Number of events to read in a single TCP\n     * message when replaying all events.\n     */\n    'batch_size' =\u003e env('EVENTSTORE_BATCH_SIZE', 4096),\n\n    /**\n     * This class contains a few callbacks to govern the bridge between EventStore and the\n     * Laravel Event Sourcing package. You can customise the class to include your\n     * own business logic. It should extend DigitalRisks\\Lese\\Lese\n     */\n    'lese_class' =\u003e env('EVENTSTORE_LESE_CLASS', DigitalRisks\\Lese\\Lese::class),\n];\n```\n\n## Getting Started\n\nI would recommend getting familiar with Event Sourcing by reading through the excellent guide at https://docs.spatie.be/laravel-event-sourcing/v3/introduction/. \n\nThe next step is to get a local version of the EventStore running (you won't need a database). There are instructions for every platform at https://eventstore.com/docs/getting-started/index.html\n\nLet's now create a simple event:\n\n```php\n\u003c?php\n\nnamespace App\\Events;\n\nuse Spatie\\EventSourcing\\ShouldBeStored;\n\nclass MoneyAdded implements ShouldBeStored\n{\n    /** @var string */\n    public $accountUuid;\n\n    /** @var int */\n    public $amount;\n\n    public function __construct(string $accountUuid, int $amount)\n    {\n        $this-\u003eaccountUuid = $accountUuid;\n\n        $this-\u003eamount = $amount;\n    }\n}\n```\n\nAnd fire it off:\n\n```php\n\u003c?php\n\nevent(new MoneyAdded('21410-81231', 100))\n```\n\nLet's create a simple Projection to put account information in a database.\n\n```php\n\u003c?php\n\nnamespace App\\Projectors;\n\nuse App\\Account;\nuse App\\Events\\AccountCreated;\nuse App\\Events\\AccountDeleted;\nuse App\\Events\\MoneyAdded;\nuse App\\Events\\MoneySubtracted;\nuse Spatie\\EventSourcing\\Projectors\\Projector;\nuse Spatie\\EventSourcing\\Projectors\\ProjectsEvents;\n\nclass AccountsProjector implements Projector\n{\n    use ProjectsEvents;\n\n    public function onMoneyAdded(MoneyAdded $event)\n    {\n        $account = Account::uuid($event-\u003eaccountUuid);\n\n        $account-\u003ebalance += $event-\u003eamount;\n\n        $account-\u003esave();\n    }\n}\n```\n\nAnd also send an event to the FBI for large transactions:\n\n```php\n\u003c?php\n\nnamespace App\\Reactors;\n\nuse App\\Account;\nuse App\\Events\\MoneyAdded;\nuse App\\Mail\\BigAmountAddedMail;\nuse Illuminate\\Support\\Facades\\Mail;\nuse Spatie\\EventSourcing\\EventHandlers\\EventHandler;\nuse Spatie\\EventSourcing\\EventHandlers\\HandlesEvents;\n\nclass BigAmountAddedReactor implements EventHandler\n{\n    use HandlesEvents;\n\n    public function onMoneyAdded(MoneyAdded $event)\n    {\n        if ($event-\u003eamount \u003c 5000) {\n            return;\n        }\n\n        $account = Account::uuid($event-\u003eaccountUuid);\n\n        Mail::to('director@fbi.gov')-\u003esend(new BigAmountAddedMail($account, $event-\u003eamount));\n    }\n}\n```\n\nIf, later on, the business wants to have an attribute on the model for `number_of_deposits`, we update the Projector:\n\n```php\n\u003c?php\n\nnamespace App\\Projectors;\n\nuse App\\Account;\nuse App\\Events\\AccountCreated;\nuse App\\Events\\AccountDeleted;\nuse App\\Events\\MoneyAdded;\nuse App\\Events\\MoneySubtracted;\nuse Spatie\\EventSourcing\\Projectors\\Projector;\nuse Spatie\\EventSourcing\\Projectors\\ProjectsEvents;\n\nclass AccountsProjector implements Projector\n{\n    use ProjectsEvents;\n\n    public function onMoneyAdded(MoneyAdded $event)\n    {\n        $account = Account::uuid($event-\u003eaccountUuid);\n\n        $account-\u003ebalance += $event-\u003eamount;\n      \t$account-\u003enumber_of_deposits += 1;\n\n        $account-\u003esave();\n    }\n}\n```\n\nAnd re-run the events:\n\n```bash\nphp artisan event-sourcing:replay App\\\\Projectors\\\\AccountsProjector\n```\n\nLearn more how to use Event Sourcing by following the guides at https://docs.spatie.be/laravel-event-sourcing/v3/introduction/\n\n## Aggregates\n\n\u003e If you're not using aggregates, you can skip this section.\n\nIn order for the EventStore repositories to fetch the events and/or snapshots related to an aggregate, it needs to know about the aggregate. To do this we simply override the two methods below to initiate the repostiory and pass in the aggregate.\n\n```php\nprotected function getStoredEventRepository(): StoredEventRepository\n{\n    return resolve(EventStoreStoredEventRepository::class, ['aggregate' =\u003e $this]);\n}\n\nprotected function getSnapshotRepository(): SnapshotRepository\n{\n    return resolve(EventStoreSnapshotRepository::class, ['aggregate' =\u003e $this]);\n}\n```\n\n## Subscribing to Streams\n\nThe package also includes a long-running process, similar to [Pub / Sub](https://laravel.com/docs/7.x/redis#pubsub)  with `php artisan redis:subscribe` whereby you can listen to events from a stream.\n\nLet's say this is the the `accounts-service` but we wanted listen for events from the `quotes-service`. When a quote is converted, we want to create an account for it. \n\n\u003e Careful: If you listen to events that you publish, projectors and reactors will process them once in your application and again when they come back down the stream. It's recommended you subscribe only to streams that you don't publish to. \n\nIn `config/lese.php` we would add the stream for quote converted events:\n\n```php\n/**\n * Listen to these streams when running `event-sourcing:subscribe`. Uses\n * a comma delimetered list from the environment as default.\n */\n'subscription_streams' =\u003e ['$et-Events\\Quotes\\QuoteConverted'],\n```\n\nWe could then run the following command to create the persistent subscriptions on EventStore\n\n```bash\nphp artisan event-sourcing:reset\n```\n\n\u003e Careful: When resetting persistent subscriptions, it will start from the first event again. If you have reactors, you should go into the eventstore admin and set the `start from` value to the event number you want to start from.\n\nAnd finally start the subscribe process\n\n```bash\nphp artisan event-sourcing:subscribe\n```\n\n## Event Metadata\n\nMetadata can help trace events around your system. You can include any of the following traits on your event to attach metadata automatically\n\n* `AddsHerokuMetadata`\n* `AddsLaravelMetadata`\n* `AddsUserMetaData`\n\nOr you can define your own methods to collect metadata. Any method with the `@metadata` annotation will be called:\n\n``` php\n\u003c?php\n\nnamespace App\\Events;\n\nuse DigitalRisks\\Lese\\MetaData\\HasMetaData;\nuse DigitalRisks\\Lese\\MetaData\\CollectsMetaData;\n\nuse DigitalRisks\\Lese\\MetaData\\AddsHerokuMetadata;\nuse DigitalRisks\\Lese\\MetaData\\AddsLaravelMetadata;\nuse DigitalRisks\\Lese\\MetaData\\AddsUserMetaData;\n\nuse Spatie\\EventSourcing\\ShouldBeStored;\n\nclass MoneyAdded implements ShouldBeStored, HasMetaData\n{\n    use CollectsMetaData, AddsUserMetaData, AddsHerokuMetadata, AddsLaravelMetadata;\n\n    /** @var string */\n    public $accountUuid;\n\n    /** @var int */\n    public $amount;\n\n    public function __construct(string $accountUuid, int $amount)\n    {\n        $this-\u003eaccountUuid = $accountUuid;\n\n        $this-\u003eamount = $amount;\n    }\n  \n    /** @metadata */\n    public function collectIpMetadata()\n    {\n        return [\n            'ip' =\u003e $_SERVER['REMOTE_ADDR'],\n        ];\n    }\n}\n```\n\n## Changelog\n\nPlease see [CHANGELOG](../../releases) for more information what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n## Security\n\nIf you discover any security related issues, please email craig.morris@digitalrisks.co.uk instead of using the issue tracker.\n\n## Credits\n\n- [Freek Van der Herten](https://github.com/freekmurze)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgosuperscript%2Flese","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgosuperscript%2Flese","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgosuperscript%2Flese/lists"}