1. 程式人生 > 其它 >領域驅動設計(遊戲框架)——從理論到實踐的覆盤

領域驅動設計(遊戲框架)——從理論到實踐的覆盤

什麼是領域驅動設計

  • 領域驅動設計(Domain-driven design)簡稱DDD,網上有很多關於DDD的介紹,也有許多專門的書籍去講述。(推薦一本領域驅動設計(精簡版)(提取碼:52dx))總結一下,領域驅動設計是一套應對複雜軟體系統分析和設計的面向物件建模方法論。閉上眼睛回想一下,什麼是核心域、子域,什麼是限界上下文,聚合根和實體有什麼區別,值物件又是什麼。如果你能快速的回想起這些是什麼個概念,相信你已經對DDD有了個大致的瞭解。

實踐出真知

  • 領域驅動設計是從後端演變而來,既然它是為了應對複雜的軟體系統,那麼像我們在應對大型的複雜遊戲,在前端開發框架的設計上是否可以借鑑呢?我們把最核心的業務,即我們的遊戲業務,用DDD作為指導思想,進一步進行劃分。

版本1.0

  • 戰略設計 從戰略設計開始,我們規定在拿到策劃案的開始,並不是就開始進行業務程式設計。按照DDD的思想,戰略設計是需要領域專家的參與,但是在遊戲業務中,我們認為我們的一線開發人員跟我們的策劃爸爸們,他們就是最好的領域專家。他們熟悉核心的業務流程,是產品的把控者。戰略設計最重要的一點是劃分出限界上下文,比如在遊戲業務中,我們可以通過策劃案的一些概念,劃分出英雄養成系統、活動、戰鬥等。戰略設計相對來說是比較容易的,這一塊在遊戲業務中我們也不會太去強調
  • 戰術設計 主要是從上下文內部,分析內部名詞的組織關係
    • 名詞建模法。名詞建模法是一個很好的方法,把一個限界上下文的東西,有條理的劃分出一個聚合內的東西。具體方法是從策劃案中提取關鍵的名詞,並將名詞規整到一個個的聚合。通過分析合併聚合或者拆分出更詳細的聚合(這裡的劃分可能會隨著迭代不斷地演進)。

      接下來就可以得到我們的聚合根和實體了。一般來說我們是把一個聚合裡面最核心的一個實體(名詞)作為我們的聚合根。比如說一個英雄的聚合,他可能包含有英雄這個實體,然後還有英雄所持有的道具,英雄所能影響的城池等。我們可以很明顯的把英雄這個實體提升為我們的聚合根,其他的就相應的作為實體了。也可以參考
      四色建模法
      ,本質上是一樣的,只是這裡刪減了一些步驟,讓開發更快的切入到業務中。
  • 建模繪製 這裡的建模繪製其實是戰術設計的一部分,也是最重要的一部分。通過模型圖,我們可以從資料(model)出發,更加聚焦於我們的核心業務,而不至於被UI的表現所影響。這裡藉助了Visual Paradigm這個工具來繪製我們的模型圖。模型圖上要把我們的實體、聚合根、聚合根之間的關係體現出來。為什麼選擇Visual Paradigm來幫助我們,原因是這個工具可以一鍵生成C#的類殼,我們在繪製完模型圖後可以直接匯出程式碼。通過與服務端策劃一起,構建模型圖,商討出網路協議(Proto、json等),伴隨著這個過程又不斷調整我們的領域模型,最終有個基礎版本,生成我們的業務程式碼架構。(如下圖)

迭代與進化

浮於上層的架構必然無法成為一個好的架構,也沒有一套絕對的架構可以適用於所有的場景。基於版本1.0我們在公司內部進行推廣,在持續了幾個迭代後,通過DDD討論大會上,我們總結與反思,列出了幾個開發的痛點:

  • 領域層一般包含實體、值物件、領域服務、聚合、聚合根、倉儲、工廠等概念,因為涉及的概念眾多並且持久化需要配合CQRS + ES 模式,而且掌握起來有相當門檻
  • 領域設計流程臃腫,VP使用成本代價高
  • DDD建議的充血模型,在遊戲客戶端業務上比較難以充血,更多的人偏向於絲血模型,甚至是貧血模型。

當然也得到了一些優點的反饋:

  • 戰略設計可以對整個系統有個大的把控,在前期策劃會時可以提出一些程式的建議和疑問,進行交流解答,更加詳細的確認需求
  • DDD帶來的後期維護,細化複雜系統的優勢
  • 從資料出發理解業務需求,不至於被UI介面影響設計。

版本2.0

摒棄不足,基於版本1.0我們進行了優化和改進。

  • 首先我們保留DDD的戰略設計,調整DDD的戰術設計。我們保留了前期對於整個需求分析的頭腦風暴,依舊倡導大家從資料出發去理解需求
  • 聚合內實體採用貧血模型。通過分析我們發現客戶端的業務,或者說養成這個品類的遊戲在客戶端這一塊確實很難進行充血。大量的遊戲資料都上傳到服務端,客戶端只是進行簡單的資料對映,進行表現,甚至於有一些是純表現項的東西。
  • 引入領域層事件匯流排(DomainEventBus),取消領域服務(DomainService)。領域層事件匯流排可以實現修改資料直接派發事件,由應用層直接監聽進行邏輯響應或者檢視對映,避免了臃腫的響應流程。而我們在實踐中發現,在領域服務的業務中也只是對資料的操作,那麼為什麼不能由聚合根暴露操作藉口來進行操作呢?這裡有的小夥伴會說,領域服務也可以用來組織多個聚合,進行多個聚合甚至是多個限界的資料操作。這邊我們也是出於在實踐過程中發現,編碼人員很難區分應用服務和領域服務,並且我們遵循一個原則:那就是對資料的讀取應該是隨意的,對資料的更改應該是謹慎的。既然我們已經規範了只能由聚合根暴露介面來進行資料更改,那麼就可以不要領域服務來進行協調,因為領域服務的業務太單調了,單調到可以由應用層來組織。當然框架依舊保留了領域服務的介面,開發人員可以選擇性的去使用。
  • 規範化資料物件設計。取消vp工具,而採用ProcessOn。取消標準的領域建模圖,而採用ER圖進行分析。強調三正規化,基於三正規化來進行實體和聚合的劃分,減少上手的學習成本。

總結

沒有通用的框架設計,只有實用的框架設計。我們在版本2.0的迭代後,確實對開發上起到了一個更好的正向的作用。當然也在這個過程中提出了一些疑問,比如說我們的領域倉儲是否有必要,是不是可以把我們的聚合變成我們的DataModel,直接靜態進行呼叫(現在是以IoC注入的形式)。依然有很多不足,希望後面能夠隨著迭代慢慢優化。