您好,登錄后才能下訂單哦!
php中怎么實現事件溯源,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
事件溯源(Event Sourcing)是領域驅動設計(Domain Driven Design)設計思想中的架構模式之一。領域驅動設計是面向業務的一種建模方式。它幫助開發者建立更貼近業務的模型。
在傳統的應用程序中,我們將狀態儲存在數據庫中,當狀態發生改變時,我們即時更新數據庫中相對應的狀態值。事件溯源則采用一種截然不同的模式,它的核心是事件,所有的狀態都來源于事件,我們通過播放事件來獲取應用中的狀態,所以它叫事件溯源。
在本文中,我們將運用事件溯源模式編寫一個簡化的購物車,以此分解事件溯源的幾個重要組成概念。我們也將使用 Spatie 的事件溯源庫來避免重復造輪。
在我們的案例中,用戶可以添加,刪除以及查看購物車內容,同時它具備兩個業務邏輯:
購物車不可添加超過 3 種產品。當用戶添加第 4 種產品時,系統將自動發出一個預警郵件。
要求以及聲明
本文使用 Laravel 框架。本文使用特定版本 spatie/laravel-event-sourcing:4.9.0 以避免不同版本之間的語法問題。本文并非手把手的分步教程,你必須有一定 Laravel 基礎才可以理解本文,請避免咬文嚼字,關注架構模式的組成結構。本文的重點是闡述事件溯源的核心思想,此庫中對事件溯源的實現方式并非唯一方案。
領域事件(Domain Event)
事件溯源中的事件被稱為領域事件,與傳統的事務事件不同,它有以下幾個特點:
它與業務息息相關,所以它的命名往往夾帶業務名詞,而不應該與數據庫掛鉤。比如購物車增添商品,對應的領域事件應該是 ProductAddedToCart, 而不是 CartUpdated。它是指發生過的事情,所以它一定是過去式,比如 ProductAddedToCart 而不是 ProductAddToCart。領域事件只可追加,不可以刪除或者更改,如果需要刪除,我們需要使用具備刪除效果的領域事件,比如 ProductRemovedFromCart。
根據以上信息,我們構建三種領域事件:
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; } }
事件 ProductAddedToCart 和 ProductRemovedFromCart 分別代表商品加入購物車以及被從購物車中移除,事件 CartCapacityExceeded 代表購物車中商品超標,這是我們前面提到的業務邏輯之一。
聚合(Aggregate)
在領域驅動設計中,聚合(Aggregate)是指一組緊密相關的類,他們自成一體形成一個有邊界的組織,邊界外部的對象只可以通過聚合根(Aggregate Root)與此聚合交互,聚合根是聚合中的一種特殊的類。我們可以將聚合想象中一個家庭戶口本,對此戶口本進行任何操作,都必須通過戶主(聚合根)。
聚合具有以下幾個特點:
它確保核心業務的不變性。也就是說我們在聚合做驗證,對違反業務邏輯的操作拋出異常。它是領域事件的產生地。領域事件在聚合根中產生。也就是說我們可在領域事件已完成業務要求。它自成一體,具有明顯的邊界,也就是說,只能通過聚合根調用聚合中的方法。
聚合是服務于業務邏輯的主要以及最直接的部分,我們使用它直觀地為我們的業務建立模型。
綜上所述,讓我們構建一個 CartAggregateRoot 聚合根:
<?php use Spatie\EventSourcing\AggregateRoots\AggregateRoot; class CartAggregateRoot extends AggregateRoot { public function addItem(int $productId, int $amount) { } public function removeItem(int $productId) { } }
CartAggregateRoot 具備兩個方法 addItem 和 removeItem,分別代表添加以及移除商品。
另外我們還需要加些屬性來記錄購物車內容:
<?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; 將記錄購物車中的商品,那么我們什么時候可以為其賦值呢?在事件溯源中,這是在事件發生以后,所以我們首先需要發布領域事件:
<?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) ); } }
在調用 addItem 和 removeItem 事件時,我們分別發布 ProductAddedToCart 和 ProductRemovedFromCart 事件,與此同時,我們通過 apply 魔術方法為 $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* 是 Spatie 的事件溯源庫自帶的魔術方法,當我們使用 recordThat 發布事件時,apply* 會被自動調用,它確保狀態的改動是在事件發布以后。
現在 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()); } }
關于php中怎么實現事件溯源問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。