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.
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; } }
ProductRemovedFromCart:
<?php use Spatie\EventSourcing\StoredEvents\ShouldBeStored; class ProductRemovedFromCart extends ShouldBeStored { public int $productId; public function __construct(int $productId) { $this->productId = $productId; } }
CartCapacityExceeded:
<?php use Spatie\EventSourcing\StoredEvents\ShouldBeStored; class CartCapacityExceeded extends ShouldBeStored { public array $currentProducts; public function __construct(array $currentProducts) { $this->currentProducts = $currentProducts; } }
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) { } }
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) { } }
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) ); } }
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; }); } }
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) ); }
现在我们已经完成了聚合根工作,虽然代码很简单,但是根据模拟业务而建立的模型非常直观。
加入商品时,我们调用:
CartAggregateRoot::retrieve(Uuid::uuid4())->addItem(1, 100);
加入商品时,我们调用:
CartAggregateRoot::retrieve($uuid)->removeItem(1);
放映机(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('product_id', $event->productId)->delete(); } }
放映机 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('admin@corporation.com')->send(new CartWarning()); } }
反应机 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!