從貧血模型到充血模型

xuding發表於2021-08-30


本文轉載自【何以解耦】:codedecoupled.com/anemic-domain-mo...

領域模型

領域模型是一種用於解決複雜業務邏輯的建模工具/思想。大神 Martin Fowler 定義其為包含行為和資料的物件模型。

使用領域模型時,我們的目標是建立豐富的充血領域模型,但由於某種原因,一種不被提倡的模式(貧血模型)被廣泛使用。

貧血領域模型

貧血模型是一種眾所周知的反模式(anti-pattern),但是要擯棄這種思想卻不是簡單的事情。因為這種設計開發迅速,易於理解。不經意間,它就可能出現在我們的程式碼中。

舉個例子,我們的應用中有一個啟用使用者功能:

class User extends OrmModel
{
    public setStatus($status)
    {
        $this->status = $status;
    }

    public getStatus()
    {
        return $this->status;
    }
}

class UserService
{
   public activate(User $user)
   {
           if ($user->getStatus() === 'blacklisted') {
               throw new \Exception('User is blacklisted');
           }

         $user->setStatus('active');
         $user->save();
   }
}

乍一看,這種模式有模有樣:資料庫表透過對映建造一些 ORM 類,各類之間存在一定的關係,而且還使用了服務層(Service layer)進行分層管理。

但是讓我們仔細想想。User 類無非一些 getter 和 setter,符合了領域模型包含資料的標準,卻缺失了最重要的部分:行為。而將業務邏輯放入服務類(Service class)中,形成了一種類似於事務處理指令碼的程式導向的程式設計方式,與領域模型所倡導的物件導向程式設計背道而馳。一個健康的服務層應該是很薄的,其主要職責是呼叫充血的領域模型完成業務流程。

這種貧血模型建立在領域模型的成本上,卻沒有帶來領域模型的好處。

充血領域模型

充血領域模型是領域模型所提倡的建模方式。顧名思義,充血領域模型是包含行為和資料的物件模型。

舉個例子,上文中的啟用使用者功能:

class User extends OrmModel
{
    public function activate()
    {
        if ($user->status === 'blacklisted') {
               throw new \Exception('User is blacklisted');
           }
           $this->status = 'active';
    }
}

class UserService
{
   public function activate(User $user)
   {           
         $user->activate();
         $user->save();
   }
}

一個完善的充血領域模型能降低 Bug 風險,幫助我們更直觀的建立業務模型。

建立充血領域模型需要開發者具備良好的物件導向思想。能夠準確劃分服務層邏輯和領域層(Domain Layer)邏輯。
經驗法則是,在建立服務層時候,業務邏輯應被封至領域層,事務邏輯封裝應被封至服務層,因此服務層應該是很薄的。此處的薄不能完全代表程式碼量,而是指業務邏輯含量。

對症下藥

領域模型的目的是簡化一個複雜的問題,而不是將一個簡單的問題複雜化。正如 Martin Fowler 所說,領域模型並非包治百病的靈丹妙藥,開發者在解決問題時還需對症下藥:

從貧血模型到充血模型

如果你處理的問題使用增刪改查即可完成,那麼使用譬如 Active Record 這種資料驅動模式才是更適宜的方案。使用領域模型來解決簡單的 CURD 問題,那就成了牛刀割雞,得不償失。

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

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

相關文章