What is New in PHP Event Sourcing 3.8

We are thrilled to announce the release of our PHP library patchlevel/event-sourcing version 3.8.0!

Although this release went live a few weeks ago, we did not have the chance to blog about it until now. Today, we are excited to highlight some major new features, including lookup, command bus, read-only event store, and the official release of the stream store. Let’s dive in!

Lookup

The first new feature is the ability to perform a lookup in subscriptions.

Often, when handling an event, you need additional data to make decisions or update projections. One approach is to store the required data separately, but that adds complexity. Since the event store already contains all past events, we introduced a lookup service that lets you access relevant past events directly without extra storage or management.

The lookup service operates within the event store but ensures you can only access events that occurred before the current event. You can use it within subscriptions by specifying the correct type hint. This feature also integrates well with the Reducer, allowing you to compute the required state efficiently.

#[Subscribe(AuctionClosed::class)]
public function onAuctionClosed(AuctionClosed $event, Lookup $lookup): void
{
    $messages = $lookup
      ->currentAggregate()
      ->events(
        Sold::class,
        AuctionPublished::class
      )->fetch();

    $state = (new Reducer())
        ->initState(['description' => null, 'price' => null])
        ->match([
            Sold::class => static fn(Message $message): array => ['price' => $message->event()->price],
            AuctionPublished::class => static fn(Message $message): array => ['description' => $message->event()->description],
        ])
        ->reduce($messages);

    $this->connection->insert('expired_auctions', $state);
}

Command Bus

Another key addition is our Command Bus, specifically our aggregate command handler. This handler automatically loads or creates the necessary aggregate based on the command and saves it afterward. It can also directly inject dependencies, allowing services to be used within aggregates to execute business logic or enforce constraints.

To enable this feature as a standalone solution, we introduced a simple built-in command bus. For advanced use cases, we still recommend external options like Symfony Messenger. If you are using our Event Sourcing Symfony Bundle, aggregate handlers are automatically registered as command handlers for Symfony Messenger.

This improvement enhances developer experience (DX) by allowing command handling directly in aggregates, eliminating the need for separate handlers. It also pairs well with micro aggregates.

Here is an example of how an aggregate now looks:

#[Aggregate('profile')]
final class Profile extends BasicAggregateRoot
{
    #[Id]
    private ProfileId $id;
    private string $name;

    #[Handle]
    public static function create(CreateProfile $command, NameValidator $nameValidator): self
    {
        if (!$nameValidator($command->name)) {
            throw new InvalidArgument();
        }

        $self = new self();
        $self->recordThat(new ProfileCreated($command->id, $command->name));

        return $self;
    }

    #[Handle]
    public function changeName(ChangeProfileName $command, NameValidator $nameValidator): void
    {
        if (!$nameValidator($command->name)) {
            throw new InvalidArgument();
        }

        $this->recordThat(new NameChanged($command->name));
    }
}

For more details, check out our documentation, where you will find an in-depth explanation of the command bus, how to configure it, and how to leverage the Inject feature for seamless dependency management within your aggregates.

Read-Only Event Store

We have added a read-only store decorator that allows reading but prevents writing to the store. This is particularly useful for migration scenarios, ensuring no new events are stored while data is being transferred.

use Patchlevel\EventSourcing\Store\ReadOnlyStore;

$readOnlyStore = new ReadOnlyStore($store);

Stream Store Release

Our Stream Store has officially exited the experimental phase! Along with this, we made a few refinements:

  • Support for filtering multiple streams with multiple wildcards
  • Refined the remove method to accept Criteria
  • Introduced an archive method
  • Moved archive logic from stream store to repository
  • Added ToPlayheadCriterion and EventIdCriterion

The StreamStore is now our recommended store, and we encourage everyone to migrate to it. We will cover how to migrate the store in a future blog post.

Conclusion

This release brings major enhancements and improvements. We are excited about the result and hope you are too! We do love to hear your feedback—feel free to open an issue on GitHub. See you next time!

Other Recent Posts

RSS

The Performance Factor in Event Sourcing: What You Need to Know

This article addresses the common concern regarding the performance of event sourcing, particularly the speed at which long-living aggregates with many events are loaded. It explores solutions such as snapshotting and stream splitting to optimize aggregate loading. Furthermore, projections allow for the creation of highly flexible and optimized read models, each can be tailored to specific needs.

Daniel Badura
Daniel Badura
Software Entwickler

What is new in patchlevel/event-sourcing in version 3.7

We’re excited to announce the release of our php library patchlevel/event-sourcing version 3.7.0. This release features better testing capabilities with InMemorySubscriptionStore::clear, improved subscription performance and a new #[Stream] attribute for micro aggregates!

Daniel Badura
Daniel Badura
Software Entwickler

What is new in php event sourcing 3.6

We are happy to announce the release of the php event sourcing library in version 3.6.0. This release contains several exciting new features like Pipe and Reducer. In this blog post, we will provide you with an overview of the changes.

David Badura
David Badura
Software Entwickler