gilsegura/shared
Composer 安装命令:
composer require gilsegura/shared
包简介
Framework-agnostic DDD, Event Sourcing and CQRS building blocks: domain value objects, aggregates, event store, command/query buses, criteria DSL and read models.
README 文档
README
Framework-agnostic building blocks for DDD, CQRS and event sourcing. It provides
the domain primitives (value objects, events, aggregates), the command/query and
event bus contracts, the event store and upcasting contracts, a criteria DSL for
querying, and read-model projection support — all as plain PHP, with no
framework dependency. The gilsegura/shared-bundle package wires these onto
Symfony, Messenger and Doctrine.
Installation
composer require gilsegura/shared
Domain primitives
Shared\Domain holds the value objects and the event envelope every other piece
builds on:
Uuid,Email,HashedPassword,NotEmptyString,DateTimeImmutable— immutable value objects with validation and equality.DomainEventInterface— marker for domain events; events are serializable so they can be stored and replayed.DomainMessage— wraps a domain event (payload) together with the aggregateid,playhead,MetadataandrecordedAt. Thetypeis derived from the event class. This is the unit the event store persists and the buses carry.DomainEventStream— an ordered, iterable stream ofDomainMessages.Metadata— arbitrary key/value context attached to a message.
Command and query handling
Shared\CommandHandling defines the CQRS contracts, with no transport opinion:
CommandInterface/QueryInterface— markers for messages.CommandHandlerInterface/QueryHandlerInterface— markers for handlers, generic over the message they handle.CommandBusInterface/QueryBusInterface— the buses a use case depends on. An adapter (e.g. in the bundle) provides the concrete bus.
Event handling
Shared\EventHandling is the synchronous, in-process event bus:
EventListenerInterface— a listener invoked with aDomainMessage.EventBusInterface— publishes aDomainEventStreamto its listeners.SimpleEventBus— publishes to its listeners in order, fail-fast: the first listener that throws stops the dispatch.
Event sourcing
Shared\EventSourcing turns event streams into aggregates and back:
AbstractEventSourcedAggregateRoot— base aggregate.apply()records an event and routes it to anapplyXxxmethod resolved from the event's short name;uncommittedEvents()returns what to persist;initialize()rebuilds state by replaying a stream onto the aggregate, advancing the playhead from its current position (a fresh aggregate replays from the start; one restored from a snapshot replays only the events after it). Child entities are reachable throughchildEntities().AbstractEventSourcedEntity— same apply mechanics for entities nested inside an aggregate.AbstractEventSourcingRepository— delegates reading to a loader and writing to a register, so it neither reads the stream nor appends, publishes or snapshots itself: both are decorated seams.AggregateRootFactoryInterface+ReflectionAggregateRootFactory/PublicConstructorAggregateRootFactory— turn a given stream into an aggregate. A factory only transforms a stream; it does not fetch anything.AggregateRootLoaderInterface— loads a fully rebuilt aggregate by id. Unlike a factory, a loader fetches what it needs and produces the aggregate, so it takes the id. This is the read seam the repository depends on and the seam snapshotting decorates.Loader\EventStoreAggregateRootLoader— the base loader: fetches the full stream from the event store and hands it to the factory.AggregateRootRegisterInterface+Register\EventStoreAggregateRootRegister— the write seam: decorate the uncommitted events, append them and publish them. Snapshotting decorates this too, symmetric with the loader.EventStreamDecoratorInterface+MetadataEnricher— decorate the outgoing stream, e.g. to enrich every message's metadata.
Event store
Shared\EventStore is the persistence contract for streams:
EventStoreInterface—load/appendaDomainEventStreamby aggregate id, withStreamNotFoundException/StreamAlreadyExistsException.EventStoreManagerInterface— visiting/streaming events for replay and maintenance.EventVisitorInterface/CallableEventVisitor— visit matching events.
Upcasting
Shared\Upcasting keeps old event shapes loadable as the schema evolves:
UpcasterInterface— transforms aDomainMessageinto its newer shape, or returns it unchanged when the event is not its concern.SequentialUpcasterChain— runs the message through its upcasters in order, each receiving the output of the previous one. With no upcasters the message passes through unchanged.UpcastingEventStore— decorates an event store so events are upcast both when they are loaded (load) and when they are visited (visitEvents), so a rebuild sees the same current shapes a load produces. An empty chain is a transparent pass-through.
Snapshotting
Shared\Snapshotting makes loading long-lived aggregates cheap by skipping the
replay of their whole history. It is opt-in: without it, aggregates always rebuild
from the full stream.
Snapshot— a point-in-time capture of an aggregate at a given playhead.SnapshotStoreInterface— the persistence port for snapshots, keyed by aggregate id (the "where"). It only stores and retrieves; it holds no policy.SnapshotStrategyInterface+EventCountSnapshotStrategy— decide when a snapshot is due from the current playhead (the "when"). The event-count strategy snapshots each time the aggregate crosses a multiple of N events.SnapshotAggregateRootLoader— a loader that decorates another loader: when a snapshot exists it restores the captured aggregate and replays only the events recorded after it; otherwise it delegates a full rebuild to the inner loader. Composed like the upcasting store:Snapshot(EventStore(...)). This saves both the read and the replay of everything up to the snapshot.SnapshotAggregateRootRegister— the write-side counterpart: a register that decorates another register, delegating the append-and-publish first, then asking the strategy whether a snapshot is due and capturing it. Composed the same way, so loading and saving are symmetric, decorated seams.
The aggregate is unaware of snapshotting throughout: capturing and restoring its state is done from the outside.
Criteria
Shared\Criteria is a small DSL for expressing filters and ordering in domain
terms, independent of any database:
- Composites
AndX/OrXcombine comparisons such asEqId,EqEmail,EqPlayhead,GtePlayhead,ByPlayhead,ByRecordedAt. OrderXexpresses sorting.- The
Exprnamespace is the lower-level expression tree the comparisons map to.
An infrastructure adapter translates these into a concrete query (the bundle, for example, converts them to Doctrine criteria).
Queries and read models
Shared\Query—QueryBuilderbuilds a typed query from criteria;CollectionQuery/SingleResultQueryexpress the expected result shape. ACollectionQuery<T>carries aPagination(offset + limit, validated) and yields aPage<T>(the items plus the total), so the result type the bus returns is the page itself and no per-query page class is needed.Shared\Projection—AbstractProjectoris the shared mechanism behind any projection, read model or index: it implementsEventListenerInterfaceand resolves anapplyXxxmethod from each event's short name, so a projector only writes handlers for the events it reacts to.Shared\ReadModel—ReadModelInterfaceandReadModelRepositoryInterfaceare the read-side contracts for a presentable projection: a model queried by criteria and served as a result. A lookup projection — one that resolves a key to ids rather than being presented (e.g. tag → ids, email → user id) — is just a read model queried for its ids, kept in sync by a projector; it needs no separate contract.
Replaying
Shared\Replaying\Replayer visits the events matching a criteria from the store
and feeds them to an event visitor — e.g. to rebuild a read model from history.
When the store is an UpcastingEventStore, the visited events are upcast too, so
a projector reacting to the replay sees the current event shapes.
Specifications
Shared\Specification\AbstractSpecification is the base for composable domain
specifications (business rules expressed as objects).
Exceptions
Shared\Exception provides a hierarchy mapping domain failures to intent —
NotFoundException, ConflictException, ForbiddenException,
UnauthorizedException, InfrastructureException,
UnexpectedException — so transport layers can translate them to the right
status without leaking domain details. Value objects validate their input with
the native \InvalidArgumentException. Namespace-specific exceptions
(EventStoreException, CorruptSnapshotException, InvalidAggregateRootException,
InvalidExpressionException, …) extend the base that matches their intent.
Support
Shared\Support\ClassName resolves the short name of a class, used to map events
to their applyXxx methods.
License
MIT. See LICENSE.
gilsegura/shared 适用场景与选型建议
gilsegura/shared 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 362 次下载、GitHub Stars 达 1, 最近一次更新时间为 2024 年 04 月 15 日, 在 PHP 生态内属于活跃度较高的组件。
我们在过去多个企业项目中使用过 gilsegura/shared 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 gilsegura/shared 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
统计信息
- 总下载量: 362
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 19
- 依赖项目数: 1
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2024-04-15