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!