Thinking in events | 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/v7)  Using-projectors  Thinking in events

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

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

- [ Introduction ](https://spatie.be/docs/laravel-event-sourcing/v7/getting-familiar-with-event-sourcing/introduction)
- [ The traditional application ](https://spatie.be/docs/laravel-event-sourcing/v7/getting-familiar-with-event-sourcing/the-traditional-application)
- [ Using projectors to transform events ](https://spatie.be/docs/laravel-event-sourcing/v7/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/v7/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/v7/using-projectors/writing-your-first-projector)
- [ Creating and registering projectors ](https://spatie.be/docs/laravel-event-sourcing/v7/using-projectors/creating-and-configuring-projectors)
- [ Making sure events get handled in the right order ](https://spatie.be/docs/laravel-event-sourcing/v7/using-projectors/making-sure-events-get-handled-in-the-right-order)
- [ Thinking in events ](https://spatie.be/docs/laravel-event-sourcing/v7/using-projectors/thinking-in-events)

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

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

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

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

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

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

 Thinking in events
==================

###  On this page

1. [ Want to know more? ](#content-want-to-know-more)

In this example we're going to try to send a mail whenever an account is broke (balance below zero). You can do this with projectors and reactors alone, but aggregates might be a better fit for this. Aggregates make it easy to make decisions based on past events. Check out the section on [how to use aggregates](/docs/laravel-event-sourcing/v7/using-aggregates/writing-your-first-aggregate) to learn more about them, or keep reading on this page if you don't want to use aggregates.

Let's build upon the examples shown in the [writing your first projector](/docs/laravel-event-sourcing/v7/using-projectors/writing-your-first-projector) and [handling side effects with reactors](/docs/laravel-event-sourcing/v7/using-reactors/writing-your-first-reactor)' sections.

Imagine you are tasked with sending a mail to an account holder whenever he or she is broke. You might think, that's easy, let's just check in a new reactor if the account balance is less than zero.

Let's first add a little helper method to the `Account` model to check if an account is broke.

```
// ...

class Account extends Projection
{
    // ...

    public function isBroke(): bool
    {
        return $this->balance < 0;
    }
}
```

Now create a new reactor called `BrokeReactor`:

```
namespace App\Reactors;

// ...

class BrokeReactor implements EventHandler
{
    use HandlesEvents;

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

        if ($account->isBroke()) {
            Mail::to($account->email)->send(new BrokeMail($account));

            event(new BrokeMailSent($account->uuid));
        }
    }
}
```

A mail will get sent when an account is broke. The problem with this approach is that mails will also get sent for accounts that were already broke before. If you want to only sent mail when an account went from a positive balance to a negative balance we need to do some more work.

You might be tempted to add some kind of flag here that determines if the mail was already sent.

But you should never let reactors write to models (or whatever storage mechanism you use) you've built up using projectors. If you were to do that, all changes would get lost when replaying events: events won't get passed to reactors when replaying them. Keep in mind that reactors are meant for side effects, not for building up state.

If you are tempted to modify state in a reactor, just fire off a new event and let a projector modify the state. Let's modify the `BrokeReactor` to do just that. If you're following along don't forget to create a migration that adds the `broke_mail_sent` field to the `accounts` table.

```
// ...

class BrokeReactor implements EventHandler
{
    use HandlesEvents;

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

        /*
         * Don't send a mail if an account isn't broke
         */
        if (! $account->isBroke()) {
            return;
        }

        /*
         * Don't send a mail if it was sent already
         */
        if ($account->broke_mail_sent) {
            return;
        }

        Mail::to($account->email)->send(new BrokeMail($account));

        /*
         * Send out an event so the projector can modify the state.
         */
        event(new BrokeMailSent($account->uuid));
    }
}
```

Let's leverage that new event in the `AccountBalanceProjector`.

```
// ...

class AccountBalanceProjector extends Projector
{
    public function onBrokeMailSent(BrokeMailSent $event)
    {
        $account = Account::uuid($event->accountUuid);

        $account->broke_mail_sent = true;

        $account->writeable()->save();
    }

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

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

        /*
         * If the balance is above zero again, set the broke_mail_sent
         * flag to false again, so we can send another mail
         * when the balance goes below zero again.
         */
        if ($account->balance >= 0) {
            $account->broke_mail_sent = false;
        }

        $account->writeable()->save();
    }
}
```

The `BrokeReactor` will only send out a mail when an account goes broke. No mails will be sent if the account was already broke. When the account goes above zero and goes broke again a new mail will be sent. When replaying all events, no mail will get sent, but all account state will be correct.

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.
