Reactive Messaging Patterns with Actor Model — 2 About Actor

A typical Domain Driven Design event handling architecture

近期特別有感,炒熱了幾年的數據分析與數據行銷,他們都仰賴著原生的使用者在系統上產生的一系列的運行軌跡,而這樣的數據源都要求著需要取得raw data,而不是被彙整過後的最終結果或當前的資料樣態

從大眾比較可以理解的一般常見的場景來看,我們都知道關於銀行帳戶的存款簿:

Two different viewpoint to recognize the acoount change

當你打開自己的存款簿,會很清楚的看到你的存提的歷程,每一次發生的時間,金額,甚至其他外部參考註記(薪轉戶/他行轉帳等等) !

不過,當自己真的在做軟體系統的時候,我們卻經常的只保留了當前帳戶的資金總額,這兩者有個很明顯的差異

  1. 只關注最終的狀態 — 一般的CRUD 的風格的系統皆是如此,某種程度上受了RDBMS的很大的影響力。
  2. 關注每一次的互動(訊息) — 從而留下了一系列對於真實事件的歷程,作為呈堂證供。

讓我們從另一種角度來思考一件事情 !!!

如果在很多事情發生的當下,我們僅只保留了最後的狀態,而不去關注他的歷程,那麼當你需要對多變的領域事件(Domain Event)做追蹤時你就很難追了。

舉個經典的即時戰略遊戲(RTS) StarCraft 為例 !
Starcraft 2 gameplay video screenshot

打星際2的時候,經常看到非常多的播報員在面對雙方一番激烈的撕殺之後,然後激動地訴說著 在剛剛戰鬥過去的幾秒鐘裡頭,(1)雙方各損失了多少工兵,(2)經濟上金礦與瓦斯的採集到的進用與損耗狀態,藉此評估依照過去(3)幾秒鐘之間的數值差異變化,(4)在後續的幾分鐘以後雙方的優勢差距可以到達幾倍 !

基於這種以”值得關注的事件”為基礎的系統設計,並且由事件發動者自己發出的訊息作為活生生的紀錄被保留下來,這種的模式稱之為 Actor Model。

The actor model promotes actors as the first-class unit of computation and emphasizes communication between actors via message sending.

在Actor model的概念當中,每一個訊息的發動者都是一個Actor,例如

  • commandCenter.produceSCV();
  • scv.buildBarrack(new ExpectedPostion());
  • scv.cancelUnderConstructionBuilding(constructingBuilding);

在上述的部分,commandCenter , scv 都是Actor的實例,而這類的Actor model的設計上都一併考量了非同步的機制,所以非常適合用在以下幾種領域:

  1. 高併發的請求訊息模型
  2. 交易系統
對於Actor 來說,由於他本身僅關注著自己所感興趣的事件 → 他自身能夠發起與接受的訊息事件!!

所以,每一個Actor 應當都盡可能地遵循單一職責的權責劃分去處理,避免做了太多不該是他自己做的事情,就像星際當中的scv就是只能建構建築與微幅的反擊敵人,但他就是無法做出其他更強大的火力支持,他就是工兵而已。

看到這裡,讓我們稍微彙整一下,關於Actor Model:

  1. Actor 只發出或接受他所能(想)理解的訊息事件
  2. Actor 只可以接受(註冊)他能(想)理解的訊息事件
  3. 每一次Actor 發出的訊息事件,都是一個既定事實(Immutable Event),不可逆的(這不廢話,歷史不就都這樣來的!)

再把這樣的概念轉回到 Eric Evans 所倡導的DDD 來看,對於領域物件取用是從Aggregate Root開始,而領域物件之間彼此的溝通訊息則是Domain Event,而每一次的Domain Event 都是immutable (正好與前述特性一致),那麼我們可以:

用Actor Model 封裝 Domain Aggregate Root !!!
用Actor Message 封裝Domain Message !!!

似乎,透過Actor Model 的設計概念來實現DDD顯然變得是一件可以考慮的做法了!

從這樣的訊息模型的概念的基礎上,可以得到幾個關鍵實用的特性

  1. 基於事件發布,所以可以特別對有興趣的事件作註冊予以監看,使得日後針對各種多變且多元的想關注的事件可以從容地取得資訊,每一次的事件都被活生生的記錄下來(Immutable fact/event),而你要做的事情就是去添增新的訊息接收者(Another actor)。
  2. 特別強調單一職責這與DDD 當中特別提到的 Domain in Context不謀而合,不要先射箭再畫靶這樣肯定會走遠跑偏了,明確的勾勒出範圍自然可以有限度合理的找出Actor。
  3. 把真正需要被關注的事件主題找出來即可,不要把Actor 自己內部的一些無關痛癢的狀態變化特別暴露成事件,這樣只會產生信息爆炸,如果真要有個紀律來做這些判別的話,或者可以考慮從Actor 所暴露的public method 範疇來做初步篩選,真的公開出去做交互的訊息才認真思考他是否是個合理的Event。
  4. 面對現在期望的即時數據分析與獲取,在知名的HTAP概念之下,要能快速的獲取多元的數據源,從添增事件註冊的Actor的方式之下,可以較為無痛的不干擾到既有的Actor(透過註冊達成群播的訴求),又能動態收取期望的數據,這是一種可以被參考的方法之一。

對於Actor Model當中的 Actor 該怎麼捕捉? 個人覺得現在處在一種沒辦法完整的定義的理解上,傳統上理應遵循著DDD 的建議風格,從領域概念抓出Entity,然後試圖去發現他是否可能基於行為產生event,從而確認他的存在的可識別性,若他確實是Entity而非 Value Object,並且去思索這領域物件的生成原因與其相依性,則能更細一步去找出他可能對應的生成源頭(Aggregate Root),以及其他的物件(Service , Repository , factory … etc )

但是這樣的設計風格,在面對 ATDD/TDD 的浪潮狂襲之下,這樣的Up-front design 似乎真的很不被認同或理解,至少我在參加過得很多關於敏捷社區開辦的設計課程裡頭,都將 up-front desgin 的理念束之高閣。

我個人認為,實際上DDD與 ATDD/TDD 的設計風格理念,在面對要以Actor Model 進行軟件開發時,並不盡然是個完全的衝突,反而倒是可以各取優勢的組合一併來探討:

讓ATDD/TDD 盡可能地闡述期望( produced Immutable Domain Event),並讓User Story 本身是個有價值(meaningful process to show out intent in domain context)的Story。
利用TDD的過程,讓重構不斷的反饋初期設計出來的 Domain Event去看看他是否能在單一一個領域物件當中完成單一職責的責任,若不能則參考DDD的設計精神去逐漸長出他可以配套關聯出來的 Aggregate root, Value Object , Service , Repository , Factory … etc

PS: 近期小弟大病一場,稿子寫得實在慢啊 ~

Show your support

Clapping shows how much you appreciated Kim Kao’s story.