溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

php中怎么實(shí)現(xiàn)事件溯源

發(fā)布時(shí)間:2021-07-06 17:58:57 來源:億速云 閱讀:146 作者:Leah 欄目:編程語(yǔ)言

php中怎么實(shí)現(xiàn)事件溯源,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎn)單易行的方法。

事件溯源(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ù)的一種建模方式。它幫助開發(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)都來源于事件,我們通過播放事件來獲取應(yīng)用中的狀態(tài),所以它叫事件溯源。

在本文中,我們將運(yùn)用事件溯源模式編寫一個(gè)簡(jiǎn)化的購(gòu)物車,以此分解事件溯源的幾個(gè)重要組成概念。我們也將使用 Spatie 的事件溯源庫(kù)來避免重復(fù)造輪。

在我們的案例中,用戶可以添加,刪除以及查看購(gòu)物車內(nèi)容,同時(shí)它具備兩個(gè)業(yè)務(wù)邏輯:

購(gòu)物車不可添加超過 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ǔ)法問題。本文并非手把手的分步教程,你必須有一定 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ā)生過的事情,所以它一定是過去式,比如 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ì)象只可以通過聚合根(Aggregate Root)與此聚合交互,聚合根是聚合中的一種特殊的類。我們可以將聚合想象中一個(gè)家庭戶口本,對(duì)此戶口本進(jìn)行任何操作,都必須通過戶主(聚合根)。

聚合具有以下幾個(gè)特點(diǎn):

它確保核心業(yè)務(wù)的不變性。也就是說我們?cè)诰酆献鲵?yàn)證,對(duì)違反業(yè)務(wù)邏輯的操作拋出異常。它是領(lǐng)域事件的產(chǎn)生地。領(lǐng)域事件在聚合根中產(chǎn)生。也就是說我們可在領(lǐng)域事件已完成業(yè)務(wù)要求。它自成一體,具有明顯的邊界,也就是說,只能通過聚合根調(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,分別代表添加以及移除商品。

另外我們還需要加些屬性來記錄購(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í),我們通過 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 已通過事件獲取了需要的狀態(tài),現(xiàn)在我們可以加入第一條業(yè)務(wù)邏輯:購(gòu)物車不可添加超過 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)容,通過重播聚合根或許會(huì)有性能問題。此時(shí)我們可以使用放映機(jī)(Projector)。

放映機(jī)實(shí)時(shí)監(jiān)控領(lǐng)域事件,我們通過它可以建立服務(wù)于 UI 的數(shù)據(jù)庫(kù)表。放映機(jī)的特點(diǎn)是它可以重塑,當(dāng)我們發(fā)現(xiàn)代碼中的 bug 影響到 UI 數(shù)據(jù)時(shí),我們可以重塑此放映機(jī)建立的表單。

讓我們寫一個(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)聽的事件來增加或者刪除表單 projection_carts,ProjectionCart 是一個(gè)普通的 Laravel 模型,我們僅使用它來操作數(shù)據(jù)庫(kù)。

當(dāng)我們的 UI 需要展示購(gòu)物車中的內(nèi)容時(shí),我們從 projection_carts 讀取數(shù)據(jù),這和讀寫分離有異曲同工之妙。

反應(yīng)機(jī)(Reactor)

反應(yīng)機(jī)(Reactor)和放映機(jī)一樣,實(shí)時(shí)監(jiān)控領(lǐng)域事件。不同的是反應(yīng)機(jī)不可以重塑,它的用途是用來執(zhí)行帶有副作用的操作,所以它不可以重塑。

我們使用它來實(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)事件溯源問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI