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