剖玄析微聚合 - 事件溯源

xuding發表於2021-08-09


本文轉載自【何以解耦】:codedecoupled.com/php-es-aggregate...

聚合是 DDD(領域驅動設計)中一個相對複雜的概念。作為 DDD 戰術設計中舉足輕重的工具,我們有必要對其瞭若指掌。

總體來說,聚合(Aggregate)是指一組緊密相關的類,他們自成一體形成一個有邊界的組織,邊界外部的物件只可以通過聚合根(Aggregate Root)與此聚合互動:

剖玄析微聚合 - 事件溯源

聚合常會與泛型 collection 混淆,他們的區別在於聚合是相對於領域模型而言,而 collection 是一種廣泛的概念。那麼具體來說,除了聚合根,聚合內部還有哪些類呢?本文闡述一個事件溯源的架構中,常見的幾個重要組成部分(類)。

實體

實體為獨立事物的建模工具,每個實體都擁有唯一識別符號,比如 ID, UUIDUsername 等等。大多數情況下,實體是可變的,它的狀態會隨著時間的遷移而改變。

實體很好理解, 如果我們用 DDD 來分析 ORM 業務,那麼 ORM 中的 Model 可以理解為實體。資料庫中的每一條資料都擁有自己的 ID(唯一識別符號),每條資料的內容也會變化無常。

用實體為使用者(User)建模:

class User extends Model
{
    private int $id;


    public function __construct(int $id)
    {
        $this->id = $id;
    }


    public function changePassword(string $password)
    {
        // domain code
    }

}


$user = new User(1);
$user->changePassword();

值物件

值物件是領域中用來描述,量化或者測量實體的模型。和實體不同,值物件沒有唯一的識別符號,兩個對等的值物件是可以替換的。值物件具有不變性(Immutability),一旦建立以後,一個值物件的屬性就定型了,不可更改。

值物件是一個強大但通常被忽略的建模工具。使用值物件建模可以大大簡化我們的程式碼。下面我們來分析一下值物件幾個特性。

無識別符號性

值物件一定沒有諸如 ID, UUID 等等之類的識別符號。

不變性

值物件初始化以後,他的值變無法被更改。我們在用值物件建模時需要特別注意,不應該給值物件賦予改變其內部屬性的功能。如果需要變異方法時,此方法必須建立一個全新的值物件(見以下 Money::add())。

class Money
{
    private int $amountInCent;

    private string $currency;

    public function __construct(int $amountInCent, string $currency)
    {
        $this->amountInCent = $amountInCent;
        $this->currency = $currency;
    }

    public function getAmountInCent(): int
    {
        return $this->amountInCent;
    }

    public function getCurrency(): string
    {
        return $this->currency;
    }


    public function add(Money $money): Money
    {
        if ($this->currency !== $this->getCurrency()) {
            throw new \Exception('You can not add two different currency');
        }

        return new Money (
            $this->amountInCent + $money->getAmountInCent(),
            $this->currency
        );
    }
}
可替換性

值物件的不變性奠定了其可替換性的基礎,如果兩個值物件的值對等,我們可以放心地替換兩個值物件。我們可以這樣理解這個概念:如果甲有一張10塊錢人民幣鈔票,乙也有一張10塊錢人民幣鈔票,他們可以進行對等交換而不會引起利益糾紛。

領域中很多模型都可以使用值物件來建模,它的特性給予我們一個安全的,靈活的環境來管理我們的程式碼。比如上節中的 $password 就可以使用值物件來完成。

class Password
{
    private string $value;

    public function __construct(string $value)
    {
        $this->guard($value);
        $this-> value = $value;
    }

    public function toString()
    {
          return $this->value;
    }    

    private function guard($value)
    {
       if (empty($value)) {
           throw new /Exception('invalid password value');
       }
    }
}


$user = new User(1);
$password = new Password($passwordStr);
$user->changePassword($password);

初始化成功後的 $password 一定是有效的,因為我們在初始化中做了防守。值物件為我們提供了一個安全的模型,接下來的程式碼中我們都無需擔心 $password 是否符合業務要求。

聚合根

聚合根的本質是實體,或者說聚合根是一種特殊的實體。聚合根的特殊性表現在它在聚合中的地位,它是聚合對外的唯一介面。如果我們把聚合想象成一個戶口簿,那麼聚合根就好像戶主,任何戶口簿的操作行為都需要通過戶主。

領域事件

領域事件是領域中與業務息息相關的業務事件,它與基礎設施,程式碼框架無關,所以領域事件不應該是諸如 EmailSentRecordUpdated 之類的極其廣泛的框架事件。

領域事件在聚合根中產生,然後被儲存持久化。領域事件持久化是最後一個步驟,以此來確保所有的領域事件的正確性。如有任何異常,領域事件都不會被錯誤地儲存起來。

總結

在一個事件溯源的架構中,一個聚合必定是由一個聚合根以及以上一個或多個部分組成。

本文轉載自【何以解耦】: codedecoupled.com/php-es-aggregate... ,如果你也對 TDD,DDD 以及簡潔程式碼感興趣,歡迎關注公眾號【何以解耦】,一起探索軟體開發之道。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
Know how, know why meanwhile.

相關文章