l

2019年12月16日 星期一

CRUD魔咒

Dec. 16 09:59~11:19


靜態的領域模型

最近在準備明年新修訂的課程【領域驅動設計與簡潔架構入門實作班】,花了一些時間重讀Alberto Brandolini的《Event Storming》。書中提到Event Storming的Domain Event(領域事件)應該以使用者操作為出發點去思考,而不僅是傳統的CRUD所產生的事件

讀到這段話Teddy突然想到:「自己在練習Event Storming也遇過同樣的疑問,怎麼許多領域事件都是代表CRUD的事件?」

***

領域驅動設計(Domain-Driven Design;DDD)有一個重點就是開發人員與領域專家一起討論領域模型,這個領域模型用來解釋與理解問題領域,成為開發團隊和領域專家在溝通時的通用語言。

使用領域模型(Domain Model)而不是資料模型(Data Model),這一點一直是物件導向分析與設計以及DDD的核心,但是以資料庫(或是使用者介面)為主的軟體設計思考方式已經深入許多開發人員的骨子裡。就算已經有了物件導向領域模型,在設計功能(use case或user story)時總是免不了寫出一堆CRUD(新增、讀取、修改、刪除)

***

動態的使用者操作

▲圖1:代表CRUD的領域事件

Teddy之前用Event Storming來塑模看板系統的工作項目(Work Item),當時直接反應就寫下圖1這三個領域事件。但這就是CRUD啊,系統的行為好像沒有被真實反應出來。


▲圖2:Kanbanize系統修改卡片的功能

上週Teddy花了點時間研究Kanbanize,這是一個做的很棒的商業看板系統,它的卡片(Card,在Teddy的領域模型中叫做WorkItem)提供很多使用者可以直接操作的功能,如圖2所示。例如:

  • Assignee:指派認領工作的人
  • Priority:設定優先順序為Low、Average、High、Critical
  • Deadline:設定卡片的截止日期
  • Done:把卡片移到完成階段

以上面四個操作為例,應該會產生類似以下的領域事件:

  • CardAssigned
  • PrioritySet
  • DeadlineSet
  • CardFinished

而不是只用一個CardUpdated來代表這些系統操作。

***

不只是粒度大小的差異

有些人可能會認為:「這只是系統功能粒度大小的差別而已啊。CardUpdated粒度比較大,包含了CardAssigned、PrioritySet、DeadlineSet、CardFinished這四個事件。」

某種程度來說,的確是功能的粒度大小不同,但除此之外還有更深層的含意。

系統行為有沒有被塑模出來?

換句話說,領域專家與開發人員針對系統的行為到底溝通到什麼程度?有沒有忽略了什麼重要的系統行為?

很顯然地,如果只是討論CRUD,使用者針對「卡片」本身有意義的操作都躲在「修改卡片」這個功能之下,因此很容易忽略的「修改卡片」這個功能在看板系統這個領域中,到底對於使用者的領域意義是什麼。如此一來,軟體功能並沒有反應出系統的領域知識,因而降低系統的易用性。

***

友藏內心獨白:系統行為的塑模和用戶體驗有關。

3 則留言:

  1. Teddy 大哥晚安:
    剛剛參加完DDD年會,有些實作上問題想要問您。就您剛剛舉例的,因為 Stage 只會屬於某個Workflow,所以應該由 WorkflowRepository 取出 Workflow 時一併取出。

    我的問題是,這時候WorkflowRepository內部應該內嵌一個 StageRepository嗎?
    有可能這時候的 Stage 也是另一個AggregateRoot嗎 ?

    回覆刪除
    回覆
    1. 這時候WorkflowRepository內部應該內嵌一個 StageRepository嗎? ==> 不需要,因為一個 Aggregate 對應一個 Repository,Stage是一個Entity,是Workflow這個Aggregate裡面的一個物件。Workflow是Aggregate Root,透過它來存取Stage。

      Stage既然是Workflow裡面的一個Entity,它就不會是另一個Aggregate Root。

      刪除