What is New in PHP Event Sourcing 3.11.0

Version 3.11.0 for patchlevel/event-sourcing is here. This release comes with some excellent improvements, including enhancements to certain console commands, query bus integration via attributes, and a gap detection mechanism for subscriptions. These are some really interesting updates so we shouldn't waste any time and go through the changes immediately!

Console Command Improvements

The first improvement is the addition of the ConsoleLogger for our WatchCommand. This enables you to see the logs from the worker in the terminal if desired, significantly enhancing the developer experience.

The next enhancement concerns our DebugCommand. Previously, we only displayed information about the configured aggregates and events. Now, we also display the configured subscribers, such as the id, group, run_mode, and the subscribed events.

These two improvements were contributed by the community. If you’re interested in contributing to patchlevel/event-sourcing, please feel free to open a PR or an issue if you’d like to discuss your idea first — we’re happy to help!

Query Bus

After adding a command bus in 3.8.0, we have now also introduced a query bus. Unlike the command bus, the query bus is not intended to perform actions on the system, instead, it retrieves information. It allows you to fully utilize the read/write split and use small, independent, tailored projections.

To enable this feature as a standalone solution, we introduced a simple built-in query bus. For advanced use cases, we still recommend external options like symfony/messenger. If you are using our event sourcing symfony bundle, the handlers are automatically registered as query handlers for Symfony Messenger when using the #[Answer] attribute.

Below is an example of how a projection can be implemented as a query handler:

use Patchlevel\EventSourcing\Attribute\Projector;

#[Projector('profiles')]
final class ProfileProjector
{
    #[Answer]
    public function answerQueryProfile(QueryProfile $query): Profile
    {
        $builder = $this->projectionConnection->createQueryBuilder();

        $builder->select('*')
            ->from($this->tableName)
            ->where('id = :id')
            ->setParameter('id', $query->profileId->toString());

        return Profile::fromArray($builder->executeQuery()->fetchAssociative());
    }

    // projector related methods to maintain the state of profiles
}

For more details, check out our documentation, where you will find an in-depth explanation of the query bus and how to configure it.

Gap Detection for Subscriptions

When using an RDBMS for the event store instead of a designated database for event sourcing, it may happen that subscriptions skip some events. Why does this occur? This behavior is due to the internals of these systems and how they handle auto increments. When saving takes too long and a parallel save operation is started and finished earlier, the later reserved auto-incremented ID will be saved before the lower one. This issue happens more frequently when using transactions, especially when they remain open for an extended period. To mitigate this, we already have a locking mechanism in place that only allows one write at a time. But what if this happens regardless, or if you want to improve write performance by deactivating the locking?

Now, the subscription engine has the capability to check for these gaps by using an additional MessageLoader. TheGapResolverStoreMessageLoader can detect gaps and, if necessary, wait a defined amount of time before re-fetching the messages. You can freely configure how often the retry should occur and how long the system should wait between retries. By default, the configuration will retry 4 times with different waiting periods in between. The first retry occursimmediately, the second after 5ms, the third after 50ms, and the final retry after 500ms.

You can also configure the timeframe during which this gap detection is considered. For example, if you want to re-create a projection and encountered a gap months ago, you might not want to retry and wait for several seconds if the gap closes, since you already know it will never happen.

$messageLoader = new GapResolverStoreMessageLoader(
    $store,
    $clock,
    [0, 5, 50, 500], // each entry is a retry and the wait time in ms
    new \DateInterval('PT5M'), // don’t retry if the message is older than 5 minutes
);

When using our symfony bundle, you can enable it like this:

patchlevel_event_sourcing:
  subscription:
    gap_detection: ~

You can also configure it with custom settings:

patchlevel_event_sourcing:
  subscription:
    gap_detection:
      retries_in_ms: [0, 500, 1000]
      detection_window: 'PT5M'

Conclusion

Version 3.11.0 of patchlevel/event-sourcing introduces several valuable enhancements aimed at improving the overall developer experience and system reliability. From refined console commands and the addition of a flexible, attribute-driven query bus, to a new gap detection mechanism for subscriptions—this release addresses practical needs and enables more robust event-driven architectures.

Some of these improvements were inspired by community feedback, and contributions continue to be welcome. Whether you're exploring the project or have ideas to share, feel free to open an issue or pull request. As always, be sure to check out the documentation for further details and guidance.

Other Recent Posts

RSS

What is New in PHP Event Sourcing 3.9 & 3.10

We are excited to announce the release of patchlevel/event-sourcing version 3.9.0 and 3.10.0! These updates introduces features around error handling for the subscription engine and also some nice DX features and PHP 8.4 support.

Daniel Badura
Daniel Badura
Software Entwickler

What is New in PHP Event Sourcing 3.8

We are excited to announce the release of patchlevel/event-sourcing version 3.8.0! This update introduces features such as lookup, command bus, read-only event store, and the official release of the stream store. In this post, we will walk you through the key changes.

David Badura
David Badura
Software Entwickler

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