本文轉載自【何以解耦】: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 協議》,轉載必須註明作者和本文連結