php中怎么實(shí)現(xiàn)事件溯源,針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。
創(chuàng)新互聯(lián)建站專注于鄱陽(yáng)網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供鄱陽(yáng)營(yíng)銷型網(wǎng)站建設(shè),鄱陽(yáng)網(wǎng)站制作、鄱陽(yáng)網(wǎng)頁(yè)設(shè)計(jì)、鄱陽(yáng)網(wǎng)站官網(wǎng)定制、成都小程序開(kāi)發(fā)服務(wù),打造鄱陽(yáng)網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供鄱陽(yáng)網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。
事件溯源(Event Sourcing)是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain Driven Design)設(shè)計(jì)思想中的架構(gòu)模式之一。領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)是面向業(yè)務(wù)的一種建模方式。它幫助開(kāi)發(fā)者建立更貼近業(yè)務(wù)的模型。
在傳統(tǒng)的應(yīng)用程序中,我們將狀態(tài)儲(chǔ)存在數(shù)據(jù)庫(kù)中,當(dāng)狀態(tài)發(fā)生改變時(shí),我們即時(shí)更新數(shù)據(jù)庫(kù)中相對(duì)應(yīng)的狀態(tài)值。事件溯源則采用一種截然不同的模式,它的核心是事件,所有的狀態(tài)都來(lái)源于事件,我們通過(guò)播放事件來(lái)獲取應(yīng)用中的狀態(tài),所以它叫事件溯源。
在本文中,我們將運(yùn)用事件溯源模式編寫(xiě)一個(gè)簡(jiǎn)化的購(gòu)物車,以此分解事件溯源的幾個(gè)重要組成概念。我們也將使用 Spatie 的事件溯源庫(kù)來(lái)避免重復(fù)造輪。
在我們的案例中,用戶可以添加,刪除以及查看購(gòu)物車內(nèi)容,同時(shí)它具備兩個(gè)業(yè)務(wù)邏輯:
購(gòu)物車不可添加超過(guò) 3 種產(chǎn)品。當(dāng)用戶添加第 4 種產(chǎn)品時(shí),系統(tǒng)將自動(dòng)發(fā)出一個(gè)預(yù)警郵件。
要求以及聲明
本文使用 Laravel 框架。本文使用特定版本 spatie/laravel-event-sourcing:4.9.0 以避免不同版本之間的語(yǔ)法問(wèn)題。本文并非手把手的分步教程,你必須有一定 Laravel 基礎(chǔ)才可以理解本文,請(qǐng)避免咬文嚼字,關(guān)注架構(gòu)模式的組成結(jié)構(gòu)。本文的重點(diǎn)是闡述事件溯源的核心思想,此庫(kù)中對(duì)事件溯源的實(shí)現(xiàn)方式并非唯一方案。
領(lǐng)域事件(Domain Event)
事件溯源中的事件被稱為領(lǐng)域事件,與傳統(tǒng)的事務(wù)事件不同,它有以下幾個(gè)特點(diǎn):
它與業(yè)務(wù)息息相關(guān),所以它的命名往往夾帶業(yè)務(wù)名詞,而不應(yīng)該與數(shù)據(jù)庫(kù)掛鉤。比如購(gòu)物車增添商品,對(duì)應(yīng)的領(lǐng)域事件應(yīng)該是 ProductAddedToCart, 而不是 CartUpdated。它是指發(fā)生過(guò)的事情,所以它一定是過(guò)去式,比如 ProductAddedToCart 而不是 ProductAddToCart。領(lǐng)域事件只可追加,不可以刪除或者更改,如果需要?jiǎng)h除,我們需要使用具備刪除效果的領(lǐng)域事件,比如 ProductRemovedFromCart。
根據(jù)以上信息,我們構(gòu)建三種領(lǐng)域事件:
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 分別代表商品加入購(gòu)物車以及被從購(gòu)物車中移除,事件 CartCapacityExceeded 代表購(gòu)物車中商品超標(biāo),這是我們前面提到的業(yè)務(wù)邏輯之一。
聚合(Aggregate)
在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中,聚合(Aggregate)是指一組緊密相關(guān)的類,他們自成一體形成一個(gè)有邊界的組織,邊界外部的對(duì)象只可以通過(guò)聚合根(Aggregate Root)與此聚合交互,聚合根是聚合中的一種特殊的類。我們可以將聚合想象中一個(gè)家庭戶口本,對(duì)此戶口本進(jìn)行任何操作,都必須通過(guò)戶主(聚合根)。
聚合具有以下幾個(gè)特點(diǎn):
它確保核心業(yè)務(wù)的不變性。也就是說(shuō)我們?cè)诰酆献鲵?yàn)證,對(duì)違反業(yè)務(wù)邏輯的操作拋出異常。它是領(lǐng)域事件的產(chǎn)生地。領(lǐng)域事件在聚合根中產(chǎn)生。也就是說(shuō)我們可在領(lǐng)域事件已完成業(yè)務(wù)要求。它自成一體,具有明顯的邊界,也就是說(shuō),只能通過(guò)聚合根調(diào)用聚合中的方法。
聚合是服務(wù)于業(yè)務(wù)邏輯的主要以及最直接的部分,我們使用它直觀地為我們的業(yè)務(wù)建立模型。
綜上所述,讓我們構(gòu)建一個(gè) CartAggregateRoot 聚合根:
<?php use Spatie\EventSourcing\AggregateRoots\AggregateRoot; class CartAggregateRoot extends AggregateRoot { public function addItem(int $productId, int $amount) { } public function removeItem(int $productId) { } }
CartAggregateRoot 具備兩個(gè)方法 addItem 和 removeItem,分別代表添加以及移除商品。
另外我們還需要加些屬性來(lái)記錄購(gòu)物車內(nèi)容:
<?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; 將記錄購(gòu)物車中的商品,那么我們什么時(shí)候可以為其賦值呢?在事件溯源中,這是在事件發(fā)生以后,所以我們首先需要發(fā)布領(lǐng)域事件:
<?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) ); } }
在調(diào)用 addItem 和 removeItem 事件時(shí),我們分別發(fā)布 ProductAddedToCart 和 ProductRemovedFromCart 事件,與此同時(shí),我們通過(guò) apply 魔術(shù)方法為 $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 的事件溯源庫(kù)自帶的魔術(shù)方法,當(dāng)我們使用 recordThat 發(fā)布事件時(shí),apply* 會(huì)被自動(dòng)調(diào)用,它確保狀態(tài)的改動(dòng)是在事件發(fā)布以后。
現(xiàn)在 CartAggregateRoot 已通過(guò)事件獲取了需要的狀態(tài),現(xiàn)在我們可以加入第一條業(yè)務(wù)邏輯:購(gòu)物車不可添加超過(guò) 3 種產(chǎn)品。
修改 CartAggregateRoot::addItem,當(dāng)用戶添加第 4 種產(chǎn)品時(shí),發(fā)布相關(guān)領(lǐng)域事件 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) ); }
現(xiàn)在我們已經(jīng)完成了聚合根工作,雖然代碼很簡(jiǎn)單,但是根據(jù)模擬業(yè)務(wù)而建立的模型非常直觀。
加入商品時(shí),我們調(diào)用:
CartAggregateRoot::retrieve(Uuid::uuid4())->addItem(1, 100);
加入商品時(shí),我們調(diào)用:
CartAggregateRoot::retrieve($uuid)->removeItem(1);
放映機(jī)(Projector)
UI 界面是應(yīng)用中不可缺少的部分,比如向用戶展示購(gòu)物車中的內(nèi)容,通過(guò)重播聚合根或許會(huì)有性能問(wèn)題。此時(shí)我們可以使用放映機(jī)(Projector)。
放映機(jī)實(shí)時(shí)監(jiān)控領(lǐng)域事件,我們通過(guò)它可以建立服務(wù)于 UI 的數(shù)據(jù)庫(kù)表。放映機(jī)的特點(diǎn)是它可以重塑,當(dāng)我們發(fā)現(xiàn)代碼中的 bug 影響到 UI 數(shù)據(jù)時(shí),我們可以重塑此放映機(jī)建立的表單。
讓我們寫(xiě)一個(gè)服務(wù)于用戶的放映機(jī) 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(); } }
放映機(jī) CartProjector
會(huì)根據(jù)監(jiān)聽(tīng)的事件來(lái)增加或者刪除表單 projection_carts,ProjectionCart 是一個(gè)普通的 Laravel 模型,我們僅使用它來(lái)操作數(shù)據(jù)庫(kù)。
當(dāng)我們的 UI 需要展示購(gòu)物車中的內(nèi)容時(shí),我們從 projection_carts 讀取數(shù)據(jù),這和讀寫(xiě)分離有異曲同工之妙。
反應(yīng)機(jī)(Reactor)
反應(yīng)機(jī)(Reactor)和放映機(jī)一樣,實(shí)時(shí)監(jiān)控領(lǐng)域事件。不同的是反應(yīng)機(jī)不可以重塑,它的用途是用來(lái)執(zhí)行帶有副作用的操作,所以它不可以重塑。
我們使用它來(lái)實(shí)現(xiàn)我們的第二個(gè)業(yè)務(wù)邏輯:當(dāng)用戶添加第 4 個(gè)產(chǎn)品時(shí),系統(tǒng)將自動(dòng)發(fā)出一個(gè)預(yù)警郵件。
<?php use Spatie\EventSourcing\EventHandlers\Reactors\Reactor; class WarningReactor extends Reactor { public function onCartCapacityExceeded(CartCapacityExceeded $event) { Mail::to('admin@corporation.com')->send(new CartWarning()); } }
關(guān)于php中怎么實(shí)現(xiàn)事件溯源問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
網(wǎng)頁(yè)題目:php中怎么實(shí)現(xiàn)事件溯源
網(wǎng)頁(yè)網(wǎng)址:http://muchs.cn/article38/pioppp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供小程序開(kāi)發(fā)、品牌網(wǎng)站設(shè)計(jì)、虛擬主機(jī)、企業(yè)網(wǎng)站制作、靜態(tài)網(wǎng)站、微信公眾號(hào)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)