andrew0928 / columns.chicken-house.net

[安德魯的部落格] 網站原始檔案
http://columns.chicken-house.net/
MIT License
8 stars 2 forks source link

微服務架構 - 從狀態圖來驅動 API 的設計 — 安德魯的部落格 #16

Open andrew0928 opened 2 years ago

andrew0928 commented 2 years ago

https://columns.chicken-house.net/2022/03/25/microservices15-api-design/

這次我直接破題了。我想寫一篇從 State Machine 的分析為主軸,來驅動整個服務的 API 設計的文章。 圖片來源: https://www.giga.de/artikel/was-ist-eine-api-schnell-erklaert/“我們服務的 API 設計很糟糕,怎樣才能設計出好的 API? 有沒有一些 SOP 或是準則可以給我參考?”其實這個問題我已經被問到爛了,答案當然是 “沒有” 啊 XDDD, 如果有的話,這個問題根本就不會是個難題,也不會有這 FAQ 了。首先,API 的好壞,”設計” 占了九成以上啊! 設計問題是沒有正確答案的,既然是設計,就是帶有個人風格的,你會發現有些大師的設計就是既精準又靈活,看的到背後的巧思,每個環節都搭配的恰到好處,找不出一絲多餘的設計。指南 (Guideline) 通常是告訴你該做什麼,不該做什麼。它可以讓你設計出及格的 API,但是沒辦法讓你設計出理想的 API。要達到這個境界 (我也還不夠格) 需要依靠的是對 domain 更精準的掌握…。先定義一下,何謂 “好” 的 API? 我很看重 API 設計上的一致性。API 必須顧及很多層面,例如你要端出那些功能? 這些功能是否都用一樣的視角來設計? (主詞會不會一直變動?) 背後處理的邏輯是否一致? (某個 API 不准你取得的資訊,另一個 API 卻又可以?) 你能否在呼叫前就很明確的知道能不能這樣用? (還是任何大小事都要查文件才能搞懂?)其實這些細節都很煩人啊! 這些都是很多不同面向的考量,我們在實作時同個面向的問題往往都能很一致的處理,不同面向的一致性往往就因為背後的實作階段不同,就很容易被忽略。因此我蠻重視在設計階段就先想辦法對齊這些不同面向的問題。我的作法是,通通都在狀態圖上面把這些 API 設計的要素標示出來,在同一張圖上面思考,就很容易在設計階段解決一致性問題。

KScaesar commented 1 year ago

會員註冊後我想發一封 email 給他。這時我該訂閱 OnMemberCreated 事件嗎?

這邊不太理解 event 和 hook 的差異
查了網路, 看到這篇文章
https://www.facebook.com/will.fans/posts/2308560599173071/
裡面提到

event 是該物件本身運作會就會預期發生的事件
hook 是利用這樣的時機去監聽並執行event設計之外別的事情, 是可選非必要的

但這個解釋 依照您文章的脈絡
我會思考為 註冊後 發出驗證郵件不是必須的
因為文章中 定義 發出mail 為 hook

如果要讓驗證郵件是必要流程
是否應該多一個 Verified state?

想問問您對 hook event 的差異
有什麼見解

andrew0928 commented 1 year ago

@KScaesar

會員註冊後我想發一封 email 給他。這時我該訂閱 OnMemberCreated 事件嗎?

這邊不太理解 event 和 hook 的差異
查了網路, 看到這篇文章
https://www.facebook.com/will.fans/posts/2308560599173071/
裡面提到

event 是該物件本身運作會就會預期發生的事件
hook 是利用這樣的時機去監聽並執行event設計之外別的事情, 是可選非必要的

但這個解釋 依照您文章的脈絡
我會思考為 註冊後 發出驗證郵件不是必須的
因為文章中 定義 發出mail 為 hook

如果要讓驗證郵件是必要流程
是否應該多一個 Verified state?

想問問您對 hook event 的差異
有什麼見解

Event 表達的是: 發生了什麼 "事件",而 Hook 則是表達了 "發生的事件應該跟哪個動作掛勾",兩者講的是同一件事的不同面向。

回到你講的,在 domain 的定義上,由於系統的分層,我的確是把 "驗證郵件" 在核心機制內當作非必須,因此交給使用這核心服務的應用程式自己決定,因此有了這樣的設計。既然驗證是由外圍系統進行,那麼驗證的結果自然也由外圍系統來維護,因此沒有納入到狀態的定義之中。

這不是對錯的問題,單純的是我舉這案例時我對需求的定義跟選擇而已;由這樣的定義,展開的狀態機跟對應的 API 定義,我想表達的就是這連串推演過程。

的卻多一個 verified state 是個好設計,您可以試著自己調整看看,體會一下由狀態機的改變,驅動一系列的相關規格調整,你就能體會我講的方法對於複雜模型的維護有多容易了 : )

KScaesar commented 1 year ago

謝謝您的回覆, 了解你寫文章時的定義, 就更加了解為什麼這樣設計

jaychou2211 commented 3 weeks ago

關於步驟五 : [ 間接/不會 改變狀態的 action ]的範例程式

var check = this._state_machine.TryExecute(this.State, "Register");

是不是應該要是 TryExecute(this.State, "ValidateEmail") 呢 ? 因為意圖執行的動作是 ValidateEmail 而系統想確保 user 在 created 狀態下才能執行 ValidateEmail

不知道我的理解有沒有問題 🤔 🤔 🤔

andrew0928 commented 2 weeks ago

@jaychou2211 關於步驟五 : [ 間接/不會 改變狀態的 action ]的範例程式

var check = this._state_machine.TryExecute(this.State, "Register");

是不是應該要是 TryExecute(this.State, "ValidateEmail") 呢 ? 因為意圖執行的動作是 ValidateEmail 而系統想確保 user 在 created 狀態下才能執行 ValidateEmail

不知道我的理解有沒有問題 🤔 🤔 🤔

看了一下,是我手誤打錯,我要表達的意思應該是 TryExecute( this.State, "Activate" ) 才對。

原因是: ValidateEmail 是在核心結構之外的行為,我的定義是在發出 OnMemberRegistered 事件後才觸發外部的服務,來執行 ValidateEmail. 而執行成功後,外部的事件處理器應該要再回頭來呼叫 Activate() 來更新 Email 驗證成功後的下一步動作。

ValidateEmail 雖然是你要執行的 "意圖" 沒錯,不過這動作並沒有被我列在狀態機內。因此這行 code 不應該填 "ValidateEmail",而是填 "Activate" 才對。