Skip to content

Commit

Permalink
v2 docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Rias committed Jun 13, 2019
1 parent 3740b52 commit ba0b1ef
Show file tree
Hide file tree
Showing 51 changed files with 8,451 additions and 1 deletion.
1 change: 0 additions & 1 deletion .gitignore
@@ -1,6 +1,5 @@
build
composer.lock
docs
vendor
coverage
.phpunit.result.cache
6 changes: 6 additions & 0 deletions docs/_index.md
@@ -0,0 +1,6 @@
---
title: v2
slogan: Event Sourcing for Artisans
githubUrl: https://github.com/spatie/laravel-event-projector
branch: master
---
11 changes: 11 additions & 0 deletions docs/about-us.md
@@ -0,0 +1,11 @@
---
title: About us
weight: 9
---

[Spatie](https://spatie.be) is a webdesign agency based in Antwerp, Belgium.

Open source software is used in all projects we deliver. Laravel, Nginx, Ubuntu are just a few
of the free pieces of software we use every single day. For this, we are very grateful.
When we feel we have solved a problem in a way that can help other developers,
we release our code as open source software [on GitHub](https://spatie.be/opensource).
4 changes: 4 additions & 0 deletions docs/advanced-usage/_index.md
@@ -0,0 +1,4 @@
---
title: Advanced usage
weight: 5
---
18 changes: 18 additions & 0 deletions docs/advanced-usage/discovering-projectors-and-reactors.md
@@ -0,0 +1,18 @@
---
title: Discovering projectors and reactors
weight: 4
---

By default the package will automatically discover all projectors and reactors and will register them at the projectionist.

If you want to see a list of the discovered projectors and reactors perform the `event-projector:list` Artisan command. Here's how the output could look like:

<img src="../../images/list.png" />

## Caching discovered projectors and reactors

In production, you likely do not want the package to scan all of your classes on every request. Therefore, during your deployment process, you should run the `event-projector:cache-event-handlers` Artisan command to cache a manifest of all of your application's projectors and reactors. This manifest will be used by the package to speed up the registration process. The `event-projector:clear-event-handlers` command may be used to destroy the cache.

## Disabling disovery

If you want to turn off autodiscovery and want to enforce manualy registration of projectors and reactors, just set the `auto_discover_projectors_and_reactors` key in the `event-projector` config file to an empty array.
14 changes: 14 additions & 0 deletions docs/advanced-usage/handling-exceptions.md
@@ -0,0 +1,14 @@
---
title: Handling exceptions
weight: 3
---

The `event-projector` config file has a key, `catch_exceptions`, that determines what will happen should a projector or reactor throw an exception. If this setting is set to `false`, exceptions will not be caught and your app will come to a grinding halt.

If `catch_exceptions` is set to `true`, and an projector or reactor throws an exception, all other projectors and reactors will still get called. The `Projectionist` will catch all exceptions and fire the `EventHandlerFailedHandlingEvent`. That event contains these public properties:

- `eventHandler`: The projector or reactor that could not handle the event.
- `storedEvent`: The instance of `Spatie\EventProjector\Models\StoredEvent` that could not be handled.
- `exception`: The exception thrown by the `EventHandler`.

It will also call the `handleException` method on the projector or reactor that threw the exception. It will receive the thrown error as the first argument. If you throw an exception in `handleException`, the `Projectionist` will not catch it and your php process will fail.
59 changes: 59 additions & 0 deletions docs/advanced-usage/preparing-events.md
@@ -0,0 +1,59 @@
---
title: Preparing events
weight: 1
---

The package will listen for events that implement the `\Spatie\EventProjector\ShouldBeStored` interface. This is an empty interface that simply signals to the package that the event should be stored.

You can quickly create an event that implements `ShouldBeStored` by running this artisan command:

```bash
php artisan make:storable-event NameOfYourEvent
```

Here's an example of such event:

```php
namespace App\Events;

use Spatie\EventProjector\ShouldBeStored;

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

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

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

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

Whenever an event that implements `ShouldBeStored` is fired it will be serialized and written in the `stored_events` table. Immediately after that, the event will be passed to all projectors and reactors.

If your event has an eloquent model, it should also use the `Illuminate\Queue\SerializesModels` trait so we are able to serialize these models correctly.

## Specifying a queue

When a `StoredEvent` is created, we'll dispatch a job on the queue defined in the `queue` key of the `event-projector` config file. Queued projectors and reactors will get called when the job is executed on the queue.

On an event you can override the queue that should be used by adding a `queue` property.

```php
namespace App\Events;

use Spatie\EventProjector\ShouldBeStored;

class MyEvent implements ShouldBeStored
{
public $queue = 'alternativeQueue';

...
}
```
57 changes: 57 additions & 0 deletions docs/advanced-usage/replaying-events.md
@@ -0,0 +1,57 @@
---
title: Replaying events
---

All [events](/laravel-event-projector/v2/handling-events/preparing-events) that implement `Spatie\EventProjector\ShouldBeStored` will be [serialized](https://docs.spatie.be/laravel-event-projector/v2/advanced-usage/using-your-own-event-serializer) and stored in the `stored_events` table. After your app has been doing its work for a while the `stored_events` table will probably contain some events.

When creating a new [projector](/laravel-event-projector/v2/handling-events/using-projectors) or [reactor](/laravel-event-projector/v2/handling-events/using-reactors) you'll want to feed all stored events to that new projector or reactor. We call this process replaying events.

Events can be replayed to [all projectors that were added to the projectionist](/laravel-event-projector/v2/handling-events/using-reactors) with this artisan command:

```bash
php artisan event-projector:replay
```

You can also projectors by using the `--projector` option. All stored events will be passed only to that projector.

```bash
php artisan event-projector:replay --projector=App\\Projectors\\AccountBalanceProjector
```

You can use the projector option multiple times:

```bash
php artisan event-projector:replay --projector=App\\Projectors\\AccountBalanceProjector --projector=App\\Projectors\\AnotherProjector
```

If your projector has a `resetState` method it will get called before replaying events. You can use that method to reset the state of your projector.

If you want to replay events starting from a certain event you can use the `--from` option when executing `event-projector:replay`. If you use this option the `resetState` on projectors will not get called. This package does not track which events have already been processed by which projectors. Be sure not to replay events to projectors that already have handled them.

## Detecting event replays

If your projector contains an `onStartingEventReplay` method, we'll call it right before the first event is replayed.

If it contains an `onFinishedEventReplay` method, we'll call it right after all events have been replayed.

You can also detect the start and end of event replay by listening for the `Spatie\EventProjector\Events\StartingEventReplay` and `Spatie\EventProjector\Events\FinishedEventReplay` events.

Though, under normal circumstances, you don't need to know this, you can detect if events are currently being replayed like this:

```php
Spatie\EventProjector\Facades\Projectionist::isReplayingEvents(); // returns a boolean
```

## Models with timestamps

When using models with timestamps, it is important to keep in mind that the projector will create or update these models when replaying and the timestamps will not correspond to the event's original timestamps. This will probably not be behavior you intended. To work around this you can use the stored event's timestamps:

```php
public function onAccountCreated(StoredEvent $storedEvent, AccountCreated $event) {
Account::create(array_merge($event->accountAttributes, ['created_at' => $storedEvent->created_at, 'updated_at' => $storedEvent->created_at]));
}
```

## What about reactors?

Reactors are used to handle side effects, like sending mails and such. You'll only want reactors to do their work when an event is originally fired. You don't want to send out mails when replaying events. That's why reactors will never get called when replaying events.
68 changes: 68 additions & 0 deletions docs/advanced-usage/storing-metadata.md
@@ -0,0 +1,68 @@
---
title: Storing metadata
weight: 2
---

You can add metadata, such as the `id` of the logged in user, to a stored event.

## Storing metadata on all events

If you need to store metadata on all events you can leverage Laravel's native models events.

You must configure the package [use your own event storage model](/laravel-event-projector/v2/advanced-usage/using-your-own-event-storage-model). On that model you can hook into the model lifecycle hooks.

```php
use Spatie\EventProjector\Models\StoredEvent;

class CustomStoredEvent extends StoredEvent
{
public static function boot()
{
parent::boot();

static::creating(function(CustomStoredEvent $storedEvent) {
$storedEvent->meta_data['user_id'] = auth()->user()->id;
});
}
}
```

## Storing metadata via a projector

The `StoredEvent` instance will be passed on to any projector method that has a variable named `$storedEvent`. On that `StoredEvent` instance there is a property, `meta_data`, that returns an instance of `Spatie\SchemalessAttributes\SchemalessAttributes`.

Here's an example:

```php
namespace App\Projectors;

use Spatie\EventProjector\Projectors\Projector;
use Spatie\EventProjector\Projectors\ProjectsEvents;
use Spatie\EventProjector\Models\StoredEvent;
use Spatie\EventProjector\Facades\Projectionist;
use App\Events\MoneyAdded;

class MetaDataProjector implements Projector
{
use ProjectsEvents;

/*
* Here you can specify which event should trigger which method.
*/
public $handlesEvents = [
MoneyAdded::class => 'onMoneyAdded',
];

public function onMoneyAdded(StoredEvent $storedEvent)
{

if (Projectionist::isReplaying()) {
$storedEvent->meta_data['user_id'] = auth()->user()->id;

$storedEvent->save();
}

// ...
}
}
```
23 changes: 23 additions & 0 deletions docs/advanced-usage/using-your-own-event-serializer.md
@@ -0,0 +1,23 @@
---
title: Using your own event serializer
weight: 6
---

Events will be serialized by the `Spatie\EventProjector\EventSerializers\JsonEventSerializer`. Like the name implies, this class can serialize an event to json so it can be easily stored in a `json` column in the database.

You can specify your own serializer by creating a class that implements `Spatie\EventProjector\EventSerializers\EventSerializer` and specifying the class in the `event_serializer` key of the `event-projector.php` config file.

This is the content of the `EventSerializer` interface:

```
namespace Spatie\EventProjector\EventSerializers;
use Spatie\EventProjector\ShouldBeStored;
interface EventSerializer
{
public function serialize(ShouldBeStored $event): string;
public function deserialize(string $eventClass, string $json): ShouldBeStored;
}
```
6 changes: 6 additions & 0 deletions docs/advanced-usage/using-your-own-event-storage-model.md
@@ -0,0 +1,6 @@
---
title: Using your own event storage model
weight: 5
---

The default model responsible for storing events is `\Spatie\EventProjector\Models\StoredEvent`. If you want to add behaviour to that model you can create a class of your own that extends the `StoredEvent` model. You should put the class name of your model in the `stored_event_model` in the `event-projector.php` config file.
6 changes: 6 additions & 0 deletions docs/changelog.md
@@ -0,0 +1,6 @@
---
title: Changelog
weight: 6
---

All notable changes to laravel-event-projector are documented [on GitHub](https://github.com/spatie/laravel-event-projector/blob/master/CHANGELOG.md)
4 changes: 4 additions & 0 deletions docs/getting-familiar-with-event-sourcing/_index.md
@@ -0,0 +1,4 @@
---
title: Getting familiar with event sourcing
weight: 1
---
18 changes: 18 additions & 0 deletions docs/getting-familiar-with-event-sourcing/introduction.md
@@ -0,0 +1,18 @@
---
title: Introduction
weight: 1
---

Event sourcing is to data what Git is to code. Most applications only have their current state stored in a database. A lot of useful information gets lost: you don't know _how_ the application got to this state.

Event sourcing tries to solve this problem by storing all events that happen in your app. The state of your application is built by listening to those events. An aggregate is used to validate if a new event is allowed to get written and to make decisions based on the past. Projectors are used to transform newly written events into a format useful for consumption in your app.

Here's a concrete example to make it more clear. Imagine you're a bank. Your clients have accounts. Storing the balance of the accounts wouldn't be enough; all the transactions should be remembered too. With event sourcing, the balance isn't a standalone database field, but a value calculated from the stored transactions.

After taking a look at [an example of traditional application](/laravel-event-projector/v2/getting-familiar-with-event-sourcing/the-traditional-application), we're going to discuss the two concepts that make up this package: [projectors](/laravel-event-projector/v2/getting-familiar-with-event-sourcing/using-projectors-to-transform-events) and [aggregates](/laravel-event-projector/v2/getting-familiar-with-event-sourcing/using-aggregates-to-make-decisions-based-on-the-past).

If you want to skip to reading code immediately, here are the Larabank example apps used in this section. In all of them, you can create accounts and deposit or withdraw money.

- [Larabank built traditionally without event sourcing](https://github.com/spatie/larabank-traditional)
- [Larabank built with projectors](https://github.com/spatie/larabank-event-projector)
- [Larabank built with aggregates and projectors](https://github.com/spatie/larabank-event-projector-aggregates)
@@ -0,0 +1,24 @@
---
title: The traditional application
weight: 2
---

In a traditional application, you're probably going to use a database to hold the state of your application. Whenever you want to update a state, you're simply going to overwrite the old value. That old value isn't accessible anymore. Your application only holds the current state.

You might think that you still have the old state inside your backups. But they don't count. Your app probably can't, nor should it, make decisions on data inside those backups.

<figure class="scheme">
<figcaption class="scheme_caption">
First, we write value X
</figcaption>
<img class="scheme_figure" src="../../images/db-01.svg">
</figure>

<figure class="scheme">
<figcaption class="scheme_caption">
Next, we overwrite X by Y. X cannot be accessed anymore.
</figcaption>
<img class="scheme_figure" src="../../images/db-02.svg">
</figure>

Here's a demo application that uses a traditional architecture. Inside the [`AccountsController`](https://github.com/spatie/larabank-traditional/blob/6ceb08f4700a9be72f0ebfe49b997d5871d64c6b/app/Http/Controllers/AccountsController.php) we are just going to [create new accounts](https://github.com/spatie/larabank-traditional/blob/6ceb08f4700a9be72f0ebfe49b997d5871d64c6b/app/Http/Controllers/AccountsController.php#L19-L27) and [update the balance](https://github.com/spatie/larabank-traditional/blob/6ceb08f4700a9be72f0ebfe49b997d5871d64c6b/app/Http/Controllers/AccountsController.php#L19-L27). We're using an eloquent model to update the database. Whenever we change the balance of the account, the old value is lost.

0 comments on commit ba0b1ef

Please sign in to comment.