{"id":22284055,"url":"https://github.com/bwaidelich/dcb-eventstore","last_synced_at":"2026-04-02T19:04:19.043Z","repository":{"id":172698457,"uuid":"649632500","full_name":"bwaidelich/dcb-eventstore","owner":"bwaidelich","description":"Implementation of the Dynamic Consistency Boundary pattern described by Sara Pellegrini","archived":false,"fork":false,"pushed_at":"2025-12-19T17:00:07.000Z","size":166,"stargazers_count":14,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-12-21T00:46:34.058Z","etag":null,"topics":["dcb","ddd","domaindrivendesign","dynamicconsistencyboundary","eventsourcing"],"latest_commit_sha":null,"homepage":"","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/bwaidelich.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["bwaidelich"],"custom":["https://www.paypal.me/bwaidelich"]}},"created_at":"2023-06-05T09:52:57.000Z","updated_at":"2025-12-19T17:00:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"30a8c1c5-4dce-4869-b1a0-c7f68651e27f","html_url":"https://github.com/bwaidelich/dcb-eventstore","commit_stats":null,"previous_names":["bwaidelich/dcb-eventstore"],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/bwaidelich/dcb-eventstore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bwaidelich%2Fdcb-eventstore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bwaidelich%2Fdcb-eventstore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bwaidelich%2Fdcb-eventstore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bwaidelich%2Fdcb-eventstore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bwaidelich","download_url":"https://codeload.github.com/bwaidelich/dcb-eventstore/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bwaidelich%2Fdcb-eventstore/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28844952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-28T10:53:21.605Z","status":"ssl_error","status_checked_at":"2026-01-28T10:53:20.789Z","response_time":57,"last_error":"SSL_read: 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":["dcb","ddd","domaindrivendesign","dynamicconsistencyboundary","eventsourcing"],"created_at":"2024-12-03T16:43:51.076Z","updated_at":"2026-01-28T11:19:06.636Z","avatar_url":"https://github.com/bwaidelich.png","language":"PHP","funding_links":["https://github.com/sponsors/bwaidelich","https://www.paypal.me/bwaidelich"],"categories":[],"sub_categories":[],"readme":"# Dynamic Consistency Boundary Event Store\n\nInterfaces and types for Event Stores implementing [Dynamic Consistency Boundaries](https://dcb.events/) according to the [specification](https://dcb.events/specification/).\n\nTo actually commit events, a corresponding [adapter package](#adapters) is required!\n\n## Adapters\n\nThe following adapter implementations can be used with this package:\n\n| Adapter                                                                                       | Storage/Engine                                                                      | Transport, SDK                                                                 |\n|-----------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------------------------------------------------------------------|\n| [ekvedaras/dcb-eventstore-illuminate](https://github.com/ekvedaras/dcb-eventstore-illuminate) | SQLite, MySQL/MariaDB, PostgreSQL                                                   | [Laravel Database](https://laravel.com/docs/12.x/database)                     |\n| [wwwision/dcb-eventstore-doctrine](https://github.com/bwaidelich/dcb-eventstore-doctrine)     | SQLite, MySQL/MariaDB, PostgreSQL                                                   | [Doctrine DBAL](https://www.doctrine-project.org/projects/dbal.html)           |\n| [wwwision/dcb-eventstore-esdb](https://github.com/bwaidelich/dcb-eventstore-esdb)             | [EventSourcing Database](https://www.eventsourcingdb.io/) (file based, proprietary) | HTTP                                                                           |\n| [wwwision/dcb-eventstore-umadb](https://github.com/bwaidelich/dcb-eventstore-umadb)           | [UmaDB](https://umadb.io/) (file based, open source)                                | Rust FFI (via custom [PHP Extension](https://github.com/bwaidelich/umadb-php)) |\n| [wwwision/dcb-eventstore-umadb-grpc](https://github.com/bwaidelich/dcb-eventstore-umadb-grpc) | [UmaDB](https://umadb.io/) (file based, open source)                                | gRPC (via official [PHP Extension](https://github.com/grpc/grpc))              |\n\n_Feel free to contact me or extend this list via [pull request](https://github.com/bwaidelich/dcb-eventstore/pulls) if you wrote another adapter implementation_\n\n## Usage\n\nInstall via [composer](https://getcomposer.org):\n\n```shell\ncomposer require wwwision/dcb-eventstore\n```\n\n### Create Event Store instance\n\nInstantiation of Event Stores depend on the corresponding [adapter package](#adapters).\nThis package comes with an in-memory Event Store for testing, that can be created like so: \n\n```php\n$eventStore = \\Wwwision\\DCBEventStore\\InMemoryEventStore\\InMemoryEventStore::create();\n```\n\n### Read Events\n\nThe `read()` function allows to read events.\nTo obtain a stream of all events in the Event Store, `Query::all()` can be used:\n\n```php\nuse Wwwision\\DCBEventStore\\Query\\Query;\n\n$eventStream = $eventStore-\u003eread(Query::all());\n```\n\nThe result is an iterable stream of `SequencedEvents`, that contain the originally appended event, the `position` of that event in the stream and some metadata:\n\n```php\n// ...\nforeach ($eventStream as $sequencedEvent) {\n  $tags = implode(', ', $sequencedEvent-\u003eevent-\u003etags-\u003etoStrings());\n  $metadata = print_r($sequencedEvent-\u003eevent-\u003emetadata-\u003evalue, true);\n  echo \"Position: {$sequencedEvent-\u003eposition-\u003evalue}\\n\";\n  echo \"Event type: {$sequencedEvent-\u003eevent-\u003etype}\\n\";\n  echo \"Event tags: $tags\\n\";\n  echo \"Recorded at: {$sequencedEvent-\u003erecordedAt-\u003eformat(DATE_ATOM)}\\n\";\n  echo \"Event data: {$sequencedEvent-\u003eevent-\u003edata}\\n\";\n  echo \"Event metadata: $metadata\\n\";\n  echo \"----\\n\";\n}\n```\n\n#### Filter events\n\n`Query::fromItems()` can be used to filter events, by their type: \n\n```php\nuse Wwwision\\DCBEventStore\\Query\\Query;\nuse Wwwision\\DCBEventStore\\Query\\QueryItem;\n\n// return only events of the type \"SomeEventType\"\n$eventStore-\u003eread(\n  Query::fromItems(\n    QueryItem::create(eventTypes: 'SomeEventType')\n  )\n);\n```\n\n...by tags:\n\n```php\nuse Wwwision\\DCBEventStore\\Query\\Query;\nuse Wwwision\\DCBEventStore\\Query\\QueryItem;\n\n// return only events that are tagged \"some:tag\"\n$eventStore-\u003eread(\n  Query::fromItems(\n    QueryItem::create(tags: 'some:tag')\n  )\n);\n```\n\n...or by a combination:\n\n```php\nuse Wwwision\\DCBEventStore\\Query\\Query;\nuse Wwwision\\DCBEventStore\\Query\\QueryItem;\n\n// return only events that are tagged with \"some:tag\" AND \"some:other-tag\" and are of type \"SomeType\" OR \"SomeOtherType\"\n$eventStore-\u003eread(\n  Query::fromItems(\n    QueryItem::create(eventTypes: ['SomeType', 'SomeOtherType'], tags: ['some:tag', 'some:other-tag'])\n  )\n);\n```\n\nMultiple `QueryItem`s can be specified to filter events that match _any_ of the specified items:\n\n```php\nuse Wwwision\\DCBEventStore\\Query\\Query;\nuse Wwwision\\DCBEventStore\\Query\\QueryItem;\n\n// return only events that are tagged \"some:tag\" and are of type \"SomeType\" OR that are tagged \"some:other-tag\" and are of type \"SomeOtherType\"\n$eventStore-\u003eread(\n  Query::fromItems(\n    QueryItem::create(eventTypes: 'SomeType', tags: 'some:tag'),\n    QueryItem::create(eventTypes: 'SomeOtherType', tags: 'some:other-tag')\n  )\n);\n```\n\n\u003e [!NOTE]\n\u003e Tags within a single QueryItem are conjunctive (combined with AND) while individual QueryItems are disjunctive (combined with OR)\n\n### Read Options\n\nAn optional 2nd argument can be specified in order to define custom limits/orderings:\n\n```php\nuse Wwwision\\DCBEventStore\\Query\\Query;\nuse Wwwision\\DCBEventStore\\ReadOptions;\n\n// read 100 events starting from sequence position 1234:\n$eventStore-\u003eread(\n  Query::all(),\n  ReadOptions::create(\n    from: 1234,\n    limit: 100,\n  )\n);\n```\n\nBy default events are always ordered by their `SequencePosition` in _ascending_ order i.e. FIFO.\nSometimes it can be useful to order events in _descending_ order, for example in order to provide cursor-based pagination:\n\n```php\nuse Wwwision\\DCBEventStore\\Query\\Query;\nuse Wwwision\\DCBEventStore\\ReadOptions;\n\n// read 50 events before (and including) event at sequence number 321\n$eventStore-\u003eread(\n  Query::all(),\n  ReadOptions::create(\n    from: 321,\n    limit: 50,\n    backwards: true,\n  )\n);\n```\n\nThis can also be used to load the last event(s) with a certain type or tag:\n\n```php\nuse Wwwision\\DCBEventStore\\Query\\Query;\nuse Wwwision\\DCBEventStore\\Query\\QueryItem;\nuse Wwwision\\DCBEventStore\\ReadOptions;\n\n// get last \"InvoiceCreated\" event (or NULL if none exists yet)\n$lastInvoiceCreatedEvent = $eventStore-\u003eread(\n  Query::fromItems(\n    QueryItem::create(eventTypes: 'InvoiceCreated')\n  ),\n  ReadOptions::create(\n    limit: 1,\n    backwards: true,\n  )\n)-\u003efirst();\n\n$lastInvoiceNumber = $lastInvoiceCreatedEvent?-\u003eevent-\u003edata-\u003ejsonDecode()['invoiceNumber'] ?? 0;\n```\n\n## Write Events\n\nThe `append()` function allows to write events.\n\n### Unconditional writes\n\nDCB is all about enforcing consistency when appending new events. But in some cases (e.g. when importing data or for testing purposes) it can be necessary to write events without enforcing any constraint.\nTherefor, the `appendCondition` parameter can be left out: \n\n```php\nuse Wwwision\\DCBEventStore\\Event\\Event;\n\n// append a single event without conditions\n$eventStore-\u003eappend(\n  Event::create(\n    type: 'SomeEventType',\n    data: ['foo' =\u003e 'bar', 'bar' =\u003e 'baz'],\n    tags: ['tag1', 'tag2'],\n  )\n);\n```\n\nMultiple events can be written atomically using `Events`:\n\n```php\nuse Wwwision\\DCBEventStore\\Event\\Event;\nuse Wwwision\\DCBEventStore\\Event\\Events;\n\n// append two events atomically without conditions\n$eventStore-\u003eappend(\n  Events::fromArray([\n    Event::create(type: 'SomeEventType', data: 'data1'),\n    Event::create(type: 'SomeOtherEventType', data: 'data2'),\n  ])\n);\n```\n\n### Append Condition\n\nThe following call appends a `ProductDefined` event, but fails if a corresponding event for the same product id was appended previously (or practically at the same time, i.e. this operation ensures transaction safety):\n\n```php\nuse Wwwision\\DCBEventStore\\AppendCondition\\AppendCondition;\nuse Wwwision\\DCBEventStore\\Event\\Event;\nuse Wwwision\\DCBEventStore\\Event\\Events;\nuse Wwwision\\DCBEventStore\\Query\\Query;\nuse Wwwision\\DCBEventStore\\Query\\QueryItem;\n\n// append a single \"ProductDefined\" event only if no corresponding event with the same tag was appended previously\n$eventStore-\u003eappend(\n  Event::create(type: 'ProductDefined', data: ['id' =\u003e 'p123', 'title' =\u003e 'Some product'], tags: ['product:p123']),\n  condition: AppendCondition::create(\n    failIfEventsMatch: Query::fromItems(QueryItem::create(eventTypes: 'ProductDefined', tags: 'product:p123')),\n  ),\n);\n```\n\nIn the previous example, no event in the entire stream must match the specified query – it can be compared with a `NoStream` expectation of a traditional event store.\nBut DCB also supports to specify a \"safe point\" using the optional `after` parameter of the `AppendCondition`:\n\n```php\nuse Wwwision\\DCBEventStore\\AppendCondition\\AppendCondition;\nuse Wwwision\\DCBEventStore\\Event\\Event;\nuse Wwwision\\DCBEventStore\\Event\\Events;\nuse Wwwision\\DCBEventStore\\Query\\Query;\nuse Wwwision\\DCBEventStore\\Query\\QueryItem;\n\n// append a single \"ProductPriceChanged\" event only if no corresponding event with the same tag was appended after the safe point (sequence position 1234)\n$eventStore-\u003eappend(\n  Event::create(type: 'ProductPriceChanged', data: ['id' =\u003e 'p123', 'newPrice' =\u003e 54321], tags: ['product:p123']),\n  condition: AppendCondition::create(\n    failIfEventsMatch: Query::fromItems(QueryItem::create(eventTypes: 'ProductPriceChanged', tags: 'product:p123')),\n    after: 1234,\n  ),\n);\n```\n\n## Higher Level API\n\nThis package mainly implements the low-level DCB specification (see [dcb.events website](https://dcb.events/specification/)).\nIt's highly advised to introduce a higher level abstraction for the usage within the actual application logic.\n\nFeel free to get in touch to see how this can be combined with the idea of [composed projections](https://dcb.events/topics/projections/#composing-projections) in practice!\n\n## Contribution\n\nContributions in the form of [issues](https://github.com/bwaidelich/dcb-eventstore/issues), [pull requests](https://github.com/bwaidelich/dcb-eventstore/pulls) or [discussions](https://github.com/bwaidelich/dcb-eventstore/discussions) are highly appreciated\n\n## License\n\nSee [LICENSE](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbwaidelich%2Fdcb-eventstore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbwaidelich%2Fdcb-eventstore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbwaidelich%2Fdcb-eventstore/lists"}