跳至主要内容

Newbe.Claptrap專案週報1-還沒輪影,先用輪跑

· 閱讀時間約 21 分鐘

Newbe.Claptrap 專案週報 1,第一周代碼寫了一點。但主要還是考慮理論可行性。

週報是啥?

成功的開源作品,離不開社區貢獻者的積極參與。作為一個新啟動的輪子專案,專案聯合創始人【月落】有交代:

"我知道你代碼能力也不怎麼樣,你就把你的想法每周的交代清楚。讓他人看到項目的價值。等待越來越多的人發現專案價值所在的時候,自然就會給予更多的關注,甚至於參與的專案的開發當中。所以你要每周都寫一下週報。週報最好側重於講解專案的概念,以及通過專案如何解決實際問題。當然也可以包含一些關於專案如何設計的內容,但要注意適度,通常大家不會太注意項目怎麼實現。而更關注項目帶來的價值。記住:只有產生了價值,專案才會成功。 ”

於是筆者就只能每周寫一下周報,勉強維持生活這樣子。

輪有輪樣

新輪要有新輪的樣子,"項目開張篇"中介紹了本框架相關的基礎理論和工作原理。鑒於相關的理論內容對於剛剛接觸的讀者較為生疏,因此本節將前文最為關鍵的內容羅列如下以激發讀者的回憶。

Actor 特性一:Actor 的狀態是通過外部調用 Actor 而改變的。

更新Actor狀態

Actor 特性一補 1:Actor 的狀態不與外部進行共用。

共享Actor狀態

Actor 特性一補 2:外部可以讀取 Actor 狀態。

讀取Actor狀態

Actor 特性二:Actor 是「單線程」工作的,每次只能處理一個請求。

併發執行Actor

Actor 特性二補 1:併發讀取狀態可以不是「單線程」。

併發讀取Actor

框架定義的 Actor 類型——Claptrap:通過事件模式,產生事件並通過事件改變自身狀態的 Actor。

Claptrap

框架定義的 Actor 類型——Minion:與 Claptrap 對比,Minion 不產生事件而是讀取對應 Claptrap 的事件來改變自身的狀態。允許存在多個 Minion 對應一個 Claptrap。

Minion

通過 Claptrap 和 Minion 配合完成「轉帳」 業務。

Claptrap & Minion

月落大佬名言警句 1:世界上本也不存在"銀彈"。一套框架解決不了所有問題。 月落大佬名言警句 2:業務複雜度是不會因為系統設計變化而減少的,它只是從一個地方轉移到了另外的地方。

還沒輪影,先用輪跑

現在我們擁有了 Claptrap 和 Minion 的概念。接下來,結合一些業務場景,實驗一下框架能否應對各種各樣的業務需求。

再美的技術手段無法應對現實的需求與變化,那也只能技術花瓶。 ——剛剛學完賽博坦 XII 量子計算機指令集的月落

業務場景

這是一個簡單的電商系統:

  1. 只賣一種綠色的水晶,為了方便描述,將這個商品命名為"原諒水晶"。
  2. 用戶可以使用自己帳號中的餘額購買原諒水晶。餘額是通過外部支付系統充值進來的。充值部分,暫時不是業務場景需要考慮的。
  3. 每個用戶還有一個積分,很巧,這個積分的圖示也是綠色的,因此,將這個積分命名為"原諒積分"。
  4. 原諒積分的獲取方式有很多,例如:用戶註冊;邀請其他用戶註冊;被邀請使用者進行了消費,邀請者也可以獲得;原諒即挖礦;現實中獲得了原諒;等等其他的一些方式,這部分可能需要配合後續的活動持續增加獲得方式。
  5. 原諒積分可以在進行購買原諒水晶時,抵扣一部分需要支付的金額。
  6. 原諒積分在未來很可能有其他的用途。
  7. 購買原諒水晶的支付方式未來很可能不止餘額和原諒積分兩種。

以上就是對於這個電商系統的一部分需求描述。需求未來肯定是會變化的。

要素察覺

電商系統,最為主要的業務場景自然是和商品的交易有關的業務場景。不論其他的需求場景多麼的複雜,交易相關的業務場景必然是首當其衝需要分析解決的。

那麼首先,我們將「使用者確認購買原諒水晶」這個場景用簡單的語言描述一下程式需要執行的業務內容:

  1. 需要檢查使用者的餘額是否足夠
  2. 假如用戶選擇了積分抵扣,需要檢查用戶的積分是否足夠
  3. 需要檢查庫存是否足夠
  4. 需要扣減用戶的餘額
  5. 需要扣減庫存
  6. 假如用戶選擇了積分抵扣,需要扣減用戶的積分

如果採用直接操作數據表的方式實現以上六個要點,對於絕大部分開發者來說應該是十分簡單的。開啟一個資料庫事務,至少具備行級鎖,將數據進行檢查和更新,便可以完成這個業務。那麼現在使用本框架進行實現,根據「業務複雜度不減少」的基本事實,也同樣需要實現以上六個要點。

未卜先知

首先,在不太討論依據的前提下,筆者圍繞上文提到的一些主體概念,設計了以下這些 Claptrap:

概念英文命名縮寫
原諒水晶SKUS
原諒積分UserPointP
用戶餘額UserBalanceB

依軲轆畫輪

按照前篇的「轉帳」業務場景的流程設計,此處採用相同的方式設計一下購買的邏輯。如下圖所示:

鏈形設計

分析一下這個設計機制:

依照業務邏輯的順序,完成了庫存檢查、庫存扣減、餘額檢查、餘額扣減、積分檢查、積分扣減的業務步驟。

注意 Client 和 Claptrap S 之間的調用線的存在時間,只有在一開始的時候,也就是說,用戶端僅需要稍作等待,便可以得到回應。

Claptrap S 將事件推送給 Minion S 之後便可以繼續回應新的請求。確保了多個用戶進行併發購買商品即確保了商品不會超賣,也確保了回應事件足夠短。

整個業務邏輯的入口是 S、這樣可以確保使用者在鎖定庫存的前提下進行支付,避免了使用者付了錢沒有辦法買到商品的情況。

基於元件上的原因,這種設計方案被命名為 「鏈形設計(Chain-Like Design)」

一樣的材料,不一樣的輪子

也存在另外一種設計方案。如下圖所示:

樹形設計

分析一下這個設計機制:

引入了一個新的 Claptrap W(What a amazing that I get a forgiven-crystal)作為業務的入口,這個 Claptrap W 通過調用其他的 Claptrap 實現這個業務過程。

相比與上節的設計方案,Minion S、P、B 都不再參與業務的流轉控制,因為這些業務的流轉控制已經由 Claptrap W 進行控制。

並且由於 Minion W 的存在,這個設計方案也可以將部分的調用交由 Minion 來進行,所以這個方案也可以是以下兩種形式。

樹形設計

樹形設計

基於元件上的原因,這種設計方案被命名為 「樹形設計(Tree-Like Design)」

那麼此處就出現了選擇,既然有出現了選擇,那麼此處就使用《月老闆的軟體開發小妙招三十二則》中記載的"WhyNot 對比分析法"來決定使用哪種設計方案:

選項為什麼不?為什麼!不!
鏈形設計業務流轉過程的控制通過 Minion 相連接,這是一種緊耦合的設計。這相當於 Minion 和 Claptrap 這次操作業務的上下文。一個明顯的問題:客戶是否選擇了積分支付,這個邏輯,要麼在 Minion B 中判斷,要麼在 Claptrap P 中判斷,但不論哪種方式其實都不合理。
這樣的設計在應對流程失敗的時候,會特別難以處理。例如在最後一步客戶如果積分不足,那麼可能就需要逐步回滾,這可能會非常困難。
樹形設計這種設計,把業務的核心流程控制內容集中的一對相關的 Claptrap W 和 Minion W 中。這是一種高內聚的表現。
基於這種設計方案,很容易基於 Claptrap S、P、B 構建出更加複雜的過程。

其實讀者很容易發現,對於這個選擇的 WhyNot 對比分析表,其實是一邊倒的。這裏明顯就是要選擇樹形設計。

《月老闆軟體開發小妙招三十二則》,是月落大佬在日常開發過程當中對軟體開發過程用到的一些小方法的收集和歸納。這些方法大多不是新發明的內容。月落大佬只是將這些方法收集在一起,為了啟示後來者,在分析判斷一些問題的時候,用一些小方法有時就能讓事情變得有條理一些。除了"WhyNot 對比分析法"之外,還有較為知名的"5W1H 需求描述法";非常簡單的"CheckList 備忘錄";被廣泛提及的"艾森豪威爾法則"等。

WhyNot 對比分析法,簡單來說就是要講選擇多個主體進行並排對比,分別列舉"應該選擇它"和"不應該選擇它"的理由,然後進行綜合判斷進而做出決定的方法。它特別適用於多人對某一選擇爭執不休時採用的方法,通過表格的形式分別記錄陳述的理由,確保了不缺不漏有理有據。在方法上的基礎,還衍生出了"理由權重計量"、"人員話語權計量"等其他的一些變種。此方法與「優劣對比法」、「異同對比法」等對比法,以及「概率選擇法」、「經驗選擇法」等選擇法有一定的聯繫與區別。此方法的命名據說是月落大佬首創,是一個語法梗。在中文當中,可以採用"為什麼不? "這樣的反問句來表示選擇一個物件的理由,可以用"為什麼!不! "這個的祈使句來表示不選擇一個物件的理由。 WhyNot 其實就是對"為什麼不"四個字的直譯。

好輪子外觀也好看

初見 WhyNot 對比分析法的讀者可能會有疑問:難道就沒有選擇鏈形設計的理由?

需要解釋的是, WhyNot 對比分析法是對固定場景的分析法,因此如果場景變了,分析的結果也會變。也就是說,愛場景下,鏈形設計有其必要性

那麼在解釋之前,我們採用另外的方法來解讀鏈形設計與樹形設計:

  • 將 Claptrap 和對應的 Minion 合併
  • 用"因為... 所以..."的句式來代替圖形中的實線調用

鏈形設計

那麼結合上圖的鏈形設計就可以表述為:

  • 因為 S,所以 B
  • 因為 B,所以 P

展開的語義可以是:

  • 因為購買而扣除了庫存,所以進一步扣減餘額
  • 因為購買而扣減了餘額,所以要進一步扣減積分

樹形設計

上圖樹形設計就可以表述為:

  • 因為 W,所以 S
  • 因為 W,所以 B
  • 因為 W,所以 P

展開的語義可以是:

  • 因為購買,所以扣減了庫存
  • 因為購買,所以扣減了餘額
  • 因為購買,所以扣減了積分

即使筆者這裏解釋的不太清楚,但是讀者仍然可以觀察"因為購買而扣減了餘額,所以要進一步扣減積分"這句其實不太合理,這兩者在業務上其實不應該有明顯的前因後果。

這其實也是鏈形設計在這個場景下不能適用的原因:如果兩者的調用關係沒有明顯的前因後果,而將兩者設計為前後調用的鏈形關係。那麼很可能得到的是不合理的設計。

那麼反過來說:如果要應用鏈形設計。兩者之間必須存在合理的前因後果。

不過,在需求分析過程中,當前可能必然存在的前因後果,過後可能就已經不太合理。業務場景的多變和需求的不完全穩定,導致了事實上,採用樹形設計能夠應對更多的問題。

讀者可以嘗試對上文業務場景中剩餘的幾點需求進行一下設計。

另外,讀者可以重新思考一下開張篇中所採用的「轉帳」場景的設計,採用樹形設計是否更為妥當。

其實就是新輪子

在開張篇中,我們將 Actor 模式與 CRUD 模式進行了簡單異同點比較。而現在還存在另外一類比較常提到的設計方案,就是"領域驅動設計」。。

領域驅動設計的概念此處不多做介紹,對此內容比較陌生的讀者可以參看微軟 MVP 湯雪華老師的文章《領域驅動設計之領域模型》

那麼,當讀者理解了領域驅動設計之後,再結合本篇前面提到的 Claptrap W、S、P、B。或許 Claptrap S、P、B 就是聚合根?或許 Claptrap W 就是應用服務?筆者認為 Actor 模式其實是對領域驅動設計的一種進一步發揮:

  • 領域驅動設計沒有在設計模型內考慮業務併發,而 Actor 模式作為一套併發編程模型其實就彌補了這部分的缺失。
  • 絕大多數(筆者所知到的)領域驅動框架仍然採用了"從倉儲還原聚合根,操作完畢后保存"的一般過程。而以 Orleans 為例的 Actor 框架會將已經啟動的 Actor 在記憶體中保留一段時間,也就是說,聚合根可以在記憶體中不斷的修改,而不需要重複的從倉儲中還原。

總的來說,讀者可以沿用領域驅動設計的思路建模,然後嘗試將原有的聚合根和應用服務設計為 Actor ,從理論上嘗試一下自己所熟悉的領域,能否採用 Actor 進行實現。或許讀者可以從中發現一些不一樣的體驗。

不過,本框架由於採用了 Actor 模式和事件溯源模式,因此設計方法與領域驅動模型相比有所繼承又不完全相同,還有一些其他需要注意的內容,會在後續整理出相應的文章。

結篇

本篇希望通過一個業務場景的設計,讓讀者瞭解到如何採用本框架的理論概念來實現業務。其中包含有一些作者的臆造詞,因此可能需要花費讀者更多的時間進行理解。

由於作者的工作經驗有限,缺乏豐富的行業領域知識,因此對於框架的設計理念是否符合特定行業特性的問題無法給出準確的判斷,還需要讀者多加思考。若有任何需要協助的問題,歡迎聯繫本專案組。

歡迎對此感興趣的朋友關注項目,參與專案。