Maison > développement back-end > Problème PHP > Parlons du sourcing d'événements en php

Parlons du sourcing d'événements en php

醉折花枝作酒筹
Libérer: 2023-03-11 09:38:02
avant
1633 Les gens l'ont consulté

Le sourcing d'événements est l'un des modèles architecturaux de la pensée conceptuelle axée sur le domaine. La conception pilotée par domaine est une approche de modélisation orientée métier. Il aide les développeurs à créer des modèles plus proches de l’entreprise. Aujourd'hui, nous allons parler du sourcing d'événements en php.

Parlons du sourcing d'événements en php

Event Sourcing est l'un des modèles architecturaux de la philosophie de conception Domain Driven Design. La conception pilotée par domaine est une approche de modélisation orientée métier. Il aide les développeurs à créer des modèles plus proches de l’entreprise.

Dans les applications traditionnelles, nous stockons l'état dans la base de données. Lorsque l'état change, nous mettons immédiatement à jour la valeur d'état correspondante dans la base de données. Le sourcing d'événements adopte un modèle complètement différent. Son noyau est constitué d'événements, et tous les états sont dérivés des événements. Nous obtenons le statut de l'application en jouant des événements, c'est ce qu'on appelle le sourcing d'événements.

Dans cet article, nous utiliserons le modèle de sourcing événementiel pour rédiger un panier d'achat simplifié afin de décomposer plusieurs concepts importants du sourcing événementiel. Nous utiliserons également la bibliothèque de sourcing d'événements de Spatie pour éviter de réinventer la roue.

Dans notre cas, les utilisateurs peuvent ajouter, supprimer et visualiser le contenu du panier, et cela a deux logiques métier :

Le panier ne peut pas ajouter plus de 3 produits. Lorsque l'utilisateur ajoute le 4ème produit, le système enverra automatiquement un email d'alerte.

Exigences et clauses de non-responsabilité

Cet article utilise le framework Laravel. Cet article utilise la version spécifique spatie/laravel-event-sourcing:4.9.0 pour éviter les problèmes de syntaxe entre les différentes versions. Cet article n'est pas un didacticiel étape par étape. Vous devez avoir une certaine connaissance de base de Laravel pour comprendre cet article. Veuillez éviter les mots verbeux et vous concentrer sur la structure du modèle architectural. L'objectif de cet article est de développer l'idée centrale du sourcing d'événements. La mise en œuvre du sourcing d'événements dans cette bibliothèque n'est pas la seule solution.

Événement de domaine

Les événements dans le sourcing d'événements sont appelés événements de domaine. Différent des événements de transaction traditionnels, ils présentent les caractéristiques suivantes :

Ils sont étroitement liés aux affaires, leur dénomination est donc souvent entraînée par des termes commerciaux et ne devrait pas l'être. lié à la base de données. Par exemple, lors de l'ajout de produits au panier, l'événement de domaine correspondant doit être ProductAddedToCart et non CartUpdated. Il fait référence à quelque chose qui s'est produit, il doit donc être au passé, par exemple ProductAddedToCart au lieu de ProductAddToCart. Les événements de domaine peuvent uniquement être ajoutés et ne peuvent pas être supprimés ou modifiés. S'ils doivent être supprimés, nous devons utiliser des événements de domaine avec des effets de suppression, tels que ProductRemovedFromCart.

Sur la base des informations ci-dessus, nous construisons trois événements de champ :

ProductAddedToCart:

<?php
use Spatie\EventSourcing\StoredEvents\ShouldBeStored;
class ProductAddedToCart extends ShouldBeStored
{
    public int $productId;
    public int $amount;
    public function __construct(int $productId, int $amount)
    {
        $this->productId = $productId;
        $this->amount = $amount;
    }
}
Copier après la connexion


ProductRemovedFromCart:

<?php
use Spatie\EventSourcing\StoredEvents\ShouldBeStored;
class ProductRemovedFromCart extends ShouldBeStored
{
    public int $productId;
    public function __construct(int $productId)
    {
        $this->productId = $productId;
    }
}
Copier après la connexion


CartCapacityExceeded:

<?php
use Spatie\EventSourcing\StoredEvents\ShouldBeStored;
class CartCapacityExceeded extends ShouldBeStored
{
    public array $currentProducts;
    public function __construct(array $currentProducts)
    {
        $this->currentProducts = $currentProducts;
    }
}
Copier après la connexion

Les événements ProductAddedToCart et ProductRemovedFromCart représentent respectivement produits ajoutés au panier et retirés du panier Sauf, l'événement CartCapacityExceeded représente que les articles dans le panier dépassent la limite, ce qui est l'une des logiques métier que nous avons mentionnées précédemment.

Aggregate

Dans la conception basée sur le domaine, Aggregate fait référence à un groupe de classes étroitement liées. Elles sont autonomes pour former une organisation délimitée. Les objets en dehors de la limite ne sont accessibles que via la racine d'agrégat (Aggregate Root). interagit avec cet agrégat, la racine de l'agrégat est une classe spéciale dans l'agrégat. On peut imaginer l'agrégation comme un livret de ménage familial. Toute opération sur ce livret de ménage doit passer par le chef de ménage (racine d'agrégation).

L'agrégation présente les caractéristiques suivantes :

Elle assure l'immuabilité du cœur de métier. En d’autres termes, nous effectuons une vérification par agrégation et générons des exceptions pour les opérations qui violent la logique métier. C'est là que se produisent les événements de domaine. Les événements de domaine sont générés dans la racine agrégée. C'est-à-dire que nous pouvons répondre aux exigences commerciales lors de l'événement de domaine. Il est autonome et a des limites claires, c'est-à-dire que les méthodes de l'agrégat ne peuvent être appelées que via la racine de l'agrégat.

L'agrégation est la partie principale et la plus directe qui sert la logique métier. Nous l'utilisons pour modéliser intuitivement notre entreprise.

Pour résumer, construisons une racine agrégée CartAggregateRoot :

<?php
use Spatie\EventSourcing\AggregateRoots\AggregateRoot;
class CartAggregateRoot extends AggregateRoot
{
    public function addItem(int $productId, int $amount)
    {
    }
    public function removeItem(int $productId)
    {
    }
}
Copier après la connexion

CartAggregateRoot a deux méthodes addItem et RemoveItem, qui représentent respectivement l'ajout et la suppression de produits.

De plus, nous devons ajouter quelques attributs pour enregistrer le contenu du panier :

<?php
use Spatie\EventSourcing\AggregateRoots\AggregateRoot;
class CartAggregateRoot extends AggregateRoot
{
    private array $products;
    public function addItem(int $productId, int $amount)
    {
    }
    public function removeItem(int $productId)
    {
    }
}
Copier après la connexion

private array $products enregistrera les produits dans le panier, alors quand pouvons-nous lui attribuer une valeur ? Dans le sourcing d'événements, c'est après que l'événement se produit, nous devons donc d'abord publier l'événement de domaine :

<?php
use Spatie\EventSourcing\AggregateRoots\AggregateRoot;
class CartAggregateRoot extends AggregateRoot
{
    private array $products;
    public function addItem(int $productId, int $amount)
    {
        $this->recordThat(
            new ProductAddedToCart($productId, $amount)
        );
    }
    public function removeItem(int $productId)
    {
        $this->recordThat(
            new ProductRemovedFromCart($productId)
        );
    }
}
Copier après la connexion

Lors de l'appel des événements addItem et RemoveItem, nous publions respectivement les événements ProductAddedToCart et ProductRemovedFromCart. En même temps, nous transmettons l'application. méthode magique pour $ Assignation des produits :

<?php
use Spatie\EventSourcing\AggregateRoots\AggregateRoot;
class CartAggregateRoot extends AggregateRoot
{
    private array $products;
    public function addItem(int $productId, int $amount)
    {
        $this->recordThat(
            new ProductAddedToCart($productId, $amount)
        );
    }
    public function removeItem(int $productId)
    {
        $this->recordThat(
            new ProductRemovedFromCart($productId)
        );
    }
    public function applyProductAddedToCart(ProductAddedToCart $event)
    {
        $this->products[] = $event->productId;
    }
    public function applyProductRemovedFromCart(ProductRemovedFromCart $event)
    {
        $this->products[] = array_filter($this->products, function ($productId) use ($event) {
            return $productId !== $event->productId;
        });
    }
}
Copier après la connexion

apply* est la méthode magique fournie avec la bibliothèque de sourcing d'événements de Spatie. Lorsque nous utilisons recordThat pour publier un événement, apply* sera automatiquement appelé. Il garantit que l'état change après l'événement. publié.

现在 CartAggregateRoot 已通过事件获取了需要的状态,现在我们可以加入第一条业务逻辑:购物车不可添加超过 3 种产品。

修改 CartAggregateRoot::addItem,当用户添加第 4 种产品时,发布相关领域事件 CartCapacityExceeded:

public function addItem(int $productId, int $amount)
{
    if (count($this->products) >= 3) {
        $this->recordThat(
            new CartCapacityExceeded($this->products)
        );
        return;
    }
    $this->recordThat(
        new ProductAddedToCart($productId, $amount)
    );
}
Copier après la connexion

现在我们已经完成了聚合根工作,虽然代码很简单,但是根据模拟业务而建立的模型非常直观。

加入商品时,我们调用:

CartAggregateRoot::retrieve(Uuid::uuid4())->addItem(1, 100);
Copier après la connexion

加入商品时,我们调用:

CartAggregateRoot::retrieve($uuid)->removeItem(1);
Copier après la connexion

放映机(Projector)

UI 界面是应用中不可缺少的部分,比如向用户展示购物车中的内容,通过重播聚合根或许会有性能问题。此时我们可以使用放映机(Projector)。

放映机实时监控领域事件,我们通过它可以建立服务于 UI 的数据库表。放映机的特点是它可以重塑,当我们发现代码中的 bug 影响到 UI 数据时,我们可以重塑此放映机建立的表单。

让我们写一个服务于用户的放映机 CartProjector:

<?php
use Spatie\EventSourcing\EventHandlers\Projectors\Projector;
class CartProjector extends Projector
{
    public function onProductAddedToCart(ProductAddedToCart $event)
    {
        $projection = new ProjectionCart();
        $projection->product_id = $event->productId;
        $projection->saveOrFail();
    }
    public function onProductRemovedFromCart(ProductRemovedFromCart $event)
    {
        ProjectionCart::where(&#39;product_id&#39;, $event->productId)->delete();
    }
}
Copier après la connexion

放映机 CartProjector

会根据监听的事件来增加或者删除表单 projection_carts,ProjectionCart 是一个普通的 Laravel 模型,我们仅使用它来操作数据库。

当我们的 UI 需要展示购物车中的内容时,我们从 projection_carts 读取数据,这和读写分离有异曲同工之妙。

反应机(Reactor)

反应机(Reactor)和放映机一样,实时监控领域事件。不同的是反应机不可以重塑,它的用途是用来执行带有副作用的操作,所以它不可以重塑。

我们使用它来实现我们的第二个业务逻辑:当用户添加第 4 个产品时,系统将自动发出一个预警邮件。

<?php
use Spatie\EventSourcing\EventHandlers\Reactors\Reactor;
class WarningReactor extends Reactor
{
    public function onCartCapacityExceeded(CartCapacityExceeded $event)
    {
        Mail::to(&#39;admin@corporation.com&#39;)->send(new CartWarning());
    }
}
Copier après la connexion

反应机 WarningReactor 

会监听到事件 CartCapacityExceeded, 我们就会使用 Laravel Mailable 发送一封警报邮件。

总结

至此我们简单的介绍了事件溯源的几个组成部分。软件的初衷是运用我们熟悉的编程语言来解决复杂的业务问题。为了解决现实中的业务问题,大神们发明了面向对象编程(OOP),于是我们可以避免写出面条代码,可以建立最贴近现实的模型。但是由于某种原因, ORM 的出现让大多数开发者的模型停留在了数据库层面,模型不应该是对数据库表的封装,而是对业务的封装。面向对象编程赋予我们的是对业务对象更精确的建模能力。数据库的设计,数据的操作并不是软件关注的核心,业务才是。

在软件设计之初,我们应该忘记数据库设计,将注意力放到业务上面。

推荐学习:php视频教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:何以解耦
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal