l

2020年12月18日 星期五

領域驅動設計學習筆記(11):Bounded Context(上)

Dec. 18 13:01~17:35

▲ezKanban的儀表板畫面


只有一個Context的問題

Bounded Context在領域驅動設計(Domain-Driven Design;DDD)中是一個很重要個概念,今天先從實作面談一下有無Bounded Context的差別。以往傳統的物件導向設計,整個問題領域就是一個巨大的Context,因此在solution domain自然產生一個很大的物件類別圖, 如圖1所示。

▲圖1:Class diagram,取自JCIS


為了簡化程式設計,這個object graph之內的物件關係往往被設計成雙向連結。例如,圖2是一個用傳統OOAD所設計出的看板系統,其簡化後的類別圖:

  • Team:團隊,每一位使用者至少屬於一個團隊。
  • Project:專案,一個團隊有零到多個專案,可以從團隊得到屬於該團隊的專案,也可以從專案得到所屬的團隊,團隊與專案兩者之間是雙向參考關係。
  • Board:板,代表一個看板。一個專案可以有零到多個看板。專案與板之間也是雙向參考關係。
  • Workflow:工作流,一個板可以有零到多個工作流,兩者之間也是雙向參考關係。
  • Lane:道,代表工作階段。Lane有兩個子類別,分別是代表垂直的Stage,以及代表水平的Swimlane。Lane與Workflow之間也是雙向參考關係。

▲圖2:Class diagram,取自JCIS


程式開發人員可以用team.getProject().getBoard().getWorkflow().getStage() 得到某個Stage。相反地,也可以從stage.getWorkflow().getBoard().getProject().getTeam()得到該Stage所屬的團隊。這麼做的好處是,物件之間靠著參考 (reference) 整個串在一起,操作物件感覺很方便。有優點一定也有缺點,主要的缺點有三項:

  • 相依性太重:任一物件修改都可能會影響其他物件,容易產生漣波效應。
  • 物件永久性與平行處裡的能力受限:為了達到整個系統交易(transaction)的一致性,物件與關連式資料庫經常透過多的表格與外鍵關係來實現。當使用者更新系統物件時,可能需要鎖定多個表格,因而降低系統的平行處理能力。
  • 物件屬性與責任膨脹:整個問題領域只有一個Context,如果問題領域夠大,相同的類別在不同的功能中可能有不同的屬性與責任。例如,從Team與Project的角度,Board只是一種被分類的「東西」。除了board id與board name以外,它們並不在乎Board裡面的實質內容。但是,從Board、Workflow、Lane的角度,Board的內容是整個功能的核心。因為整個系統只有一個Context,因此只有一個Board類別,所以Board類別身上就會包含所有「客戶端」所需要的資料與方法。

***


Bounded Context

▲圖3:兩個bounded contexts


如圖3所示,有了bounded context概念,如果把原本的問題分成兩個bounded contexts:

  • Project Management
  • Board Management

Board同時出現在兩個bounded contexts之中,但是有著不同的意義與屬性。Project Management的Board只需要有board id、board name、project id、team id即可。而Board Management的Board才需要workflows等資料。

如此一來,當Project Management移動Board,就與Board Management的Board沒有關係,解決了上述只有一個Context所造成的三個問題:

  • 相依性太重
  • 物件永久性與平行處裡的能力受限
  • 物件屬性與責任膨脹

區分Bounded Context還有其他好處,像是在多個Bounded Context中找出企業最具競爭力的core domain,以便區分業務優先順序以及安排開發資源。

***

代價

區分Bounded Context所付出的代價就是當企業流程或資料跨越不同Bounded Context時,需要協調這些活動與資料。例如,當Board名稱在Project Management被修改時,需要通知Board Management更新狀態。或是當Board Management新增一個Board時候,也要通知Project Management產生一個Board。

***

友藏內心獨白:要因地制宜。

8 則留言:

  1. 請教一下:
    >>例如,當Board名稱在Project Management被修改時,需要通知Board Management更新狀態
    會有這個問題,是因為這二個bounded context 同時都被叫用時, 對嗎?
    因為若在 Project Management改了Board name, 它最後會被存到db; 稍後使用者在操作 Board Management功能時, 由於會從db讀取, 自然就會讀到新的Board name了

    回覆刪除
    回覆
    1. Hi Allen,

      Project Management的Board和Board Management的Board,是兩個不同的物件,儲存在資料庫也是不同的表格(它們甚至可以不存在同一個資料庫,因為它們屬於不同的Aggregate)。

      所以,若在 Project Management改了Board name,Board Management並不知道,除非它去聽Project Management的domain event然後Board Management再更新自己的board name。反之,也可以將change board name這個使用案例設計在Board Management,先更新Board Management的board name,之後再通知Project Management讓它去更新自己的board name。

      刪除
    2. 作者已經移除這則留言。

      刪除
    3. 我想問的是allen所提到問題中Project Management與Board Management2個aggreagte root之間交叉的區域,藉由訂閱事件來達到一致性,您的回覆中有提到可以不存在於同一個資料庫,假設2者是可以獨立存在、資料互相分割的服務、我是不是可以理解成Project Management的資料庫其實也有一個Board的view,但欄位可能僅是Project Management所需要的,並非完整,且也不涉及Board的生命週期與狀態變化,有點類似query的概念,大部份時間是查詢的,即便是command也是因訂閱事件所觸發,核心還是存在於Board Management,所以是當其他aggreagte root需要Board的資訊維持最新的情況,藉由訂閱事件及非同步執行來整合跨context的資訊,實務上是這樣嗎?

      刪除
    4. Hi stone,

      基本上就像你說的這樣,Board Management的Board是一個Aggregate,身上有 board id, team id, board name, board members, workflows等資料。Project Management也有一的Board,不過這個Board只是Team Aggregate的一個Entity,它的資料只有board id, team id, project id與 board name (這個board name是Board Management的board name的副本,會透過監聽 Board Renamed領域事件來更新這個副本的狀態)。

      刪除
    5. 謝謝您的回覆,由於工作環境中並沒有同伴或前輩可以討論請教,只能不斷的去翻閱紅皮書與藍皮書,我想請問board在project中為什麼是entity,而不是valueobject,只需要做value equal 的判定是否相等即可變更值,而不需要entity的唯一性,entity按理會需要entityid這個pk唯一識別碼

      刪除
    6. Hi stone,

      你說的沒錯,board在project應該是value object即可。因為ezKanban一開始Team Management的Board自己是Aggregate,後來修改設計之後被降級的Team的一個屬性。當初偷懶把它從Aggregate Root直接降成Entity,日後重構是可以再改成Value Object。



      刪除
  2. 謝謝您的解釋,因不了解專案的來龍去脈,所以有這個疑問,domain的邊界確實是很難確定,隨著熟悉業務知識與需求變動都會造成domain的變更,有關ddd的文章幫助很多,讓我有不同的參照去印證,謝謝您的分享

    回覆刪除