Event Sourcing ist eines der Architekturmuster im domänengesteuerten Design Thinking. Domain-Driven Design ist ein geschäftsorientierter Modellierungsansatz. Es hilft Entwicklern, Modelle zu erstellen, die näher am Geschäft sind. Heute werden wir über Event Sourcing in PHP sprechen.
Event Sourcing ist eines der Architekturmuster in der Designphilosophie Domain Driven Design. Domain-Driven Design ist ein geschäftsorientierter Modellierungsansatz. Es hilft Entwicklern, Modelle zu erstellen, die näher am Geschäft sind.
In herkömmlichen Anwendungen speichern wir den Status in der Datenbank. Wenn sich der Status ändert, aktualisieren wir sofort den entsprechenden Statuswert in der Datenbank. Bei der Ereignisbeschaffung wird ein völlig anderes Modell verwendet, und alle Zustände werden von Ereignissen abgeleitet. Wir erhalten den Status der Anwendung durch das Abspielen von Ereignissen, daher wird dies als Ereignisbeschaffung bezeichnet.
In diesem Artikel verwenden wir das Event-Sourcing-Modell, um einen vereinfachten Warenkorb zu schreiben, der mehrere wichtige Konzepte des Event-Sourcings aufschlüsselt. Wir werden auch die Event-Sourcing-Bibliothek von Spatie nutzen, um das Rad nicht neu erfinden zu müssen.
In unserem Fall können Benutzer den Inhalt des Warenkorbs hinzufügen, löschen und anzeigen, und es gibt zwei Geschäftslogiken:
Der Warenkorb kann nicht mehr als 3 Produkte hinzufügen. Wenn der Benutzer das vierte Produkt hinzufügt, sendet das System automatisch eine Benachrichtigungs-E-Mail.
Anforderungen und Haftungsausschluss
Dieser Artikel verwendet das Laravel-Framework. In diesem Artikel wird die spezifische Version spatie/laravel-event-sourcing:4.9.0 verwendet, um Syntaxprobleme zwischen verschiedenen Versionen zu vermeiden. Bei diesem Artikel handelt es sich nicht um eine Schritt-für-Schritt-Anleitung. Um diesen Artikel zu verstehen, müssen Sie über gewisse Grundkenntnisse verfügen. Vermeiden Sie wortreiche Wörter und konzentrieren Sie sich auf die Struktur des Architekturmusters. Der Schwerpunkt dieses Artikels liegt auf der Erläuterung der Kernidee von Event Sourcing. Die Implementierung von Event Sourcing in dieser Bibliothek ist nicht die einzige Lösung.
Domain-Ereignis
Ereignisse im Event-Sourcing werden als Domänenereignisse bezeichnet. Sie unterscheiden sich von herkömmlichen Transaktionsereignissen und weisen die folgenden Merkmale auf:
Es steht in engem Zusammenhang mit dem Geschäft, daher ist seine Benennung oft mit Geschäftsbegriffen verbunden und sollte nicht so sein mit der Datenbank verknüpft. Wenn Sie beispielsweise Produkte zum Warenkorb hinzufügen, sollte das entsprechende Domänenereignis ProductAddedToCart und nicht CartUpdated lauten. Es bezieht sich auf etwas, das passiert ist, daher muss es in der Vergangenheitsform stehen, z. B. ProductAddedToCart statt ProductAddToCart. Domänenereignisse können nur angehängt und nicht gelöscht oder geändert werden. Wenn sie gelöscht werden müssen, müssen wir Domänenereignisse mit Löschwirkung verwenden, z. B. ProductRemovedFromCart.
Basierend auf den oben genannten Informationen erstellen wir drei Feldereignisse:
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; } }
Events ProductAddedToCart und. ProductRemovedF romCart stellt jeweils Produkte dar, die zum Warenkorb hinzugefügt bzw. aus dem Warenkorb entfernt wurden. Das Ereignis CartCapacityExceeded stellt dar, dass die Artikel im Warenkorb das Limit überschreiten, was eine der zuvor erwähnten Geschäftslogiken ist.
Aggregate
Im domänengesteuerten Design bezieht sich Aggregate auf eine Gruppe eng verwandter Klassen. Sie bilden eine begrenzte Organisation. Auf Objekte außerhalb der Grenze kann nur über den Aggregate Root zugegriffen werden. Wenn es mit diesem Aggregat interagiert, ist der Aggregatstamm eine spezielle Klasse im Aggregat. Wir können uns die Aggregation als ein Familienhaushaltsregistrierungsbuch vorstellen, das über den Haushaltsvorstand (Aggregationsstamm) erfolgen muss.
Aggregation hat die folgenden Eigenschaften:
Sie gewährleistet die Unveränderlichkeit des Kerngeschäfts. Mit anderen Worten: Wir führen eine Verifizierung in Aggregation durch und lösen Ausnahmen für Vorgänge aus, die gegen die Geschäftslogik verstoßen. Hier treten Domänenereignisse auf. Domänenereignisse werden im Aggregatstamm generiert. Das heißt, wir können die Geschäftsanforderungen im Domänenereignis erfüllen. Es ist in sich geschlossen und hat klare Grenzen, das heißt, Methoden im Aggregat können nur über die Aggregatwurzel aufgerufen werden.
Aggregation ist der wichtigste und direkteste Teil, der der Geschäftslogik dient. Wir nutzen sie, um unser Geschäft intuitiv zu modellieren.
Zusammenfassend lässt sich sagen, dass wir einen CartAggregateRoot-Aggregatstamm erstellen:
<?php use Spatie\EventSourcing\AggregateRoots\AggregateRoot; class CartAggregateRoot extends AggregateRoot { public function addItem(int $productId, int $amount) { } public function removeItem(int $productId) { } }
CartAggregateRoot verfügt über zwei Methoden addItem und removeItem, die das Hinzufügen bzw. Entfernen von Produkten darstellen.
Darüber hinaus müssen wir einige Attribute hinzufügen, um den Inhalt des Warenkorbs aufzuzeichnen:
<?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; wann können wir ihm also einen Wert zuweisen? Bei der Ereignisbeschaffung erfolgt dies nach dem Eintreten des Ereignisses, daher müssen wir zuerst das Domänenereignis veröffentlichen:
<?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) ); } }
Beim Aufrufen der Ereignisse addItem und removeItem veröffentlichen wir jeweils die Ereignisse ProductAddedToCart und ProductRemovedFromCart. Gleichzeitig übergeben wir das Ereignis apply Zuweisung der magischen Methode zu $ Products:
<?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* ist die magische Methode, die in der Event-Sourcing-Bibliothek enthalten ist. Wenn wir recordThat zum Veröffentlichen eines Ereignisses verwenden, wird apply* automatisch aufgerufen. Es stellt sicher, dass die Statusänderung nach dem Ereignis erfolgt wird veröffentlicht.
现在 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视频教程
Das obige ist der detaillierte Inhalt vonLassen Sie uns über Event-Sourcing in PHP sprechen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!