DDD實踐問題之 - 關於論壇的帖子回復統計信息的更新的思考


之前,在用ENode開發forum案例時,遇到了關於如何實現論壇帖子的回復的統計信息如何更新的問題。后來找到了自己認為比較合理的解決方案,分享給大家。也希望能和大家交流,擦出更多的火花。

論壇核心領域問題分析

論壇領域的核心概念是:帖子、回復。大家都知道,一個帖子可以有零個或多個回復。對同一個帖子,不同的人可以並行發表回復。回復發表后,查看帖子詳情時,可以根據回復的發表時間排序顯示;此外,我們還關心某個帖子的最新發表的回復、最新回復的作者、最新回復時間,以及總回復數。

我們設計的系統,應該在實現上述的領域問題的前提下,盡量做到發表回復時要快,且能保證帖子能對它的所有回復的統計信息能正確的統計出來。

方案1

把帖子設計為聚合根,回復設計為帖子的子實體。然后發表回復就是在帖子聚合根里添加一個回復實體。

優點:

  1. 模型清晰並符合人們對領域的一般認識,帖子和回復1對多,很自然想到這個模型設計;
  2. 帖子內部就已經聚合了所有的回復信息,數據強一致性,所以統計信息自然不用擔心;
  3. 在DB層面,當發表一個回復時,先插入回復,再更新帖子回復統計信息,兩個步驟在一個數據庫事務里,確保了數據強一致性。

缺點:

  1. 當很多人同時對同一個帖子進行回復時,也就是回復的並發很高時,會被阻塞;因為每個回復都要同步更新帖子的回復統計信息;
  2. 帖子由於聚合了所有的回復,所以會導致帖子本身比較重;必須要求ORM框架支持延遲加載,否則獲取帖子會成為一個問題;比如,我僅僅為了修改帖子的內容,而要把整個帖子的回復都加載出來,會付出很多不必要的代價。

這種方案,大部分情況下都沒問題。因為大部分論壇,對一個帖子的回復的並發都不會太高。所以,我覺得設計總體來說是可行的。

方案2

把帖子和回復都設計為聚合根,回復不再是帖子內的子實體。發表回復就是新增了一個回復聚合根。

優點:

  1. 帖子聚合根比較輕量級了,因為它內部不在維護回復了;
  2. 高並發創建回復時,不會再有性能問題。但前提是,創建回復后,更新帖子的統計信息必須采用異步的方案,否則如果也是像方案1那樣采用同步+事務的方式,那DB層面還是成為瓶頸,並發上不去;

缺點:

  1. 模型一般人理解有點困難,大部分人會問的一個問題是,為何回復不是帖子的子實體,回復不是離開帖子后就沒意義了嗎?這個問題,這里先不做討論,我之前的文章中有討論過這個問題。
  2. 本質問題和方案1類似;如果采用同步更新統計信息,那並發也是上不去,只是模型的設計改變了一下;如果采用異步更新統計信息,那就是消息驅動的架構,就需要考慮消息的丟失、冪等、亂序等問題;比如消息丟失,那統計信息最終就不正確;假如消息重復被處理(分布式消息隊列一般不會保證消息絕對不會被重復投遞),那統計信息也不正確;假如消息的處理順序亂序了,那最后的統計信息也會不對;開發人員需要考慮到這些問題。當然,如果你不care,也可以,呵呵。

這種方案,模型層面做了一些變化,DB層面,引入了異步更新統計信息的思想,但要求技術上需要處理EDA架構所帶來的典型問題。如果論壇的並發問題確實影響了用戶的體驗,則可以嘗試考慮次方案;

方案3

模型層面,設計為兩個聚合根還是一個聚合根無所謂。然后統計信息決定不保存,也就是不冗余存儲統計信息了。大家知道,統計信息,一般只是用來展示數據使用,並不會參與到業務邏輯中去。所以,理論上我們不保存統計信息也可以,因為我們總是可以在需要的時候動態查詢統計出所需要的信息,SQL的統計功能是很強大的,呵呵。

優點:

  1. 無需冗余存儲統計信息,設計簡單;
  2. 發表回復時也無並發問題;

缺點:

  1. 需要付出更多的查詢代價,尤其是在論壇數據量大,查詢並發高的時候;而且還要根據統計信息的結果進行分頁的話,數據量一大,性能一定比較糟糕。當然,我們還有辦法,比如分庫分表,減少單表的數據量,確保查詢性能;或者,總是只支持查詢近期2周的數據,歷史數據不顯示,必須通過其他方式查看。這樣的話,也可以控制活躍數據在一定的數量級之內;
  2. 上面提到的分庫分表,方案顯得有點重;只保顯示近期活躍的數據相當於犧牲了一部分業務功能,換來更高的查詢性能;只要業務上能接受,就可行;

這種方案,我覺得需要業務人員和設計人員仔細評估考量。大家覺得如何呢?

方案4

這種方案,一般老外的開源論壇中出現的較多。領域模型的設計有較大不同,因為對論壇核心領域的認識有所不同。當用戶發帖時,我們把帖子的標題和內容看成兩個部分,標題叫thread,內容是屬於這個thread下的第一個message;然后對這個帖子的回復,看成是第二個message。所以,通過這樣的理解,thread還是可以理解為帖子,但其含義和我們通常所理解的帖子稍微有點不同,因為這個帖子僅僅只有標題沒有內容,它的作用就是“穿針引線”,thread的英文意思就是線索、穿成串的意思。可見,一個thread就是對很多message的串聯,我們也可以把thread理解為一個主題,這個主題下有若干個討論內容。然后一個message,即消息,就表達了一個內容。所以,當用戶發帖時,就是會生成一個thread,以及一個message,兩個聚合根;當用戶發表回復時,就是只是創建一個message聚合根。另外,也很明顯thread應該維護所有的回復的統計信息,因為我們設計它的目的也在於此(串聯message,以及維護message統計信息)。然后,message就是簡單的表達某個內容即可,同時message上記錄當前自己所屬的threadId即可。好像說的有點啰嗦,呵呵!

上面這個描述的是我們對論壇核心領域有不同的認識,最后設計出來的領域模型也完全不同。所以,我們發現,當我們在做DDD領域驅動設計時,往往每個人最后設計出來的領域模型是不同的,因為每個人對同一個領域的問題的本質理解不同。甚至可以說,這個是每個人的世界觀的不同導致。雖然這樣,但DDD的最大價值在於會要求我們主動去思考領域,思考如何用模型,從OO的角度去思考問題,思考狀態的一致性維護,狀態變化的規則,等。相比傳統數據庫驅動的方式、面向過程的方式,腦子里只有數據結構、關系、以及過程。沒有對象、交互、職責這方面的思考意識。

我承認,這個領域模型的設計確實不錯,甚至更好!但我們不能說,前面的領域模型的設計(帖子包含標題和內容、回復只有內容)是錯誤的,因為只是對領域問題的不同理解而已。前面的領域模型的設計,也是自然合理的,我認為。

當然,討論回來,在DB層面,不管領域模型如何設計,我們在更新帖子統計信息時,還是會碰到並發的情況。這個其實是多用戶並發回帖導致的技術問題,不是業務上的問題。技術問題就是需要通過技術手段去解決。當然,有時也可以把某些看似是技術問題的問題,提煉出合適的業務規則,通過聚合根封裝業務規則,確保數據一致性的思路來解決。下面我們來看一下方案5。

方案5

基於ENode框架實現,采用CQRS架構。領域模型的設計,也是設計為帖子和回復兩個聚合根。不同的是帖子中聚合了回復的統計信息,設計一個值對象,表示帖子的當前回復的統計信息。包括:最近回復的ID、作者、回復時間,以及總回復數。為什么要這樣設計?因為經過對領域進一步的分析和思考,發現了領域內的一個潛在的業務規則。就是我們關心帖子的“最后”的回復信息。關鍵問題就是在這個“最后”兩個字上。既然是最后,那就必須要知道哪個是最后,根據什么判斷?就是根據回復的創建時間。然后,在高並發創建回復的時候,我們需要有一個地方,可以准確的統計出這個最后的回復是哪個。前面幾個方案,要么通過DB的強一致性事務保證,要么依賴消息隊列的順序消息處理(必須依靠開發人員要處理好各種EDA的問題才行)。這些方案雖然最終卻是能統計出這個最后的回復信息;但我認為,這個是屬於領域內的一個業務規則;這個規則是由於我們所關心的統計信息而必須引入的。所以,更好的方案應該是用聚合根來維護這個業務規則。

所以,帖子本身可以不用聚合所有的回復信息,因為帖子本身確實不關心回復信息本身,它只是關心回復的統計信息(最后一個回復的信息以及總回復數);因此,我們設計帖子聚合時,只需要再讓其內聚一個包含回復統計信息的值對象即可。

當有一個新的回復產生后,發送一個命令,通知回復的帖子更新其統計信息;然后帖子處理這個命令時,內部判斷當前回復的時間是否是最新時間,如果是,則更新最后回復信息和總回復數;如果不是,則只更新回復數。然后帖子的統計信息更新后,產生領域事件,表示帖子的統計信息有更新,然后CQRS的event handler,根據領域事件,更新讀庫帖子表的那幾個統計字段即可;

這種方案,通過ENode提供的技術保證,可以確保消息至少投遞一次,確保消息的冪等處理,以及消息(領域事件)的順序處理,以及最重要的一點,最同一個聚合根,確保盡量做到了無並發操作;就算出現並發,也能框架層面自動重試。所以,開發人員就不用再關心這些技術相關的問題了。

個人認為,這種方案在基於ENode框架引入CQRS架構的異步思路,解決高並發的問題的同時,進一步挖掘了業務需求,分析出了潛在的業務規則,通過用聚合根來維護這個業務規則,最終確保了數據的最終一致性;缺點是,依賴了ENode框架。對我個人來說,肯定是最喜歡這種方式,呵呵。目前enode forum案例,就是采用這種方式來實現帖子的回復統計信息的維護和存儲。

結束語

我覺得很多問題的解決思路都是類似的。我總喜歡使用大家都通俗易懂的案例來分析、討論。因為只有大家對這個業務都比較理解,才具有討論的基礎。另外,我相信每個人都有舉一反三的能力。一旦我們把某個業務問題分析清楚,那也許遇到其他類似的業務問題時,我們曾經的業務問題分析經驗會幫助到我們,從而可以更加快速的理解和解決相似問題領域。這也是我為什么要花精力寫出這么多方案的原因。多思考一點,多深入一點。自己就會多成長一點,對未來的領域問題的分析就會更快速一點。

不知不覺又下半夜了,眼睛酸,不寫了,基本把能想到的都寫了,呵呵。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM