定制 wwwision/dcb-eventstore 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

wwwision/dcb-eventstore

Composer 安装命令:

composer require wwwision/dcb-eventstore

包简介

Implementation of the Dynamic Consistency Boundary pattern described by Sara Pellegrini

README 文档

README

Interfaces and types for Event Stores implementing Dynamic Consistency Boundaries according to the specification.

To actually commit events, a corresponding adapter package is required!

Adapters

The following adapter implementations can be used with this package:

Adapter Storage/Engine Transport, SDK
ekvedaras/dcb-eventstore-illuminate SQLite, MySQL/MariaDB, PostgreSQL Laravel Database
wwwision/dcb-eventstore-doctrine SQLite, MySQL/MariaDB, PostgreSQL Doctrine DBAL
wwwision/dcb-eventstore-esdb EventSourcing Database (file based, proprietary) HTTP
wwwision/dcb-eventstore-umadb UmaDB (file based, open source) Rust FFI (via custom PHP Extension)
wwwision/dcb-eventstore-umadb-grpc UmaDB (file based, open source) gRPC (via official PHP Extension)

Feel free to contact me or extend this list via pull request if you wrote another adapter implementation

Usage

Install via composer:

composer require wwwision/dcb-eventstore

Create Event Store instance

Instantiation of Event Stores depend on the corresponding adapter package. This package comes with an in-memory Event Store for testing, that can be created like so:

$eventStore = \Wwwision\DCBEventStore\InMemoryEventStore\InMemoryEventStore::create();

Read Events

The read() function allows to read events. To obtain a stream of all events in the Event Store, Query::all() can be used:

use Wwwision\DCBEventStore\Query\Query;

$eventStream = $eventStore->read(Query::all());

The result is an iterable stream of SequencedEvents, that contain the originally appended event, the position of that event in the stream and some metadata:

// ...
foreach ($eventStream as $sequencedEvent) {
  $tags = implode(', ', $sequencedEvent->event->tags->toStrings());
  $metadata = print_r($sequencedEvent->event->metadata->value, true);
  echo "Position: {$sequencedEvent->position->value}\n";
  echo "Event type: {$sequencedEvent->event->type}\n";
  echo "Event tags: $tags\n";
  echo "Recorded at: {$sequencedEvent->recordedAt->format(DATE_ATOM)}\n";
  echo "Event data: {$sequencedEvent->event->data}\n";
  echo "Event metadata: $metadata\n";
  echo "----\n";
}

Filter events

Query::fromItems() can be used to filter events, by their type:

use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;

// return only events of the type "SomeEventType"
$eventStore->read(
  Query::fromItems(
    QueryItem::create(eventTypes: 'SomeEventType')
  )
);

...by tags:

use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;

// return only events that are tagged "some:tag"
$eventStore->read(
  Query::fromItems(
    QueryItem::create(tags: 'some:tag')
  )
);

...or by a combination:

use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;

// return only events that are tagged with "some:tag" AND "some:other-tag" and are of type "SomeType" OR "SomeOtherType"
$eventStore->read(
  Query::fromItems(
    QueryItem::create(eventTypes: ['SomeType', 'SomeOtherType'], tags: ['some:tag', 'some:other-tag'])
  )
);

Multiple QueryItems can be specified to filter events that match any of the specified items:

use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;

// 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"
$eventStore->read(
  Query::fromItems(
    QueryItem::create(eventTypes: 'SomeType', tags: 'some:tag'),
    QueryItem::create(eventTypes: 'SomeOtherType', tags: 'some:other-tag')
  )
);

Note

Tags within a single QueryItem are conjunctive (combined with AND) while individual QueryItems are disjunctive (combined with OR)

Read Options

An optional 2nd argument can be specified in order to define custom limits/orderings:

use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\ReadOptions;

// read 100 events starting from sequence position 1234:
$eventStore->read(
  Query::all(),
  ReadOptions::create(
    from: 1234,
    limit: 100,
  )
);

By default events are always ordered by their SequencePosition in ascending order i.e. FIFO. Sometimes it can be useful to order events in descending order, for example in order to provide cursor-based pagination:

use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\ReadOptions;

// read 50 events before (and including) event at sequence number 321
$eventStore->read(
  Query::all(),
  ReadOptions::create(
    from: 321,
    limit: 50,
    backwards: true,
  )
);

This can also be used to load the last event(s) with a certain type or tag:

use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;
use Wwwision\DCBEventStore\ReadOptions;

// get last "InvoiceCreated" event (or NULL if none exists yet)
$lastInvoiceCreatedEvent = $eventStore->read(
  Query::fromItems(
    QueryItem::create(eventTypes: 'InvoiceCreated')
  ),
  ReadOptions::create(
    limit: 1,
    backwards: true,
  )
)->first();

$lastInvoiceNumber = $lastInvoiceCreatedEvent?->event->data->jsonDecode()['invoiceNumber'] ?? 0;

Write Events

The append() function allows to write events.

Unconditional writes

DCB 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. Therefor, the appendCondition parameter can be left out:

use Wwwision\DCBEventStore\Event\Event;

// append a single event without conditions
$eventStore->append(
  Event::create(
    type: 'SomeEventType',
    data: ['foo' => 'bar', 'bar' => 'baz'],
    tags: ['tag1', 'tag2'],
  )
);

Multiple events can be written atomically using Events:

use Wwwision\DCBEventStore\Event\Event;
use Wwwision\DCBEventStore\Event\Events;

// append two events atomically without conditions
$eventStore->append(
  Events::fromArray([
    Event::create(type: 'SomeEventType', data: 'data1'),
    Event::create(type: 'SomeOtherEventType', data: 'data2'),
  ])
);

Append Condition

The 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):

use Wwwision\DCBEventStore\AppendCondition\AppendCondition;
use Wwwision\DCBEventStore\Event\Event;
use Wwwision\DCBEventStore\Event\Events;
use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;

// append a single "ProductDefined" event only if no corresponding event with the same tag was appended previously
$eventStore->append(
  Event::create(type: 'ProductDefined', data: ['id' => 'p123', 'title' => 'Some product'], tags: ['product:p123']),
  condition: AppendCondition::create(
    failIfEventsMatch: Query::fromItems(QueryItem::create(eventTypes: 'ProductDefined', tags: 'product:p123')),
  ),
);

In 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. But DCB also supports to specify a "safe point" using the optional after parameter of the AppendCondition:

use Wwwision\DCBEventStore\AppendCondition\AppendCondition;
use Wwwision\DCBEventStore\Event\Event;
use Wwwision\DCBEventStore\Event\Events;
use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;

// append a single "ProductPriceChanged" event only if no corresponding event with the same tag was appended after the safe point (sequence position 1234)
$eventStore->append(
  Event::create(type: 'ProductPriceChanged', data: ['id' => 'p123', 'newPrice' => 54321], tags: ['product:p123']),
  condition: AppendCondition::create(
    failIfEventsMatch: Query::fromItems(QueryItem::create(eventTypes: 'ProductPriceChanged', tags: 'product:p123')),
    after: 1234,
  ),
);

Higher Level API

This package mainly implements the low-level DCB specification (see dcb.events website). It's highly advised to introduce a higher level abstraction for the usage within the actual application logic.

Feel free to get in touch to see how this can be combined with the idea of composed projections in practice!

Contribution

Contributions in the form of issues, pull requests or discussions are highly appreciated

License

See LICENSE

wwwision/dcb-eventstore 适用场景与选型建议

wwwision/dcb-eventstore 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 36.72k 次下载、GitHub Stars 达 21, 最近一次更新时间为 2023 年 06 月 05 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 wwwision/dcb-eventstore 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 wwwision/dcb-eventstore 我们能提供哪些服务?
定制开发 / 二次开发

基于 wwwision/dcb-eventstore 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 36.72k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 22
  • 点击次数: 5
  • 依赖项目数: 10
  • 推荐数: 0

GitHub 信息

  • Stars: 21
  • Watchers: 3
  • Forks: 3
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2023-06-05