
Récemment, dans le podcast Zinc Prod , mes amis et moi avons discuté du modèle CQRS / ES et de certaines fonctionnalités de sa mise en œuvre dans Elixir. Parce que J'utilise Laravel dans mon travail, c'était un péché de ne pas plonger dans Internet et de ne pas trouver comment on peut siroter cette approche dans l'écosystème de ce framework.
J'invite tout le monde sous la coupe, j'ai essayé de décrire le sujet le plus abstraitement possible.
Quelques définitions
CQRS (Command Query Responsibility Segregation) - allocation des opérations de lecture et d'écriture en entités distinctes. Par exemple, nous écrivons au maître, lisons à partir de la réplique. CQRS. Faits et idées fausses - Aide à acquérir une connaissance approfondie du Zen CQRS.
ES (Event Sourcing) - stockage de tous les changements d'état d'une entité ou d'un ensemble d'entités.
CQRS / ES est une approche architecturale dans laquelle nous enregistrons tous les événements d'un changement d'état d'une entité dans la table d'événements et y ajoutons un agrégat et un projecteur.
Agrégat - stocke en mémoire les propriétés nécessaires pour prendre des décisions de logique métier (pour accélérer l'écriture), prend des décisions (logique métier) et publie des événements.
Projecteur - écoute les événements et écrit dans des tables ou des bases de données distinctes (pour une lecture plus rapide).

Au combat
Projecteur d'événements Laravel - Bibliothèque CQRS / ES pour Laravel
Larabank est un référentiel avec une approche CQRS / ES. Nous allons le prendre pour le procès.
La configuration de la bibliothèque vous dira où chercher et dira de quoi il s'agit. Nous regardons le fichier event-projecteur.php . Du nécessaire pour décrire le travail:
projectors
- enregistrer les projecteurs;reactors
- enregistrer les réacteurs. Reactor - dans cette bibliothèque ajoute des effets secondaires au traitement des événements, par exemple, dans ce référentiel, si vous essayez de dépasser la limite de retrait trois fois, l'événement MoreMoneyNeeded est écrit et un message est envoyé à l'utilisateur au sujet de ses difficultés financières;replay_chunk_size
- taille du morceau de relecture. L'une des fonctionnalités d'ES est la possibilité de restaurer l'historique des événements. Projecteur d'événements Laravel préparé pour une fuite de mémoire lors d'une telle opération en utilisant ce paramètre.
Faites attention à la migration. En plus des tables Laravel standard, nous avons
stored_events
- la table ES principale avec plusieurs colonnes de données non structurées pour les métadonnées, nous stockons les types d'événements dans une rangée. Importante colonne aggregate_uuid
- stocke l'uuid de l'agrégat pour recevoir tous les événements qui lui sont liés;accounts
- le tableau du projecteur de comptes d'utilisateurs, est nécessaire pour le retour rapide des données actuelles sur l'état du solde;transaction_counts
- une table du projecteur du nombre de transactions utilisateur, nécessaire pour un retour rapide du nombre de transactions terminées.
Et maintenant, je propose de prendre la route avec une demande de création d'un nouveau compte.
Création de compte
Le routage de resource
standard décrit AccountsController . Nous sommes intéressés par la méthode du store
public function store(Request $request) { $newUuid = Str::uuid();
AccountAggregateRoot hérite de la bibliothèque AggregateRoot . Examinons les méthodes appelées par le contrôleur.
La méthode persist
appelle la méthode storeMany
le modèle spécifié dans la configuration event-projecteur.php comme stored_event_model
dans notre cas, StoredEvent
public static function storeMany(array $events, string $uuid = null): void { collect($events) ->map(function (ShouldBeStored $domainEvent) use ($uuid) { $storedEvent = static::createForEvent($domainEvent, $uuid); return [$domainEvent, $storedEvent]; }) ->eachSpread(function (ShouldBeStored $event, StoredEvent $storedEvent) {
* Projecteur en file d'attente
Les projecteurs AccountProjector et TransactionCountProjector implémentent Projector
ils répondront donc aux événements de manière synchrone avec leur enregistrement.
Ok, un compte a été créé. Je propose de considérer comment le client le lira.
Affichage des factures
Si le projecteur de comptes implémente l'interface QueuedProjector , l'utilisateur ne verra rien tant que l'événement n'aura pas été traité à son tour.
Enfin, nous étudierons le fonctionnement du réapprovisionnement et du retrait d'argent du compte.
Recharge et retrait
Encore une fois, regardez le AccountsController :
Considérez AccountAggregateRoot
lors de la reconstitution du compte:
public function addMoney(int $amount) { $this->recordThat(new MoneyAdded($amount)); return $this; }
* AggregateRoot
lors du retrait de fonds:
public function subtractMoney(int $amount) { if (!$this->hasSufficientFundsToSubtractAmount($amount)) {

Conclusion
J'ai essayé de décrire le processus «d'intégration» dans CQRS / ES sur Laravel comme étant sans eau possible. Le concept est très intéressant, mais pas sans fonctionnalités. Avant la mise en œuvre, n'oubliez pas:
- cohérence éventuelle;
- il est souhaitable d'utiliser DDD dans des domaines séparés, vous ne devez pas créer un grand système complètement sur ce modèle;
- les modifications du schéma de la table d'événements peuvent être très douloureuses;
- De manière responsable, il convient d'aborder le choix de la granularité des événements, plus il y a d'événements concrets, plus ils seront sur la table et plus il faudra de ressources pour travailler avec eux.
Je serai heureux de constater des erreurs.