PHPackages                             twanhaverkamp/event-sourcing-with-php - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. twanhaverkamp/event-sourcing-with-php

ActiveLibrary

twanhaverkamp/event-sourcing-with-php
=====================================

Event Sourcing with PHP

1.3.1(4mo ago)21592MITPHPPHP ^8.3CI passing

Since Jan 19Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/twanhaverkamp/event-sourcing-with-php)[ Packagist](https://packagist.org/packages/twanhaverkamp/event-sourcing-with-php)[ RSS](/packages/twanhaverkamp-event-sourcing-with-php/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (5)Dependencies (4)Versions (8)Used By (2)

Event Sourcing with PHP
=======================

[](#event-sourcing-with-php)

**Table of Contents**

- [What problem does Event Sourcing solve for you?](#what-problem-does-event-sourcing-solve-for-you)

    - [Compare data structures](#compare-data-structures)
    - [Drawbacks](#drawbacks)
    - [Considerations](#considerations)
- [Components](#components)

    - [Aggregate](#aggregate)
    - [AggregateRootId](#aggregaterootid)
    - [Event](#event)
    - [EventStore](#eventstore)
- [Usage](#usage)

    - [Installation](#installation)
    - [Implementation](#implementation)
        - [Create an Aggregate class](#create-an-aggregate-class)
        - [Create your Event classes](#create-your-event-classes)
        - [Record Events with your Aggregate](#record-events-with-your-aggregate)
        - [Store the Aggregate](#save-the-aggregate)
        - [Load the Aggregate](#load-the-aggregate)

What problem does Event Sourcing solve for you?
-----------------------------------------------

[](#what-problem-does-event-sourcing-solve-for-you)

You not only want to know what the current state of an object is, but you also want to know *how* the object got in this state? In that case Event Sourcing might be the solution to your problem!

### Compare data structures

[](#compare-data-structures)

**CRUD with relations:**

IDNumberSubtotalTaxTotalCreatedAtPaymentDueAt112-3422.803.7516.552025-02-012025-03-01IDInvoice IDReferenceDescriptionQuantityPriceTax11prod.123.456Product35.9521.0021Shipping14.950.00IDInvoice IDPaymentMethodAmountStatus11Manual10.00Completed**Event sourced:**

AggregateRootIdEventPayloadRecordedAt01941d8f-995...invoice-was-created{"number": "12-34", "items": \[\]}2025-02-0101941d8f-995...payment-transaction-was-started{"id": 1, amount": 10.00}2025-02-0101941d8f-995...payment-transaction-was-completed{"id": 1}2025-02-01Your read models can, of course, still be stored in relational tables as illustrated above, but your Aggregate will be built based on the stored Events.

### Drawbacks

[](#drawbacks)

While Event Sourcing may solve problems, it also brings some challenges with it:

- Learning curve; *When shifting from CRUD to Event Sourcing, you may experience a steep learning curve.*
- Potentially slow; *Especially when your aggregate has a long life cycle.*

### Considerations

[](#considerations)

Since this is supposed to be a *lightweight* library you will have to come up (for now) with a solution for the following:

- Snapshots; *Cache your aggregate with a "Snapshot event" to reduce loading time.*
- Projections; *For working with read models.*
- Anonymize; *Protect (privacy) sensitive data.*

> Yes, I'm planning to implement these features **soon™**, but until then it's up to you. 😅

Components
----------

[](#components)

### Aggregate

[](#aggregate)

The [Aggregate](/src/Aggregate/AggregateInterface.php) encapsulates business logic and its public methods reflect your domain.

### AggregateRootId

[](#aggregaterootid)

The [AggregateRootId](/src/Aggregate/AggregateRootId/AggregateRootIdInterface.php) is the Aggregate's unique identifier, which is instantiated before the Aggregate is being created.

**Available:**

- [UUID v7](/src/Aggregate/AggregateRootId/Uuid7.php); *wraps the [ramsey/uuid](https://uuid.ramsey.dev/) library.*

### Event

[](#event)

An [Event](/src/Event/EventInterface.php) changed one or multiple properties of your Aggregate. New property values are "stored" in its payload and will be re-applied when the Aggregate is being rebuilt from storage.

As an Event took place in the past, it's considered good practice to reflect this when naming your Events.

### EventStore

[](#eventstore)

The [EventStore](/src/Event/EventStore/EventStoreInterface.php) is an interesting one. Instead of fetching an Aggregate directly from your storage you query it's related Events with the AggregateRootId sorted by their "recordedAt" value in ascending order. Each Event will be applied to the Aggregate, which eventually will get in it's expected state.

**Available:**

- [MongoDB](https://github.com/twanhaverkamp/event-storage-in-mongodb-with-php)
- [Redis](https://github.com/twanhaverkamp/event-storage-in-redis-with-php)

Usage
-----

[](#usage)

### Installation

[](#installation)

**Requirements:**

- PHP 8.3 (or higher)

If you're using [Composer](https://getcomposer.org/) in your project you can run the following command:

```
composer require twanhaverkamp/event-sourcing-with-php:^1.0
```

### Implementation

[](#implementation)

To understand how to implement this library in your project I would encourage you to take a look at the [/example](/example) directory and specifically the [Invoice](/example/Aggregate/Invoice.php) class as it represents an aggregate containing both business logic and the usage of events.

> You'll see some `// ...` in the code snippets. It indicates there's more code, but it's not relevant for the given example.

#### Create an Aggregate class

[](#create-an-aggregate-class)

Add public methods for your business logic, where their names reflect your domain.

```
