patchlevel/event-sourcing

repository·3.22.x·Indexed 42 days ago

https://github.com/patchlevel/event-sourcing

A comprehensive event sourcing library for PHP built on the Doctrine ecosystem. It features automatic snapshotting for performance, support for splitting aggregates into multiple streams, versioned lifecycle management for projections and processors, and crypto-shredding for data privacy. It provides official integrations for Symfony via the event-sourcing-bundle and Psalm via the event-sourcing-psalm-plugin. Officially supported databases include PostgreSQL, MariaDB, MySQL, and SQLite.

Tokens
62.5K
Snippets
188
Records
230
Agent score
53%

What's inside patchlevel/event-sourcing

  1. Overview of patchlevel/event-sourcing features

    3.22.x

    The patchlevel/event-sourcing library is a fully-typed event sourcing implementation built on the Doctrine ecosystem. Key features include:

    • Performance: Automatic snapshot system and the ability to split large aggregates into multiple streams.
    • Lifecycle Management: Versioned and managed subscriptions for projections and processors.
    • Data Privacy: Safe handling of Personal Data using crypto-shredding.
    • Evolution: Smooth upcasting of old events to maintain compatibility with new schemas.
    • Tooling: Schema management, Doctrine migrations, and built-in Symfony CLI commands.
  2. Overview of Event-Sourcing features

    3.22.x

    The patchlevel/event-sourcing library is a developer-experience-oriented event sourcing implementation powered by the Doctrine ecosystem. Key features include:

    • Performance: Automatic snapshot system to boost performance.
    • Scalability: Ability to split big aggregates into multiple streams.
    • Lifecycle Management: Versioned and managed lifecycle for subscriptions (projections and processors).
    • Data Privacy: Safe usage of Personal Data via crypto-shredding.
    • Evolution: Smooth upcasting of old events.
    • Infrastructure: Scheme management, Doctrine migration support, and built-in Symfony CLI commands.
  3. What is Upcasting and when to use it

    3.22.x

    Upcasting is a mechanism used to transform event data before it is denormalized into an event object. It is useful when you have existing events in a stream that are missing data or contain data in an outdated format. Instead of creating many versioned event classes (which can lead to complexity), you can use an Upcaster to:

    1. Adjust the payload: Modify existing keys or values within the event data.
    2. Adjust the event name: Rename an event if the original name was poorly chosen.

    An Upcaster operates on an Upcast object, which contains the eventName and the payload (as an array).

  4. What is an Aggregate in event-sourcing?

    3.22.x

    An aggregate is the central entity in event-sourcing. Unlike traditional ORM entities that save current state, an aggregate saves only the individual events that led to that state. This allows the current state to be rebuilt at any time by replaying the events.

    To function as an aggregate in this library, a class must:

    1. Implement the AggregateRoot interface.
    2. Have a unique identifier.
    3. Provide the current playhead.
    4. Make state changes available as events.
    5. Be able to rebuild/catchup its state from those events.

    You can implement these requirements manually or use the BasicAggregateRoot implementation, which uses attributes to handle event configuration and state application.

  5. What is a Translator and how to use it

    3.22.x

    A Translator acts as middleware to manipulate, filter, or expand messages or events in an event stream. Translators can return zero, one, or multiple messages, allowing you to remove, swap, or enrich the stream. You can group multiple translators using a ChainTranslator.

    use Patchlevel\
    EventSourcing\\Message\\Translator\\ChainTranslator;
    use Patchlevel\
    EventSourcing\\Message\\Translator\\ExcludeEventTranslator;
    use Patchlevel\
    EventSourcing\\Message\\Translator\\RecalculatePlayheadTranslator;
    
    $translator = new ChainTranslator([
        new ExcludeEventTranslator([EmailChanged::class]),
        new RecalculatePlayheadTranslator(),
    ]);
  6. What is a Message and how to use it

    3.22.x

    A Message is a construct that wraps an event with additional metadata in the form of headers. Messages are automatically created, saved, and dispatched by the repository whenever an aggregate is saved.

    Note that the Message object is immutable; methods like withHeader return a new instance of the message.

    To manually create a message or add headers:

    use Patchlevel\
    EventSourcing\\Message\\Message;
    use Patchlevel\\EventSourcing\\Aggregate\\AggregateHeader;
    use Patchlevel\\EventSourcing\\Clock\\SystemClock;
    
    $clock = new SystemClock();
    $message = Message::create(new NameChanged('foo'))
        ->withHeader(new AggregateHeader(
            aggregateName: 'profile',
            aggregateId: 'bca7576c-536f-4428-b694-7b1f00c714b7',
            playhead: 2,
            recordedOn: $clock->now(),
        ));
  7. What is Split Stream and when to use it

    3.22.x

    Split Stream allows you to restart the event stream for an aggregate at a specific point in time. This is useful when business rules imply that past events are no longer relevant for the current state (e.g., a bank account performing a year-end balance report) or when an aggregate's event stream becomes so long that it impacts performance.

    How it works:

    • When a 'split event' occurs, the library marks all preceding events as archived.
    • When loading an aggregate, the repository will only load events from the split event onwards.
    • Subscriptions are unaffected: Projections and subscribers will still receive the full history of events, allowing you to maintain projections based on the complete event stream while keeping aggregate loading efficient.
  8. What is a Clock and why is it used?

    3.22.x
    In this library, a Clock is used to retrieve the current datetime, which is required to generate the recorded_on timestamp for event streams. The library provides two primary implementations: SystemClock for production environments and FrozenClock for deterministic testing. You can also provide your own implementation as long as it is compatible with the PSR-20 clock specification.
  9. Use event aliases for renaming

    3.22.x

    If you need to rename an event class or its primary name while maintaining compatibility with existing data in your database, use the aliases option in the #[Event] attribute.

    When saving new events, the primary name is always used. When loading (deserializing) events, the library will check both the primary name and any provided aliases to find the correct class.

    use Patchlevel\EventSourcing\Attribute\Event;
    
    #[Event(name: 'profile.registered', aliases: ['profile.created'])]
    final class ProfileRegistered
    {
    }
  10. Handle multiple commands in one handler

    3.22.x

    You can handle multiple commands within a single handler method using three different approaches:

    1. Multiple Attributes: Use multiple #[Handle] attributes on a single method.
    2. Union Types: Use PHP union types in the parameter hint; the library automatically detects the commands.
    3. Inheritance: The handler will be invoked if the command implements an interface or extends a class that the handler expects.
    // Multiple Attributes
    #[Handle(CreateProfile::class)]
    #[Handle(UpdateProfile::class)]
    public function __invoke(object $command): void { ... }
    
    // Union Types
    #[Handle]
    public function __invoke(CreateProfile|UpdateProfile $command): void { ... }
    
    // Inheritance
    #[Handle]
    public function __invoke(CommandInterface $command): void { ... }