微服務架構 - 從狀態圖來驅動 API 的設計
問題與答案 (FAQ)
Q&A 類別 A: 概念理解類
Q1: 什麼是以狀態機驅動的 API 設計?
- A簡: 以有限狀態機建模服務生命週期,據此推導狀態、動作、事件與授權,讓 API 一致、可預測、可驗證。
- A詳: 狀態機驅動的 API 設計,先用 FSM 抽象出核心實體的狀態與可允許的轉移,將「狀態、動作、事件、授權」四要素統一到一張圖中。之後由圖推導出 API、事件、授權規範與程式架構,使不同面向一致對齊。這種方法能避免 CRUD 導向的失控與不一致,支援用例走訪、快速驗證、與設計變更,對齊 DDD 與微服務的封裝與內聚。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q2, A-Q6, B-Q1
Q2: 什麼是有限狀態機(FSM)?
- A簡: FSM 是具有有限狀態、受限轉移與明確起終點的模型,用以描述系統行為與允許的流程。
- A詳: 有限狀態機以「狀態、轉移、觸發條件」描述系統行為,必須有明確的起點與終點。狀態以節點呈現,動作以邊表達,邊表示在特定狀態下可執行的操作及執行後的目標狀態。FSM 能建立可驗證的路徑,支援一致性檢查、授權標註與事件推導,是驅動 API 設計的理想工具。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q1, A-Q6, B-Q4
Q3: 為什麼 API 設計要從「狀態」出發?
- A簡: 狀態能統一串起動作、事件與授權,使 API 行為可預測,避免不同面向間不一致。
- A詳: 以狀態為中樞,可在單一模型上同時思考「能做什麼、何時可做、做了會變成什麼」,並自然導出事件(狀態變化)、授權(可走的路)與一致的命名語意。狀態圖讓可用路徑一目了然,避免 CRUD 混亂與資安漏洞,提高可設計性、可測試性與可演化性。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q6, B-Q1, B-Q19
Q4: 「好的 API 設計」的核心評估面向有哪些?
- A簡: 結構清楚一致、規格合乎慣例、服務穩定可靠,三層遞進評估。
- A詳: 可用三層評估 API:1) 結構設計是否清晰(狀態、領域知識、一致性);2) 規格是否合乎慣例(風格、資源或 RPC 導向、文件);3) 服務是否穩定可靠(效能、可靠性、缺陷與安全性)。其中第一層決定後續成本與品質,建議以 FSM 作為設計主軸,先確保一致性與可驗證性。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q1, A-Q5, B-Q18
Q5: API 設計中的「一致性」為何重要?
- A簡: 一致性讓使用者可預測,減少查文件與誤用,避免安全與邏輯漏洞。
- A詳: 不一致會導致不同 API 行為不一、規則互相矛盾、文件難維護與誤用風險。一致性靠單一模型(FSM)統一命名、規則、狀態、動作、事件與授權,讓可用情境與限制可在設計期檢查,實作期自動化驗證,減輕文件與支援負擔,提高長期可維護性。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q1, B-Q19, D-Q5
Q6: 狀態、動作、事件、授權四要素的定義是什麼?
- A簡: 狀態描述當前位階;動作為允許的操作;事件是狀態變更通知;授權限定誰能走哪條轉移。
- A詳: 狀態是實體當前所處的生命週期節點;動作是允許在某狀態執行的操作,對應狀態轉移;事件為狀態轉移後對外發布的訊息;授權限制哪些角色能在某狀態執行哪些動作。四者以 FSM 為中心對齊,支援 API 設計、事件驅動與安全機制的一致建模。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q2, B-Q1, B-Q12
Q7: 貧血模型與充血模型有何差異?
- A簡: 貧血只暴露資料與 CRUD;充血封裝行為,以動作語意驅動狀態與事件,避免邏輯外泄。
- A詳: 貧血模型偏資料導向,操作以 CRUD 表述,流程控制分散在呼叫端,易造成不一致與事件重複/遺漏。充血模型封裝領域行為,提供語意清楚的動作(如 Register、Activate),在類內控制狀態轉移與事件發布,能集中管控授權與一致性,更適合微服務的封裝與自治。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q9, B-Q3, D-Q2
Q8: 為什麼單純 CRUD 導向常導致失控?
- A簡: CRUD 無法精準表達流程與限制,邏輯分散、難控授權與事件,易生資安與一致性問題。
- A詳: CRUD 操作與業務語意距離大,呼叫端需額外判斷流程,造成「何時可做」「做了會產生何事件」不清楚。當需求複雜時,邏輯分散、事件重複或遺漏、授權判斷不一致,最終帶來不可預測行為與漏洞。以動作語意與 FSM 能化繁為簡,明確限制與流程。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q7, A-Q9, D-Q1
Q9: Action-driven API 與 REST CRUD 導向的差異?
- A簡: Action-driven 以業務動作為介面,內控狀態與事件;CRUD 以資料為中心,流程外移。
- A詳: Action-driven 直接提供 Register、Activate、Lock 等動作,描述允許狀態與轉移,成功即發布對應事件,授權內聚。REST CRUD 將流程拆分為多次資源操作,需由外部控制順序與檢查,容易誤用。兩者可並存,但核心流程建議以 Action-driven 為主以確保一致性。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q7, B-Q3, C-Q9
Q10: 在建模時,如何區分「狀態」與「屬性」?
- A簡: 狀態影響允許動作與轉移;屬性僅描述特徵,不應讓狀態數爆炸或路徑失控。
- A詳: 若某資訊會影響「能做什麼」或「做了會變成什麼」,它就應是狀態;若僅是標記或查詢條件,應視為屬性。例如會員「啟停」應為狀態,但等級多半是屬性,避免狀態乘法爆炸。依核心業務決定層級,控制複雜度與可維護性。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q11, B-Q2, D-Q4
Q11: 何時該把資訊建模為「狀態」而非屬性?
- A簡: 當它主導生命週期、決定允許動作或需嚴格事件對應時,應升級為狀態。
- A詳: 判斷準則包含:1) 是否主導流程;2) 是否需控制能否執行某動作;3) 是否需精準對應事件;4) 是否需在授權上做限制。若是,建議升級為狀態。例如 Registered/Activated/Deactivated/Archived 等生命週期狀態優先於等級標記,避免模型失衡。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q10, B-Q2, B-Q11
Q12: FSM 的起點與終點代表什麼?
- A簡: 起點代表實體誕生前的抽象狀態;終點代表生命週期結束,不再允許轉移。
- A詳: 起點(START)常對應初次建立(Register/Import)後的第一個正式狀態;終點(END)代表實體被徹底歸檔或清除後不再接受操作。明確標出起終點可避免循環錯誤與未定義路徑,也便於用例驗證與完成度評估,提升設計可控性。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q1, B-Q19, C-Q1
Q13: 事件(Event)與掛鉤(Hook)有何不同?
- A簡: 事件由狀態轉移驅動;Hook 由特定動作完成驅動,不一定伴隨狀態變更。
- A詳: 狀態事件描述「狀態已改變」的事實,具有可重放與一致語意;Hook 用於「某動作完成」後的外掛行為,如註冊完成寄信、密碼驗證結果通知等。兩者可並存:核心以狀態事件為主,補充以 Hook;避免用事件承載過多非狀態語意,保持模型清晰。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q10, B-Q11, C-Q5
Q14: 為何授權要在設計期(FSM)就考慮?
- A簡: 先標註「誰能走哪條路」,可防越權與不一致,讓安全成為設計屬性而非事後補救。
- A詳: 在 FSM 的動作上標註角色(RBAC)與必要條件,能將安全規則內聚在流程中,避免同一路徑有時開放有時封閉。設計期標註讓之後能直接映射到框架(Authorize)、API Gateway 或 OAuth2 Scope,並可於 Middleware 統一檢查,提高安全與一致性。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q12, B-Q13, B-Q14
Q15: RBAC 與 OAuth2 Scope 的關係是什麼?
- A簡: RBAC 管人之角色;Scope 管應用存取的資源能力;兩者可映射 FSM 動作與路徑。
- A詳: RBAC 常用於判斷使用者角色是否可執行動作;OAuth2 Scope 多用於第三方應用授權資源範圍。兩者本質皆為「能否存取某能力」。在 FSM 中,可將動作需求映射為角色或 Scope,於 Request 讀取 Token 驗證,實現一致的授權控制。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q12, B-Q14, C-Q6
Q&A 類別 B: 技術原理類
Q1: 用 FSM 導出 API 的整體流程為何?
- A簡: 先列狀態與動作,再繪轉移;映射事件與授權;最後生成程式骨架與檢查機制。
- A詳: 流程包含:1) 盤點核心實體的必要狀態(起終點明確);2) 推導允許的動作與轉移;3) 標註狀態事件與必要 Hook;4) 在動作上標明可執行角色;5) 對應至程式:狀態枚舉、StateMachine 查詢、Service 方法、事件發布、授權檢查;6) 用例走訪驗證與調整。這使設計與實作緊密對應。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q1, A-Q6, C-Q1
Q2: 如何從需求導出狀態與動作?
- A簡: 先找生命週期主軸的關鍵節點,再找可引發節點切換的行為與限制。
- A詳: 以用例與規則為線索,萃取「必須控制能做什麼」的資訊為狀態,如 Created/Activated/Deactivated/Archived;將「推動狀態切換」的操作定義為動作,如 Register/Activate/Lock/Unlock/Remove。避免把僅影響屬性的資訊升級為狀態,控制狀態數量與路徑清晰度。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q10, A-Q11, C-Q1
Q3: 狀態轉移如何映射為程式碼?
- A簡: 以 StateMachine 查詢(TryExecute)決定轉移合法性,成功後在臨界區更新狀態並發布事件。
- A詳: 建立 StateMachine(查表或轉移清單)存放 (State, Action)→NextState;Service 方法先呼叫 TryExecute 檢查,再進入 lock(或分散式鎖)保證原子性,執行 Domain 邏輯、更新狀態,最後發布對應事件/Hook。此模板可由 FSM 自動生成或手動套用。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q4, B-Q5, C-Q4
Q4: 查表法的狀態機如何運作?
- A簡: 建立狀態×動作矩陣,格子填入目標狀態;無路徑則為空,查表快速判定合法性。
- A詳: 以二維表格存儲所有 (currentState, action) 組合對應的 nextState。優點是檢索 O(1)、邏輯直觀;缺點是初始化需填 N×M 項,變更維護較重。適用於狀態與動作相對穩定,需高效查詢的情境。可配合程式生成與單元測試確保正確。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q5, C-Q3, D-Q6
Q5: 轉移清單法如何實作?
- A簡: 條列 (狀態, 動作)→(目標狀態) 的清單,執行時按鍵查找或索引,命中即合法。
- A詳: 用字典或映射存放每筆轉移規則,形式近似 FSM 圖上的每個箭頭。優點是貼近思維、初始化簡潔;缺點是查找需鍵組合與維護索引。適用於設計變動較頻繁、狀態圖非滿矩陣時。與查表法可混用,採取最適合的初始化與查詢策略。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q4, C-Q3, B-Q17
Q6: 何時選擇查表法或轉移清單法?
- A簡: 狀態/動作穩定且密集選查表;轉移稀疏或常變更選清單;以工具降低初始化負擔。
- A詳: 若 N×M 接近滿矩陣、需要極速查詢與容易視覺化,選查表;若轉移稀疏、設計常調整、偏向以箭頭思考,選清單。兩法核心皆是同一介面(TryExecute),可替換、不影響 Service 邏輯。可透過生成器或測試輔助維護。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q4, B-Q5, B-Q17
Q7: 如何在 Service 層保證狀態轉移原子性?
- A簡: 以臨界區(lock)或分散式鎖保護「查詢→執行→更新→事件」的原子序列。
- A詳: 為避免兩個動作同時由 A 轉至 B/C 造成錯亂,需以同步機制保護:單機用 lock;多節點用分散式鎖(如 Redis、DB 鎖、Lease)。把 TryExecute 結果與狀態比對納入臨界區,保障一次僅一條合法路徑成立,更新狀態後再發布事件。
- 難度: 高級
- 學習階段: 進階
- 關聯概念: C-Q4, D-Q3, B-Q17
Q8: AOP 如何融入 FSM 檢查?
- A簡: 以 Middleware/Filter 於方法前後切入,統一檢查狀態、授權,事後發布事件。
- A詳: 在 Web 框架導入 AOP:前置切面讀取當前狀態與動作宣告,執行 TryExecute 與授權驗證;方法內只專注 Domain;後置切面依結果發布狀態事件或 Hook。這將橫切關注點集中管理,提高一致性與可測試性,減少重複樣板碼。
- 難度: 高級
- 學習階段: 進階
- 關聯概念: B-Q9, C-Q6, D-Q10
Q9: 在 ASP.NET Core 中如何用 Middleware 實作 FSM 檢查?
- A簡: 以 Filter/Middleware 擷取動作與狀態,呼叫 StateMachine,失敗即短路回應。
- A詳: 透過 Action Filter 或 Middleware 讀取路由對應之動作、當前狀態與使用者身分,呼叫 TryExecute 驗證合法與授權;不合法時返回 4xx,合法則進入應用邏輯;完成後根據轉移結果發布事件。此模式讓控制器輕量、規則集中,提高可維護性。
- 難度: 高級
- 學習階段: 進階
- 關聯概念: B-Q8, C-Q6, C-Q9
Q10: 事件發布機制如何設計?
- A簡: 以狀態轉移為觸發點,封裝事件發布於服務層,支援內部委派與外部 Message/Webhook。
- A詳: 在服務方法完成狀態更新後,觸發對應事件(如 OnMemberActivated),用委派/EventHandler 或整合 Message Bus/Webhook。事件應語意穩定、對應狀態事實,避免承載流程控制。Hook 則在動作完成時觸發,用於外圍通知,不與狀態耦合。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q13, C-Q5, D-Q1
Q11: 如何在 FSM 中推導與標註事件?
- A簡: 對每條轉移標註對應事件;若需動作型通知,額外標註 Hook 並在完成後觸發。
- A詳: 事件以「狀態→狀態」轉移為主,如 →Activated 觸發 Activated 事件;動作型通知(如 Register 完成寄信)用 Hook 表示。圖上以符號標註事件/Hook,實作時服務層統一觸發,確保一次且僅一次。這可避免重複與遺漏。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q13, B-Q10, C-Q5
Q12: 如何在 FSM 上標示角色並映射授權?
- A簡: 在動作邊標出可執行角色(RBAC),實作時映射框架 Authorize 或 Token Scope。
- A詳: 將 USER/STAFF 等角色標到每個動作邊上,意味著「誰能走這條路」。實作可用 ASP.NET Core 的 Authorize(Role) 或在 API Gateway 管理 Products/Subscriptions;第三方整合則以 OAuth2 Scopes 對應。設計期標註確保安全與一致性,呼叫期自動驗證。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q14, B-Q13, B-Q15
Q13: RBAC 在 ASP.NET Core 中如何落地?
- A簡: 以 AuthorizeAttribute 標註方法角色,管線於請求時驗證使用者角色是否符合。
- A詳: 在控制器或方法套用 [Authorize(Roles=”USER,STAFF”)],搭配身份驗證(如 JWT)於 Token 內嵌角色宣告。請求抵達時由中介軟體驗證角色是否包含所需,未通過則回 403。此法與 FSM 角色標註可一一對應,落實一致安全策略。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: C-Q6, B-Q12, A-Q15
Q14: OAuth2 Scope 如何對應 FSM 動作?
- A簡: 將動作需求映射為 Scope,驗證 Token 是否包含對應 Scope,控制第三方能力。
- A詳: 為每個動作或資源定義所需 Scope(如 member.activate),授權流程中讓第三方獲取必要 Scope,Token 持有即代表被授權。請求處理時驗證 Token 與 FSM 的動作需求是否相符。此方法與角色可並行,支援人與應用的雙重授權模型。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q15, C-Q6, B-Q12
Q15: 如何用 API Management 實作角色隔離?
- A簡: 以 Products 與 Subscriptions 管理 API 能見度,按角色發佈對應動作集合。
- A詳: 在 API Management 中,為 USER/STAFF 建立不同 Products,將對應的 API 動作發布於各自 Product;再將外部應用的 Subscription 綁定指定 Product。此法在基礎設施層面實作能力隔離,與 FSM 標註一一對應,亦利流量控管與稽核。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q12, C-Q9, A-Q14
Q16: 鎖與分散式鎖的技術選擇與機制?
- A簡: 單機以語言 lock;多節點用外部協調(Redis/DB/Lease),確保狀態轉移原子性。
- A詳: 同一實體同時兩個動作會致競態,需鎖保護。單機環境用互斥鎖(如 C# lock);多節點用 Redis 分散式鎖、DB 悲觀/樂觀鎖、或雲端 Lease。把 TryExecute、狀態核對、更新與事件觸發包於臨界區,確保一次僅一條合法轉移成功。
- 難度: 高級
- 學習階段: 進階
- 關聯概念: B-Q7, D-Q3, C-Q4
Q17: 如何設計 StateMachine 介面以封裝查詢?
- A簡: 定義 TryExecute(current, action)→(ok, init, final),統一合法性查詢與目標狀態。
- A詳: 封裝為通用介面:輸入當前狀態與動作名稱,回傳是否可執行、初始與目標狀態。Service 僅依此介面決策,與實際實現(查表/清單)解耦,有利替換與測試。此分層讓設計(FSM)與程式(行為)緊密對應又保持彈性。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q4, B-Q5, C-Q3
Q18: 如何確保設計與實作一致?
- A簡: 以 FSM 為唯一真實來源,建立模板化 Service 實作並用例走訪與測試驗證。
- A詳: 將 FSM 作為 Source of Truth:由圖生成狀態列舉、轉移表、授權標註與事件清單;Service 方法遵循固定模板;建立用例走訪測試與狀態覆蓋率;每次設計變更先調整 FSM,再重生骨架與回歸測試,確保規格與行為一致。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q1, B-Q19, C-Q10
Q19: 如何用狀態圖進行用例走訪與驗證?
- A簡: 以用戶故事步步沿圖走,驗證每步可走與結果正確,發現死路、孤島與缺漏。
- A詳: 將每個情境映射為狀態路徑,逐步檢查是否存在合法轉移、授權是否允許、事件是否匹配。若出現無路可走、重複路徑或矛盾,即回修狀態或動作。此法能早期發現一致性與安全缺陷,避免實作後大改,成本更低。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: D-Q6, C-Q10, A-Q3
Q20: 如何處理不更動狀態的動作與 Hook?
- A簡: 在執行前檢查狀態與授權,執行後觸發 Hook;不進入狀態轉移臨界區。
- A詳: 如密碼驗證、發送信件等不改變狀態的動作,只需在方法入口用 FSM 確認當前狀態允許該動作,再執行 Domain 行為;完成後觸發 Hook(如驗證成功/失敗)。不需更新狀態或加鎖,模型因此保持清晰,性能也更佳。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q13, C-Q8, D-Q1
Q&A 類別 C: 實作應用類(10題)
Q1: 如何建立會員生命週期的 FSM 狀態清單?
- A簡: 盤點 Created/Activated/Deactivated/Archived 等核心狀態,明確 START/END。
- A詳: 步驟:1) 自用例與規則萃取生命週期節點:Created、Activated、Deactivated、Archived;2) 明確 START/END;3) 列動作:Register、Import、Activate、Lock/Unlock、Remove;4) 畫出允許轉移;5) 加註事件與角色。注意事項:避免把等級等屬性升級為狀態,控制複雜度。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q1, B-Q2, C-Q3
Q2: 如何用 C# enum 定義狀態與起終點?
- A簡: 以列舉型別標示 START/END 與各狀態,並保留 UNDEFINED 預設值。
- A詳: 實作:定義 MemberStateEnum,包含 START、END、CREATED、ACTIVATED、DEACTIVATED、ARCHIVED、UNDEFINED。建議為 START/END 給固定碼,方便日後追蹤。最佳實踐:狀態只允許由 StateMachine 控制更新,避免外部任意修改。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q3, C-Q4, C-Q10
Q3: 如何以轉移清單實作 StateMachineBase?
- A簡: 用字典存 (state, action)→nextState,提供 TryExecute 查詢合法轉移。
- A詳: 步驟:1) 建抽象 StateMachineBase
,含字典 _state_transits;2) 在衍生類(如 MemberStateMachine)於建構式填入轉移清單;3) TryExecute 查詢轉移,回傳可否執行、初始與目標狀態。注意維護鍵一致性(動作名)與測試覆蓋。 - 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q5, B-Q17, C-Q4
Q4: 如何在 Service 方法中整合 TryExecute 與 lock?
- A簡: 先 TryExecute,進入 lock 核對狀態,執行邏輯→更新狀態→發布事件。
- A詳: 模板:var chk=TryExecute(state, “Action”); 若失敗回傳;lock(sync){若 state≠chk.init 失敗;執行 Domain;state=chk.final;} 之後觸發事件。注意:鎖粒度以單一實體為單位;分散式部署改用外部鎖;所有改變狀態的方法遵循同一模板。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q7, B-Q16, C-Q5
Q5: 如何實作並觸發狀態轉移事件與 Hook?
- A簡: 以 C# event 委派綁定 OnXxx 事件,轉移成功後觸發;Hook 在動作完成時觸發。
- A詳: 建議:為主要狀態轉移(Created/Activated/Deactivated/Archived)定義事件;為特定動作(如 RegisterCompleted)定義 Hook。程式碼在成功更新狀態後引發對應事件;Hook 於動作完成後引發。注意只觸發一次且順序在狀態更新之後。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q10, B-Q11, A-Q13
Q6: 如何在 ASP.NET Core 使用 Authorize 綁定角色?
- A簡: 於控制器/方法標註 [Authorize(Roles=”USER,STAFF”)],框架自動驗證。
- A詳: 步驟:1) 啟用身份驗證(如 JWT);2) 在動作方法依 FSM 標註角色;3) 配置授權策略;4) 測試角色不符時回 403。最佳實踐:角色命名與 FSM 保持一致;在 Middleware 加入狀態檢查以雙重保障;第三方應用用 Scope 搭配。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q13, B-Q12, B-Q14
Q7: 如何用 Thread.CurrentPrincipal 模擬授權?
- A簡: 設定 GenericPrincipal 與角色,方法內用 IsInRole 判斷是否可執行。
- A詳: 於入口設定 Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(“user”), new[]{“USER”}); 在服務方法起始 if(!IsInRole(“USER”)) return false。此法適合主控台或測試環境快速驗證授權邏輯,正式環境仍建議用框架或 Token 驗證。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q13, C-Q4, C-Q10
Q8: 如何新增不改變狀態的動作並檢查狀態?
- A簡: 入口用 TryExecute 驗證允許性,執行 Domain 後直接返回,不進入鎖與更新。
- A詳: 例如 ValidatePassword:先查 StateMachine 確認當前狀態允許;執行驗證邏輯;依結果觸發 Hook(成功/失敗)。注意:這類動作仍需授權檢查;避免在此修改狀態,以維持模型純淨;高頻動作應注意效能與防爆破策略。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q20, A-Q13, D-Q1
Q9: 如何將 FSM 導出的動作映射為 HTTP API?
- A簡: 以動作為端點語意(/members/{id}:activate),搭配一致命名、授權與事件。
- A詳: 步驟:1) 將動作轉為清晰端點(POST /members/register, POST /members/{id}/activate);2) 入參/出參對齊 Domain;3) 套用 Authorize 與狀態檢查;4) 成功後發布事件;5) 在文件清楚註明允許狀態與行為。避免將核心流程拆解成裸 CRUD。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q9, B-Q9, B-Q12
Q10: 如何用示例程式驗證 FSM 設計?
- A簡: 以不同角色與動作序列跑圖測試,核對允許性與最終狀態,確保與預期一致。
- A詳: 建立主控台測試:設定角色與初始狀態,依序呼叫 Register→Activate→Lock→Remove,檢查最後一步因狀態不允許而失敗;再覆蓋其他路徑。加入單測覆蓋所有轉移與授權失敗情境。確認事件觸發一次且順序正確,建立迴歸保障。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q18, B-Q19, C-Q4
Q&A 類別 D: 問題解決類(10題)
Q1: 遇到事件重複觸發或遺漏怎麼辦?
- A簡: 把事件綁在狀態轉移後的單一出口,確保一次觸發;動作型通知改為 Hook。
- A詳: 症狀:相同事件多次送出或未送出。原因:分散於多處判斷、以 CRUD 觸發或流程分岔。解法:事件只在「更新狀態成功後」統一觸發;動作完成通知改為 Hook;以用例走訪檢查所有轉移路徑。預防:以 FSM 驅動事件清單與自動化測試。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q10, B-Q11, C-Q5
Q2: CRUD 導向導致安全與一致性問題如何補救?
- A簡: 轉為動作語意端點,加入 FSM 檢查與 RBAC/Scope,統一流程與授權。
- A詳: 症狀:不同 API 規則不一、越權操作、流程漏洞。原因:流程控制在客戶端、無一致約束。解法:重構為 Action-driven;在控制器前導入 FSM 檢查;以 Authorize 或 Scope 控制;調整文件反映允許狀態。預防:設計期導入 FSM 與授權標註。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q8, A-Q9, B-Q12
Q3: 併發下狀態轉移錯亂的原因與解法?
- A簡: 競態導致同時轉移;以鎖(單機)或分散式鎖(多節點)保證原子性。
- A詳: 症狀:同一實體被兩個動作同時改變至不同狀態。原因:缺乏臨界區保護,TryExecute 與更新分離。解法:將 TryExecute、狀態核對、更新置於同一臨界區;多節點用分散式鎖或樂觀鎖;建立衝突重試策略。預防:所有改變狀態方法遵循同一模板。
- 難度: 高級
- 學習階段: 進階
- 關聯概念: B-Q7, B-Q16, C-Q4
Q4: 狀態與屬性混淆導致 FSM 爆炸怎麼修正?
- A簡: 降級非核心資訊為屬性,保留影響流程者為狀態,縮減路徑複雜度。
- A詳: 症狀:狀態數乘法爆炸、圖難維護。原因:將等級、旗標等屬性升級為狀態。解法:回顧核心流程,將僅影響顯示/查詢的降回屬性;保留生命週期狀態;重新繪製轉移並以用例驗證。預防:以「能否做事、做了變什麼」作為狀態判準。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q10, A-Q11, B-Q2
Q5: API 不一致導致文件與使用困難,如何調整?
- A簡: 建立 FSM 為單一真實來源,重構端點語意、授權與事件,使文件可機器生成。
- A詳: 症狀:同義不同名、規則彼此矛盾。原因:缺乏統一模型與模板。解法:以 FSM 重建狀態、動作、事件、授權;重命名端點對齊語意;用模板生成規格與文件。預防:設計審查以 FSM 為核心,建立變更流程與用例覆蓋。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q5, B-Q18, C-Q9
Q6: 狀態機圖出現「死路」或「孤島」如何診斷?
- A簡: 以用例路徑走訪檢查;若某狀態無入/出度,補充動作或移除冗餘。
- A詳: 症狀:某狀態無法抵達或到達後無法離開。原因:轉移缺漏或狀態不必要。解法:清點入度/出度,補齊對應動作或刪除無意義狀態;再跑用例驗證。預防:每次新增狀態必檢查路徑完整性與授權覆蓋。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q19, C-Q1, C-Q10
Q7: 事件定義與狀態變更不一致如何修正?
- A簡: 將事件語意對齊「狀態已改變」,動作完成型通知改用 Hook 分離。
- A詳: 症狀:事件名稱含混、未能反映實際狀態。原因:事件承載流程控制或動作語意。解法:狀態事件只描述事實(如 Activated);動作完成通知改為 Hook;統一在狀態更新後觸發。預防:在 FSM 圖標註事件與 Hook,建立對照表與測試。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q13, B-Q11, C-Q5
Q8: 授權與角色未對齊造成越權,如何修補?
- A簡: 以 FSM 為準重標動作角色,框架層加 Authorize/Scope,API Gateway 隔離。
- A詳: 症狀:一般用戶可執行管理動作。原因:未在動作層標注角色或框架未檢驗。解法:回 FSM 標注角色,於方法加 Authorize;第三方用 Scope 驗證;必要時以 API Gateway 分產品隔離。預防:授權作為設計屬性,納入審查與測試。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q12, B-Q13, B-Q15
Q9: 註冊與批次匯入事件混淆如何處理?
- A簡: 狀態皆進入 Created,但註冊通知用 Hook(RegisterCompleted),匯入不觸發。
- A詳: 症狀:匯入也發註冊信。原因:以狀態事件代表註冊完成。解法:狀態轉移事件維持通用(Created);針對註冊流程使用動作 Hook(RegisterCompleted)寄信;匯入流程不觸發該 Hook。預防:分離狀態事件與動作 Hook。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q13, B-Q11, C-Q5
Q10: 過度依賴框架(AOP/Middleware)導致失效如何應對?
- A簡: 保留服務層模板與 StateMachine 檢查為後盾,框架僅做增強不做唯一防線。
- A詳: 症狀:框架策略變更或關閉導致檢查缺失。原因:邏輯完全倚賴外層切面。解法:在服務層仍保留 TryExecute 與授權檢查(或至少可開關),框架層作為第一道門;加上單元/整合測試捕捉偏差。預防:設計時分層,關鍵驗證不可單點失效。
- 難度: 高級
- 學習階段: 進階
- 關聯概念: B-Q8, B-Q9, C-Q4
學習路徑索引
- 初學者:建議先學習哪 15 題
- A-Q1: 什麼是以狀態機驅動的 API 設計?
- A-Q2: 什麼是有限狀態機(FSM)?
- A-Q3: 為什麼 API 設計要從「狀態」出發?
- A-Q4: 「好的 API 設計」的核心評估面向有哪些?
- A-Q5: API 設計中的「一致性」為何重要?
- A-Q6: 狀態、動作、事件、授權四要素的定義是什麼?
- A-Q10: 在建模時,如何區分「狀態」與「屬性」?
- A-Q12: FSM 的起點與終點代表什麼?
- B-Q1: 用 FSM 導出 API 的整體流程為何?
- B-Q19: 如何用狀態圖進行用例走訪與驗證?
- C-Q1: 如何建立會員生命週期的 FSM 狀態清單?
- C-Q2: 如何用 C# enum 定義狀態與起終點?
- C-Q10: 如何用示例程式驗證 FSM 設計?
- D-Q5: API 不一致導致文件與使用困難,如何調整?
- D-Q6: 狀態機圖出現「死路」或「孤島」如何診斷?
- 中級者:建議學習哪 20 題
- A-Q7: 貧血模型與充血模型有何差異?
- A-Q8: 為什麼單純 CRUD 導向常導致失控?
- A-Q9: Action-driven API 與 REST CRUD 導向的差異?
- A-Q11: 何時該把資訊建模為「狀態」而非屬性?
- A-Q13: 事件(Event)與掛鉤(Hook)有何不同?
- A-Q14: 為何授權要在設計期(FSM)就考慮?
- A-Q15: RBAC 與 OAuth2 Scope 的關係是什麼?
- B-Q2: 如何從需求導出狀態與動作?
- B-Q3: 狀態轉移如何映射為程式碼?
- B-Q4: 查表法的狀態機如何運作?
- B-Q5: 轉移清單法如何實作?
- B-Q6: 何時選擇查表法或轉移清單法?
- B-Q10: 事件發布機制如何設計?
- B-Q11: 如何在 FSM 中推導與標註事件?
- B-Q12: 如何在 FSM 上標示角色並映射授權?
- B-Q13: RBAC 在 ASP.NET Core 中如何落地?
- B-Q14: OAuth2 Scope 如何對應 FSM 動作?
- C-Q4: 如何在 Service 方法中整合 TryExecute 與 lock?
- C-Q5: 如何實作並觸發狀態轉移事件與 Hook?
- D-Q2: CRUD 導向導致安全與一致性問題如何補救?
- 高級者:建議關注哪 15 題
- B-Q7: 如何在 Service 層保證狀態轉移原子性?
- B-Q8: AOP 如何融入 FSM 檢查?
- B-Q9: 在 ASP.NET Core 中如何用 Middleware 實作 FSM 檢查?
- B-Q15: 如何用 API Management 實作角色隔離?
- B-Q16: 鎖與分散式鎖的技術選擇與機制?
- B-Q17: 如何設計 StateMachine 介面以封裝查詢?
- B-Q18: 如何確保設計與實作一致?
- B-Q20: 如何處理不更動狀態的動作與 Hook?
- C-Q6: 如何在 ASP.NET Core 使用 Authorize 綁定角色?
- C-Q8: 如何新增不改變狀態的動作並檢查狀態?
- C-Q9: 如何將 FSM 導出的動作映射為 HTTP API?
- D-Q1: 遇到事件重複觸發或遺漏怎麼辦?
- D-Q3: 併發下狀態轉移錯亂的原因與解法?
- D-Q8: 授權與角色未對齊造成越權,如何修補?
- D-Q10: 過度依賴框架導致失效如何應對?