當授權資料不可信時,我選擇讓系統安靜地退後一步

延續上一篇的問題 在上一篇文章中,我們遇到了一個問題:Link Table 資料遺失導致使用者沒有角色,所有 API 都回傳 401。 文章最後我拋出了一個問題: 如果你的系統把角色資訊 cache 在 Redis、JWT claim、或 BFF 層,當 Link Table 資料不正確時,系統應該: 名詞解釋: Redis:一種高速的記憶體資料庫(In-Memory Database),常用於快取(Cache)熱門資料,避免每次都查詢主資料庫 JWT claim:JWT Token 內的資料欄位。例如把使用者角色直接寫在 Token 裡:{ "sub": "user_123", "role": "admin" },這樣就不用每次都查 DB BFF(Backend For Frontend):一種架構模式,在前端和後端 API 之間多一層「專為前端服務的後端」,常會在這層做權限快取 立即全站拒絕? 繼續相信 cache? 還是進入 degraded mode? 我的選擇是:進入 Degraded Mode。 這篇文章會解釋為什麼,以及如何實作。 一句話定義 Degraded Mode = 系統已知自己「部分不可信」,主動降級功能以維持安全與可用性。 不是壞了、不是裝沒事,而是: 我知道哪裡壞 我知道哪些功能不能給 我知道要保住什麼 為什麼不選另外兩個方案? ❌ 方案 A:立即全站拒絕 所有 API → 401/503 → 業務全掛 問題: 使用者完全無法使用系統 對業務衝擊太大 「寧可錯殺一百」的策略在商業系統中代價過高 適用場景: 金融交易、醫療處方等「錯了比沒有更糟」的場景 ...

January 20, 2026 · 3 分鐘 · Peter

同樣的功能,為什麼 Flutter 比 Vue 難 Debug 十倍?從實戰到架構的六層反思

前言:同一個功能,截然不同的 Debug 體驗 最近在維護一個同時有 Vue 前端和 Flutter App 的專案。兩邊都要實作「關於我們」頁面的選單過濾邏輯——根據不同情境顯示或隱藏特定項目。 Vue 那邊:兩天內改了十幾個 commit,每次都是小幅調整,順順地完成。 Flutter 這邊:卡了一整天,改了一個地方沒效果,懷疑方向錯誤,來回折騰。 同樣的業務邏輯,為什麼 debug 體驗差這麼多? 這篇文章從 debug 實戰出發,一路延伸到架構層面的反思。我們會探討 Domain Model 的防禦能力、Clean Architecture 的責任邊界、扁平架構的取捨、BFF 的可靠性價值,最後揭露這次 debug 困難的真正原因——交接代碼的信任陷阱。 Part 1:Debug 實戰 Vue:問題在 UI 顯示層 Vue 那邊的典型修正長這樣: // Vue - 在 computed 裡加一個 filter const filteredPageTabs = computed(() => { return response.value.pageTabs .filter(item => item.subtitle !== 'Service') .map(item => { if (item.subtitle === 'ABOUT_US') { return { ...item, subTabs: item.subTabs?.filter( subTab => subTab.title !== '醫療團隊' ) || [] } } return item }) }) 問題本質:資料從 API 回來是正確且完整的,只需要決定「哪些要顯示在畫面上」。 Debug 過程:打開 Vue DevTools → 看 store 資料 → 加個 filter → 完成。整個過程不超過 10 分鐘。 ...

January 4, 2026 · 5 分鐘 · Peter

為什麼技術選型 CMS 我要選 Strapi?2024 年中的預算與系統分析決策

引言:一個技術選型的起點 2024 年 6 月,我坐在會議室裡,面對著老闆和行銷總監,準備報告我對公司新系統 CMS 的技術選型建議。這不是一個輕鬆的決定——選錯了,可能浪費數百萬的開發成本;選對了,能為公司省下可觀的人力支出。 經過數週的研究與分析,作為一個架構規劃師同時也是技術決策者,我最終選擇了 Strapi 作為我們的 Headless CMS 解決方案。這篇文章將分享我的決策過程、考量因素,以及最重要的——這個選擇如何為公司省下大筆預算。 什麼是 Headless CMS? 在深入 Strapi 之前,先理解 Headless CMS 的核心概念。 傳統 CMS vs Headless CMS 傳統 CMS 將前端與後端緊密耦合,網站的外觀和內容管理綁在一起。而 Headless CMS 則專注於內容管理和 API 提供,讓前端團隊可以使用任何技術框架來消費這些 API。 Headless CMS 的核心優勢 特性 傳統 CMS Headless CMS 前後端耦合 緊密耦合 完全分離 前端技術選擇 受限於 CMS 模板 任意框架(Vue、React、Flutter) 多平台支援 僅限網頁 Web、App、IoT 皆可 擴展性 中等 極高 API 優先 否 是 為什麼選擇 Strapi?三大核心原因 原因一:前後端完全分離 Strapi 作為 Headless CMS,天生就是為了前後端分離而設計。這意味著: ...

December 23, 2025 · 3 分鐘 · Peter

Swift Redux 架構完整指南:從 Reducer 到 Middleware 的狀態管理實踐

引言:為什麼需要 Redux? 在 iOS 開發中,隨著應用規模擴大,狀態管理逐漸成為最具挑戰性的課題。當多個 View 需要共享狀態、狀態變化難以追蹤時,應用很容易陷入混亂。 Redux 作為一種可預測的狀態容器,最早在 JavaScript 生態系中流行,如今也廣泛應用於 Swift/iOS 專案。本文將深入介紹 Redux 架構的核心觀念,包含: Reducer(減少器):狀態更新的核心邏輯 Store(儲存區):應用的單一狀態來源 Action(動作):描述「發生什麼事」的指令 Middleware(中介層):處理非同步與副作用 Redux 核心架構概覽 架構組成 架構特性: ✅ 單向資料流:資料流向可預測 ✅ 單一狀態來源:整個應用只有一個 State 樹 ✅ 狀態不可變:不直接修改 State,而是創建新 State ✅ 可測試性高:Reducer 是純函數,易於測試 核心概念 1:State(狀態) State 是什麼? State 是整個應用的單一資料來源(Single Source of Truth)。它通常是一個 struct,描述當前應用的完整狀態。 實作範例 // AppState.swift struct AppState { // 購物車 var cartItems: [CartItem] = [] var totalAmount: Decimal = 0.0 // 用戶資訊 var userProfile: UserProfile? var isLoggedIn: Bool = false // UI 狀態 var isLoading: Bool = false var errorMessage: String? // 套餐選擇 var packages: [Package] = [] var selectedPackageId: String? } // 購物車商品 struct CartItem: Identifiable { let id: String let name: String let price: Decimal var quantity: Int } // 用戶資料 struct UserProfile { let id: String let name: String let email: String } // 套餐 struct Package: Identifiable { let id: String let name: String let items: [PackageItem] } struct PackageItem: Identifiable { let id: String let name: String var quantity: Int } 設計原則: ...

August 21, 2025 · 10 分鐘 · Peter

RxSwift -bindViewModel

第69行cell.configure一直進不去 RxSwift -bindViewModel 第69行cell.configure一直進不去 原因如下: required init?(coder aDecoder: NSCoder) 是 UIKit 控制器初始化的一部分,尤其是從 Storyboards 或 Xibs 中加載時。如果你正在嘗試從 Storyboard 或 Xib 中加載你的 ZooViewController,那麼就需要實現這個初始化方法。在你的程式碼中, required init?(coder aDecoder: NSCoder) { fatalError(“init(coder:) has not been implemented”) }你用 fatalError("init(coder:) has not been implemented") 拋出了一個執行時期的錯誤。 如果你確實不需要從 Storyboard 或 Xib 加載,你可以避免使用 ZooViewController 作為 Storyboard 或 Xib 的類別,或者避免透過 Storyboard 或 Xib 實例化 ZooViewController。在這種情況下,你的 init(coder:) 方法將永遠不會被呼叫,因此你的 fatalError 也不會觸發。 如果你需要從 Storyboard 或 Xib 加載 ZooViewController,那麼你需要提供 init(coder:) 方法的實現。這可能意味著你需要提供一個預設的 ZooViewModel,或者提供一種方式來設定 ZooViewModel,如下所示: required init?(coder aDecoder: NSCoder) { self.viewModel = ZooViewModel() super.init(coder: aDecoder) }最後從Main.storyboard的viewController冠上我要的ZooViewController, 問題就解決了.

July 21, 2023 · 1 分鐘 · Peter

Flutter 狀態監聽完全指南:Provider、Bloc、Riverpod、GetX 深度比較

前言:Flutter 狀態管理的核心挑戰 在 Flutter 開發中,狀態管理是最重要也最複雜的主題之一。當應用程式規模增長,Widget 樹層級加深,如何讓不同層級的 Widget 能夠正確地監聽和響應狀態變化,成為每個 Flutter 開發者必須面對的挑戰。 選擇錯誤的狀態監聽方式,會導致: ❌ 不必要的重繪:整個 Widget 樹被重建,效能下降 ❌ 記憶體洩漏:忘記釋放監聽器,導致記憶體持續增長 ❌ 狀態不同步:多個 Widget 顯示不一致的資料 ❌ 程式碼難以維護:狀態邏輯散落各處,難以追蹤 本文涵蓋內容 本文將深入探討 Flutter 四大主流狀態管理方案的監聽機制: Provider:Flutter 官方推薦的輕量級狀態管理 Bloc:基於 Stream 的企業級狀態管理 Riverpod:Provider 的改進版本,解決了 Provider 的核心問題 GetX:高效能的響應式狀態管理 我們將從實際問題出發,比較這四種方案的: 🔍 核心原理:底層如何實現狀態監聽 💻 實戰範例:可執行的完整程式碼 ⚡ 效能表現:重繪範圍、記憶體使用 🎯 適用場景:什麼情況下使用哪種方案 ⚠️ 常見陷阱:實戰中容易踩的坑 Provider 狀態監聽機制 Provider 是 Flutter 官方推薦的狀態管理方案,基於 InheritedWidget 實現。它的核心優勢是簡單易用,適合中小型應用。 Provider 核心概念 Provider 使用 依賴注入 (Dependency Injection) 和 作用域 (Scope) 的概念來管理狀態: 關鍵概念: Provider 有作用域限制:只有在 Provider 子樹內的 Widget 能存取狀態 三種存取方式:context.watch()、context.read()、context.select() 自動重繪機制:使用 watch 時,狀態改變會自動觸發 Widget 重繪 實戰問題:跨路由讀取 Provider 這是我在開發中遇到的真實問題: ...

July 13, 2023 · 15 分鐘 · Peter