L'Event Sourcing en pratique, ça donne quoi ?

Thibaud Dauce

@ThibaudDauce | https://thibaud.dauce.fr
Quantic Telecom

Co-fondateur de Quantic Telecom

https://www.quantic-telecom.net
Formations Laravel

Formations Laravel

https://www.formations-laravel.fr

Qu'est-ce que l'Event Sourcing ?

Stocker l'état de l'application comme une succession d'évènements

3 concepts importants sur les évènements

Portefeuille en ligne

Que se passe-t-il si je ne suis pas d'accord ?

CompteMontant
Thibaud42,00€
Jane1337,00€
John-123,00€

Event Sourcing à la rescousse

DateCompteLibelléChangement
20/01/2017ThibaudRemboursement+20,00€
12/01/2017ThibaudFacture-20,00€
08/01/2017ThibaudCourses-58,00€
06/01/2017ThibaudVirement initial+100,00€

Avantages

Un audit complet des évènements de notre application

Les nouvelles fonctionnalités profitent des informations passées

Inconvénients

Impossible d'effectuer des requêtes simplement

Plus long et compliqué à mettre en place

En pratique

Les évènements


                    <?php

                    class TransactionMade extends Event
                    {
                        private $account;
                        private $label;
                        private $amount;

                        public function __construct($account, $label, $amount)
                        {
                            $this->account = $account;
                            $this->label = $label;
                            $this->amount = $amount;
                        }
                    }
                

Stockage des évènements

  • NoSQL avec EventStore
  • SQL avec une table par évènement
  • SQL ou NoSQL (type MongoDB) avec une table unique avec un champ JSON libre pour les données additionnelles
  • Sérialisés dans un fichier

Aggregates


                    <?php

                    class Account extends Aggregate
                    {
                        public static function new($name, $amount)
                        {
                            if ($amount < 0) {
                                throw new InitialAccountTransactionMustBePositive;
                            }

                            $account = new self;
                            $account->raise(
                              new TransactionMade($name, 'Virement initial', $amount)
                            );
                        }
                    }
                

Aggregates


                    <?php

                    abstract class Aggregate
                    {
                        public function raise(Event $event)
                        {
                            $event->save();
                            $this->apply($event);
                            dispatch($event);
                        }
                    }
                

Aggregates


                    <?php

                    class Account extends Aggregate
                    {
                        public function apply(Event $event)
                        {
                            if ($event instanceof TransactionMade) {
                                if (! isset($this->name)) {
                                    $this->name = $event->getAccount();
                                }
                                $this->amount += $event->getAmount();
                            }
                        }
                    }
                

Aggregates


                    <?php

                    class Account extends Aggregate
                    {
                        public function pay($label, $amount)
                        {
                            if ($amount > $this->amount) {
                                throw new NotEnoughMoney;
                            }

                            $this->raise(
                              new TransactionMade($this->name, $label, -1 * $amount)
                            );
                        }
                    }
                

Aggregates


                    <?php

                    abstract class Aggregate
                    {
                        public static function fromEvents($events)
                        {
                            $aggregate = new static;
                            foreach($events as $event) {
                                $aggregate->apply($event);
                            }

                            return $aggregate;
                        }
                    }
                

Read models


                    // Save current amount in SQL database
                    public function onTransactionMade($event)
                    {
                        $account = $this->getAccountByName($event->getAccount());
                        $account->update([
                            'amount' => $account->getAmount() + $event->getAmount(),
                        ]);
                    }
                

Read models


                    // Save suspicious accounts in Redis
                    public function onTransactionMade($event)
                    {
                        if ($event->getAmount() > 1000) {
                            Redis::set('suspicious.' . $event->getAccount(), true);
                        }
                    }
                

Et en vraie pratique

En conclusion

Ne faites pas d'Event Sourcing…

… mais pensez y

@ThibaudDauce | https://thibaud.dauce.fr