Writing your first projector | laravel-event-sourcing | Spatie

 SPATIE

  Laravel Event Sourcing
=========================

spatie.be/open-source

  [Docs](https://spatie.be/docs)  [Laravel-event-sourcing](https://spatie.be/docs/laravel-event-sourcing/v6)  Using-projectors  Writing your first projector

 Version   v7   v6   v5   v4   v3   v2   v1

 Other versions for crawler [v7](https://spatie.be/docs/laravel-event-sourcing/v7) [v6](https://spatie.be/docs/laravel-event-sourcing/v6) [v5](https://spatie.be/docs/laravel-event-sourcing/v5) [v4](https://spatie.be/docs/laravel-event-sourcing/v4) [v3](https://spatie.be/docs/laravel-event-sourcing/v3) [v2](https://spatie.be/docs/laravel-event-sourcing/v2) [v1](https://spatie.be/docs/laravel-event-sourcing/v1)

- [ Introduction ](https://spatie.be/docs/laravel-event-sourcing/v6/introduction)
- [ Support us ](https://spatie.be/docs/laravel-event-sourcing/v6/support-us)
- [ Requirements ](https://spatie.be/docs/laravel-event-sourcing/v6/requirements)
- [ Installation &amp; setup ](https://spatie.be/docs/laravel-event-sourcing/v6/installation-setup)
- [ Questions and issues ](https://spatie.be/docs/laravel-event-sourcing/v6/questions-issues)
- [ Changelog ](https://spatie.be/docs/laravel-event-sourcing/v6/changelog)
- [ Upgrading ](https://spatie.be/docs/laravel-event-sourcing/v6/upgrading)
- [ Resources and alternatives ](https://spatie.be/docs/laravel-event-sourcing/v6/resources-and-alternatives)
- [ About us ](https://spatie.be/docs/laravel-event-sourcing/v6/about-us)

Getting familiar with event sourcing
------------------------------------

- [ Introduction ](https://spatie.be/docs/laravel-event-sourcing/v6/getting-familiar-with-event-sourcing/introduction)
- [ The traditional application ](https://spatie.be/docs/laravel-event-sourcing/v6/getting-familiar-with-event-sourcing/the-traditional-application)
- [ Using projectors to transform events ](https://spatie.be/docs/laravel-event-sourcing/v6/getting-familiar-with-event-sourcing/using-projectors-to-transform-events)
- [ Using aggregates to make decisions based on the past ](https://spatie.be/docs/laravel-event-sourcing/v6/getting-familiar-with-event-sourcing/using-aggregates-to-make-decisions-based-on-the-past)

Using projectors
----------------

- [ Writing your first projector ](https://spatie.be/docs/laravel-event-sourcing/v6/using-projectors/writing-your-first-projector)
- [ Creating and registering projectors ](https://spatie.be/docs/laravel-event-sourcing/v6/using-projectors/creating-and-configuring-projectors)
- [ Making sure events get handled in the right order ](https://spatie.be/docs/laravel-event-sourcing/v6/using-projectors/making-sure-events-get-handled-in-the-right-order)
- [ Thinking in events ](https://spatie.be/docs/laravel-event-sourcing/v6/using-projectors/thinking-in-events)

Using reactors
--------------

- [ Writing your first reactor ](https://spatie.be/docs/laravel-event-sourcing/v6/using-reactors/writing-your-first-reactor)
- [ Creating and configuring reactors ](https://spatie.be/docs/laravel-event-sourcing/v6/using-reactors/creating-and-configuring-reactors)

Using aggregates
----------------

- [ Writing your first aggregate ](https://spatie.be/docs/laravel-event-sourcing/v6/using-aggregates/writing-your-first-aggregate)
- [ Creating and configuring aggregates ](https://spatie.be/docs/laravel-event-sourcing/v6/using-aggregates/creating-and-configuring-aggregates)
- [ Testing aggregates ](https://spatie.be/docs/laravel-event-sourcing/v6/using-aggregates/testing-aggregates)
- [ Snapshots ](https://spatie.be/docs/laravel-event-sourcing/v6/using-aggregates/snapshots)

Advanced usage
--------------

- [ Preparing events ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/preparing-events)
- [ Replaying events ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/replaying-events)
- [ Storing metadata ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/storing-metadata)
- [ Handling exceptions ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/handling-exceptions)
- [ Discovering projectors and reactors ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/discovering-projectors-and-reactors)
- [ Using your own event storage model ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/using-your-own-event-storage-model)
- [ Using your own event storage repository ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/using-your-own-event-storage-repository)
- [ Using your own event serializer ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/using-your-own-event-serializer)
- [ Using aliases for stored event classes ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/using-aliases-for-stored-event-classes)
- [ Adding and Removing Projectors and Reactors ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/adding-and-removing-projectors-and-reactors)
- [ Aggregate Partials ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/aggregate-partials)
- [ Event Queries ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/event-queries)
- [ Commands ](https://spatie.be/docs/laravel-event-sourcing/v6/advanced-usage/commands)

      You are viewing the documentation for **an older version** of this package. You can check the version you are using with the following command:

 `                                    composer show spatie/laravel-event-sourcing                                                                                                                                                                                                                                    `

Writing your first projector
============================

###  On this page

1. [ Creating a model ](#content-creating-a-model)
2. [ Defining events ](#content-defining-events)
3. [ Creating your first projector ](#content-creating-your-first-projector)
4. [ Let's fire off some events ](#content-lets-fire-off-some-events)
5. [ Your second projector ](#content-your-second-projector)
6. [ Welcoming new events ](#content-welcoming-new-events)
7. [ Benefits of projectors and projections ](#content-benefits-of-projectors-and-projections)
8. [ Using Factories in Tests ](#content-using-factories-in-tests)
9. [ Want to know more? ](#content-want-to-know-more)

This section is a perfect entry point to get yourself acquainted with projectors. Most examples in these docs are also available in the Laravel app you'll find in [this repo on GitHub](https://github.com/spatie/larabank-projectors). Clone that repo to toy around with the package.

A projector is a class that gets triggered when new events come in. It typically writes data (to the database or to a file on disk). We call that written data a projection.

Imagine you are a bank with customers that have accounts. All these accounts have a balance. When money gets added or subtracted we could modify the balance. If we do that however, we would never know why the balance got to that number. If we were to store all the transactions as events we could calculate the balance.

Creating a model
--------------------------------------------------------------------------------------------------------

Here's a small migration to create a table that stores accounts. Using a `uuid` is not strictly required, but it will make your life much easier when using this package. In all examples we'll assume that you'll use them.

```
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateAccountsTable extends Migration
{
    public function up()
    {
        Schema::create('accounts', function (Blueprint $table) {
            $table->increments('id');
            $table->string('uuid');
            $table->string('name');
            $table->integer('balance')->default(0);
            $table->timestamps();
        });
    }
}
```

The `Account` model itself could look like this:

```
namespace App;

use App\Events\AccountCreated;
use App\Events\AccountDeleted;
use App\Events\MoneyAdded;
use App\Events\MoneySubtracted;
use Illuminate\Database\Eloquent\Model;
use Ramsey\Uuid\Uuid;

class Account extends Model
{
    protected $guarded = [];

    public static function createWithAttributes(array $attributes): Account
    {
        /*
         * Let's generate a uuid.
         */
        $attributes['uuid'] = (string) Uuid::uuid4();

        /*
         * The account will be created inside this event using the generated uuid.
         */
        event(new AccountCreated($attributes));

        /*
         * The uuid will be used the retrieve the created account.
         */
        return static::uuid($attributes['uuid']);
    }

    public function addMoney(int $amount)
    {
        event(new MoneyAdded($this->uuid, $amount));
    }

    public function subtractMoney(int $amount)
    {
        event(new MoneySubtracted($this->uuid, $amount));
    }

    public function remove()
    {
        event(new AccountDeleted($this->uuid));
    }

    /*
     * A helper method to quickly retrieve an account by uuid.
     */
    public static function uuid(string $uuid): ?Account
    {
        return static::where('uuid', $uuid)->first();
    }
}
```

Defining events
-----------------------------------------------------------------------------------------------------

Instead of creating, updating and deleting accounts, we're simply firing off events. All these events should extend `\Spatie\EventSourcing\StoredEvents\ShouldBeStored`. This abstract class signifies to our package that the event should be stored.

Let's take a look at all events used in the `Account` model.

```
namespace App\Events;

use Spatie\EventSourcing\StoredEvents\ShouldBeStored;

class AccountCreated extends ShouldBeStored
{
    /** @var array */
    public $accountAttributes;

    public function __construct(array $accountAttributes)
    {
        $this->accountAttributes = $accountAttributes;
    }
}
```

```
namespace App\Events;

use Spatie\EventSourcing\StoredEvents\ShouldBeStored;

class MoneyAdded extends ShouldBeStored
{
    /** @var string */
    public $accountUuid;

    /** @var int */
    public $amount;

    public function __construct(string $accountUuid, int $amount)
    {
        $this->accountUuid = $accountUuid;

        $this->amount = $amount;
    }
}
```

```
namespace App\Events;

use Spatie\EventSourcing\StoredEvents\ShouldBeStored;

class MoneySubtracted extends ShouldBeStored
{
    /** @var string */
    public $accountUuid;

    /** @var int */
    public $amount;

    public function __construct(string $accountUuid, int $amount)
    {
        $this->accountUuid = $accountUuid;

        $this->amount = $amount;
    }
}
```

```
namespace App\Events;

use Spatie\EventSourcing\StoredEvents\ShouldBeStored;

class AccountDeleted extends ShouldBeStored
{
    /** @var string */
    public $accountUuid;

    public function __construct(string $accountUuid)
    {
        $this->accountUuid = $accountUuid;
    }
}
```

Creating your first projector
-----------------------------------------------------------------------------------------------------------------------------------------------

A projector is a class that listens for events that were stored. When it hears an event that it is interested in, it can perform some work.

Let's create your first projector. You can perform `php artisan make:projector AccountBalanceProjector` to create a projector in `app\Projectors`.

Here's an example projector that handles all the events mentioned above:

```
namespace App\Projectors;

use App\Account;
use App\Events\AccountCreated;
use App\Events\AccountDeleted;
use App\Events\MoneyAdded;
use App\Events\MoneySubtracted;
use Spatie\EventSourcing\EventHandlers\Projectors\Projector;

class AccountBalanceProjector extends Projector
{
    public function onAccountCreated(AccountCreated $event)
    {
        Account::create($event->accountAttributes);
    }

    public function onMoneyAdded(MoneyAdded $event)
    {
        $account = Account::uuid($event->accountUuid);

        $account->balance += $event->amount;

        $account->save();
    }

    public function onMoneySubtracted(MoneySubtracted $event)
    {
        $account = Account::uuid($event->accountUuid);

        $account->balance -= $event->amount;

        $account->save();
    }

    public function onAccountDeleted(AccountDeleted $event)
    {
        Account::uuid($event->accountUuid)->delete();
    }
}
```

Just by type hinting an event in a method will make the package call that method when the event occurs. As in the example above, make sure the method parameter name is `$event`. By default the package will automatically discover and register projectors.

Let's fire off some events
------------------------------------------------------------------------------------------------------------------------------------

With all this out of the way we can fire off some events.

Let's try adding an account with:

```
Account::createWithAttributes(['name' => 'Luke']);
Account::createWithAttributes(['name' => 'Leia']);
```

And let's make some transactions on that account:

```
$account = Account::where(['name' => 'Luke'])->first();
$anotherAccount = Account::where(['name' => 'Leia'])->first();

$account->addMoney(1000);
$anotherAccount->addMoney(500);
$account->subtractMoney(50);
```

If you take a look at the contents of the `accounts` table you should see some accounts together with their calculated balance. Sweet! In the `stored_events` table you should see an entry per event that we fired.

Your second projector
-----------------------------------------------------------------------------------------------------------------------

Imagine that, after a while, someone at the bank wants to know which accounts have processed the most transactions. Because we stored all changes to the accounts in the events table we can easily get that info by creating another projector.

We are going to create another projector that stores the transactions count per account in a model. Bear in mind that you can easily use any other storage mechanism instead of a model. The projector doesn't care what you use.

Here's the migration and the model class that the projector is going to use:

```
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTransactionsCountTable extends Migration
{
    public function up()
    {
        Schema::create('transactions_count', function (Blueprint $table) {
            $table->increments('id');
            $table->string('account_uuid');
            $table->integer('count')->default(0);
            $table->timestamps();
        });
    }
}
```

If you're following along don't forget to run this new migration.

```
php artisan migrate
```

```
namespace App;

use Illuminate\Database\Eloquent\Model;

class TransactionCount extends Model
{
    protected $table = 'transactions_count';
    protected $guarded = [];
}
```

Here's the projector that is going to listen to the `MoneyAdded` and `MoneySubtracted` events. Typehinting `MoneyAdded` and `MoneySubtracted` will make our package call `onMoneyAdded` and `MoneySubtracted` when these events occur.

```
namespace App\Projectors;

use App\Events\MoneyAdded;
use App\Events\MoneySubtracted;
use App\TransactionCount;
use Spatie\EventSourcing\Models\StoredEvent;
use Spatie\EventSourcing\EventHandlers\Projectors\Projector;

class TransactionCountProjector extends Projector
{
    public function onMoneyAdded(MoneyAdded $event)
    {
        $transactionCounter = TransactionCount::firstOrCreate(['account_uuid' => $event->accountUuid]);

        $transactionCounter->count += 1;

        $transactionCounter->save();
    }

    public function onMoneySubtracted(MoneySubtracted $event)
    {
        $transactionCounter = TransactionCount::firstOrCreate(['account_uuid' => $event->accountUuid]);

        $transactionCounter->count += 1;

        $transactionCounter->save();
    }
}
```

Let's not forget to register this projector:

```
// in a service provider of your own
Projectionist::addProjector(TransactionCountProjector::class);
```

If you've followed along, you've already created some accounts and some events. To feed those past events to the projector we can simply perform this artisan command:

```
php artisan event-sourcing:replay App\\Projectors\\TransactionCountProjector
```

This command will take all events stored in the `stored_events` table and pass them to `TransactionCountProjector`. After the command completes you should see the transaction counts in the `transactions_count` table.

Welcoming new events
--------------------------------------------------------------------------------------------------------------------

Now that both of your projections have handled all events, try firing off some new events.

```
Account::createWithAttributes(['name' => 'Yoda']);
```

And let's add some transactions to that account:

```
$yetAnotherAccount = Account::where(['name' => 'Yoda'])->first();

$yetAnotherAccount->addMoney(1000);
$yetAnotherAccount->subtractMoney(50);
```

You'll notice that both projectors are immediately handling these new events. The balance of the `Account` model is up to date and the data in the `transactions_count` table gets updated.

Benefits of projectors and projections
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------

The cool thing about projectors is that you can write them after events have happened. Imagine that someone at the bank wants to have a report of the average balance of each account. You would be able to write a new projector, replay all events, and have that data.

Projections are very fast to query. Imagine that our application has processed millions of events. If you want to create a screen where you display the accounts with the most transactions you can easily query the `transactions_count` table. This way you don't need to fire off some expensive query. The projector will keep the projections (the `transactions_count` table) up to date.

Using Factories in Tests
--------------------------------------------------------------------------------------------------------------------------------

In the example above the `Account` model contains the necessary logic to create an `Account`, this pattern may require you to revise how you create test data using model factories. One possible solution is illustated below.

```
public function test_can_have_many_accounts()
{
    Account::factory()->times(5)->make()->each(function($account) {
        Account::createWithAttributes($account->toArray());
    });

    $this->assertCount(5, auth()->user()->accounts);
    $this->assertInstanceOf(Account::class, auth()->user()->accounts()->first());
}
```

Want to know more?
------------------------------------------------------------------------------------------------------------

We discuss projections and complex patterns such as CQRS in depth in our [Event Sourcing in Laravel](https://event-sourcing-laravel.com/) course. In practice, you want to check out these chapters:

- 5. Storing and Projecting Events
- 6. [Projectors in Depth](https://event-sourcing-laravel.com/projectors-in-depth)
- 14. CQRS
