聊聊 API First 開發策略


整理我個人這幾年來 (Since 2017) 接觸 API 有關的議題,共分成以下的大議題:

  1. API 設計:重點在於滿足業務需求、控制範圍,包含資料模型、設計方法、流程與驗證
  2. API 架構:API Gateway、快取、效能、通訊協議、監控 …
  3. API 治理與管理:版本控管、導入策略 (API First / Spec First / Contract First)、上架政策、訂閱管理、問題排查
  4. API 實作與模式:包含常見的機制,像是 async、pagination、tracing、idempotence、batch、bulk … 等
  5. API 軟體工程:開發方法、測試方法、開發流程、跨團隊協作、文件

以及寫在 FB, Thread#1, Thread#2, Thread#3 的隨筆,包含 API 在當代軟體開發的重要性、設計方法論、實務經驗談,以及一些背後的思路。


一、API 的重要性

為啥組織需要透過 API?

AWS 的基因 - 標準通訊介面 API

從 Jeff Bezos 與 Werner Vogels 學到的 整理了這段在 2002 年寫下非常有名的 Memo,如下:

Jeff Bezos’ Mandate: Amazon and Web Services

  1. All teams will henceforth expose their data and functionality through service interfaces.
  2. Teams must communicate with each other through these interfaces.
  3. There will be no other form of interprocess communication allowed: no direct linking, no direct reads of another team’s data store, no shared-memory model, no back-doors whatsoever. The only communication allowed is via service interface calls over the network.
  4. It doesn’t matter what technology they use. HTTP, Corba, Pubsub, custom protocols — doesn’t matter.
  5. All service interfaces, without exception, must be designed from the ground up to be externalizable. That is to say, the team must plan and design to be able to expose the interface to developers in the outside world. No exceptions.
  6. Anyone who doesn’t do this will be fired.

第四點提到的其他協議,像是 Corba / Custom Protocols,這些會隨時代逐漸退場的,其他在這世代是都適合用的,特別第六點。

Amazon 本業是電商,其中 IT 屬於支援部門,但卻誕生了最成功的公有雲 - AWS (Amazon Web Services),而這段 Memo 被認為是 AWS 能成功的基因,

MACH Architecture

MACH 架構表示這四個概念: Microservices(微服務)API firstCloud-nativeHeadless (無頭式架構),是現代企業數位轉型中的軟體設計理念。

底下用 ChatGPT 整理:

  • MACH 起源於對傳統單體式架構的反思,MACH 鼓勵系統以小型、獨立部署的模組建構,透過 API 溝通,並部署在雲端環境中,使前後端可分離(Headless),提升靈活性與擴展性。
  • MACH 聯盟(MACH Alliance)於 2020 年成立,成員包括 commercetools、Contentstack、Amplience、EPAM 等,致力推動開放、可替換、最佳化體驗的軟體生態。其理念與組織影響了電商、金融、媒體等領域,促使企業拋棄封閉式平台,轉向組合式架構(Composable Architecture)。

然而實務上導入 MACH 並不容易,挑戰包含系統整合成本高、團隊技術門檻高、測試與部署複雜度提升等。因此適合已有一定規模、追求敏捷與差異化的企業,而非所有組織皆適用。整體而言,MACH 架構代表一種軟體現代化的方向,但需要與組織成熟度搭配落地。

Kubernetes isn’t about containers

It’s about APIs; we’ll get to that shortly.

以下用 ChatGPT 摘要重點:

Kubernetes 的價值不僅在於管理容器,而在於提供一套標準化的 API 框架,讓基礎設施能以軟體方式定義和操作。作者指出,雖然容器技術如 Docker 改變了應用部署方式,但 Kubernetes 的成功關鍵在於其統一的資源模型和控制器機制,使得不同類型的基礎設施資源(如計算、網路、儲存)能以一致的方式描述和管理。透過自訂資源定義(CRDs)和 Operator Framework,Kubernetes 的 API 被擴展到涵蓋資料庫、任務排程、訊息佇列等服務,形成一個可擴展的雲端服務標準。即使 Kubernetes 本身並非完美,其廣泛的採用和生態系統的成熟,使其成為現代雲端基礎設施的事實標準。

小結

如果組織想要:

  • 組織如果要發揮跨團隊整合
  • 組織想要降低溝通成本
  • 組織想找出效能不佳,可以改善的地方
  • 產品從外界來看,是一個產品,而不是一盤散沙

那麼整頓內部的 API 標準化與設計是必要的,而 API 的品質則會體現出上述的反面問題。


二、設計方法論

這裡指的是 RESTful API 的設計,不會涉及實作。

貧血型模型,API = CRUD?

API 開發經常會被很多鄉民這樣說:

整天就是在 CRUD,每天上班都在懷疑人生

一些 Backend 工程師整天會抱怨工作內容就是 CRUD,之所以會有這樣的連結、認知,這與資料庫操作方式密切相關。

CRUD 是 Create / Read / Update / Delete 的縮寫,通常針對一筆資料的操作 (Operate),背後描述的是針對「資料的操作」。當開發者直覺地將資料表對應到一組 CRUD API,就會出現 API = CRUD 的認知,然後就會出現自我懷疑的狀況。

在此想先引用一個來自於 Martin Fowler 在 2003 年提出的概念:貧血型模型 (Anemic Domain Model),文中提到大家在寫 Model 時,經常無腦的用 Getter / Setter 來描述資料模型,卻缺乏透過模型的領域知識描述模型,像是領域特有的動作 / 動詞。缺乏領域知識的模型就被稱為貧血型模型。而此描述的現象,跟用 CRUD 描述 API 有著一樣的現象,也就是只用 DB 操作 CRUD 來描述 API,因此我把這種只有 CRUD API,沒有領域概念的 API 稱為 貧血型 API

實際上,優秀設計的 API 並不一定與資料表的操作一一對應。因為有些資源屬於 聚合(Aggregation)、有些是 靜態資訊(如 Config、Version)、有些則是狀態描述或操作指令,並無對應的資料落地。因此從設計角度出發,應先理解「資源」的本質,再決定是否與資料表做一對一對應,最終目的則是要設計出以領域知識為核心的操作與行為,使用者更容易理解、更能滿足業務需求的 API,這樣才不會陷入 CRUD 這種思路。

以 Domain 為核心的設計出發點,以 AWS EC2 Instance 為例

當我們以 Domain Knowledge 作為設計基礎,每個資源(Resource)的操作會有其特有動詞,形成獨特的 API。舉例來說,AWS EC2 的 Instance 資源,一台 VM Instance 操作 包含以下動作 (動詞):

launch、start、stop、shutdown、reboot、terminate、change instance type … etc

而一個 Instance 則會有以下的狀態移轉 (官方狀態圖):

相關文章: EC2 Instance Lifecycle and Troubleshooting

這些 狀態 (State)動作 (Action) 才是 EC2 Instance 的領域知識。

使用狀態機描述的 RESTful API 設計方法論

用 AWS EC2 Instance 官方提供的 Lifecycle,透過 狀態機 (Finite State Machine) 重新分析過後得到以下狀態機:

這張狀態機設計有幾個重點:

  1. 狀態 (State):
    • Instance 特有的領域用詞,這些狀態以 名詞 或者 形容詞 描述
    • 圖中描述的狀態有 固定狀態, 以及 過度狀態 (passing states) 兩種。因為 Instance 操作本身是非同步的。
    • 狀態機一定要有 終止狀態 (End State),這個例子的結束是 terminated
    • 圖中灰底表示過度狀態,由系統控制
  2. 領域行為 (Behaviro, 動詞):
    • 領域特定的行為,VM Instance 的行為就是前述提到的 launch、start、stop、shutdown、reboot、terminate … 等 動詞
    • 領域行為 是使用者可以操作的動詞,用來觸發狀態之間的 轉換 (Transition)
    • 圖中可以表述的領域行為有 launch, start, stop, reboot, terminate … etc
    • 本案例中的 使用者 預設是 end user of ec2,不包含 admin of ec2 instance

以此為原則,展開相對應的 API 設計,得到以下的設計藍圖:

上圖中根據動作展開對應的 API,主要的有如下 (這不是全部):

  • POST /instances: 建立 (launch) Instance
  • GET /instances/{id}: 獲取 (retrieve) 指定的 Instance 資訊
  • DELETE /instances/{id}: 刪除 (terminate) 指定的 Instance
  • PATCH /instances/{id}:start: 啟動 (start) 指定的 Instance
  • PATCH /instances/{id}:stop 關閉 (stop) 指定的 Instance
  • PATCH /instances/{id}:reboot 重啟 (reboot) 指定的 Instance

其中依據不同的實作技術,動作可能更多。

從這裡開始可以很明顯的感覺到,這已經不是 CRUD 概念的設計,而是以領域為核心的設計。其中在技術上使用了 Standard MethodsCustom Methods 這兩個概念。Standard Methods 表示 HTTP 幾個標準的 Methods,包含 GET / POST / PUT / DELETE / PATCH … 等,而 Custom Methods 則是透過 URI 的結構定義 CRUD 以外的 動詞,也就是圖中紫色底的動作。

更多關於 Custom Methods 參閱我整理的簡報: API Design Patterns - CH9: Custom Methods

狀態機:狀態控制

上圖透過狀態移轉設計出來的 RESTful API 因為版面關係無法表述所有的狀,所以透過另一種方式來所有排列組合列出來。透過 狀態轉移(State Transition) 的排列組合列表,表列出所有有可能的動作,這張表稱為 State Table or Transition Table

這張表明確描述狀態 起點 (From)目標 (To)之間的可操作性,如果可以操作,那是什麼動作?這樣可以明確的表示有哪些行為組合,展開後如下圖:

圖中 From / To 的每個交集動作:

  • 如果是 ✅ 代表可以操作,如果是 ❌ 代表不可操作。
  • 能否操作,則由領域知識專家決定
  • 每個可以操作的動作,都是領域專有的動詞,用詞由領域知識決定

使用狀態機設計要注意的地方如下:

  • 狀態用詞為 State,使用為 名詞形容詞,是有 數量限制、有 先後次序
    • 經驗上來看,狀態 10 個為上限,再上去會複雜到很難處理
    • 狀態數量 (N) 則表示有 (N+1) x N 的狀態移轉要思考
    • 非同步 的系統,則會有 過度狀態 (Passing State)
  • 狀態機要有明確的 起始終止狀態 (End State)
  • 狀態與狀態之間的轉換稱為 狀態移轉 (Transition)
    • 狀態移轉 不是由使用者直接指定,而是由動作觸發,結果則由 業務邏輯決定 (Business Flow)
    • 狀態移轉則透過動作 (動詞) 觸發
    • 狀態移轉的條件,由領域知識決定
  • 狀態之間的連線代表 動詞,背後影含著:誰 (who) 能做的 動作 (action),以及產生的 事件 (event)
    • who: 代表授權機制,通常是 垂直權限,可以類似於 AWS IAM 的 Role,可以是 OAuth2 的 scope
    • action: 動詞,同一個狀態移轉,可以透過多個動作觸發
    • event: 事件則表示非同步行為,像是計算成本、觸發其他的資源的行為

State 與 Status 的差異:Status 沒有數量限制、沒有先後次序、也沒有起始、終止等狀態,像是 HTTP Status Code

狀態機在實作上,可以透過 State Table 的方式控制,也可以利用既有的框架,像是 Spring State Machine

Q&A

Q: 狀態機不見得適合所有的 resource 吧?

經驗上來看,大部分的資源分成幾種:resource-based, record-based, request-based。resource-based 就是本文中提到的 VM,他有個實際的資源佔用,而 API 本身的資料庫算是另一份副本,來控制狀態。第二種是 record-based,也就是純粹的資訊紀錄,像是電商常見的訂單。第三種則是重點在處理程序 (time-bound),通常不會被長時間保留的,像是 push / sms 這種東西。但實際上無論哪一種,實際上都還是有個狀態,會比較容易理解。

Q: 這個方法並沒有提到 Data Model,這樣的設計是可行的?

Data Model 也是設計很重要的一環,本文先以 API URI 定義開始,背後核心的概念就是: 好的 API 應該具備容易理解的特性,也就是還沒有看 Data Model 就大概知道可以做什麼。Data Model 有機會之後來分享設計的想法,但業界最好的範例就是 K8s 的 API.

小結

當我們畫出資源的狀態機,並標示狀態轉移,即可初步知道要實作哪些 API,下一步就是透過產開 Transition Table,分析出所有可能組合,再用刪去法去除不合理或不必要的部分,留下即為必要實作的 API。透過此方式設計出的 API,既符合 RESTful 精神,也更容易理解與使用。反之,純以 CRUD 寫出的 API,往往缺乏 Domain 的思考,只是機械式的實作。而且範圍是模糊且不清楚的。

上述的設計方法論只是個基本摘要,但已經包含整個設計核心的論述與思路。重點如下:

  1. 以資源為核心的領域知識, 類似於 ER Digram or Class Diagram 的概念
  2. 每個資源以狀態機為核心
  3. RESTful API 透過狀態機展開

整個設計背後的思路是:

以使用者角度出發,使用者可以 怎麼用,而不是 API 的開發者 怎麼做

對使用者而言,就是拿到一個語意明白、意圖清晰、有限範圍,容易使用 API。反之,這樣的設計對於 API 開發者會考驗其真實的實作實力,例如 CQRS、State Machine 的控制等。


三、API First 導入經驗談

如同 MACH 架構提到的,實務上要導入 API First Approach 並不容易,會有方方面面的挑戰。底下摘要一些導入過程曾經遇到的問題:

  1. 讓團隊意識到 OpenAPI 規格的重要性需花費不少時間。
    • 所有細節其實都寫在規格裡,但出現問題時大家往往不會先翻規格。
    • 這與許多工程師不寫文件,卻抱怨別人系統沒有文件的現象如出一轍。
  2. API 規格可以反映設計者是否具備 OOAD / DDD 的觀念。
  3. 優秀的 API 設計應具高度重用性與易用性,應視為「積木」;
    • 而商業流程應由 Application 層負責。
    • 若 API 設計者將業務邏輯寫進 API 中,將導致耦合升高、重用困難。
  4. API 只要定義好 Swagger YAML,Client / Server 便可同步開工。
  5. OpenAPI v3.0 規格在大型系統開發上仍有不足之處
    • 例如缺乏模組化能力,$ref 的 YAML path 存在侷限,難以切分大型模組。
  6. 雖然市面上有不少 OpenAPI 工具,但要找到真正適用、穩定的仍需耗費時間,整體缺乏標準化與生態系統整合。
  7. API 必須與 Use Case 搭配才能完整理解。
    • API 主要描述結構(空間),但無法說明行為(時間),兩者結合才是產品真正的使用情境。
  8. 許多人認同 API First,但實際仍先寫程式,再反推生成 API Spec,因為這樣做比較容易、趕 Deadline 比較實際。
  9. 同理,使用他人 API 的開發者也常發生類似狀況。
    • 即使認同 API First,拿到 API Spec 後也常是看不懂,或拖到最後一天才看。
  10. API 設計過程本質上是一面「照妖鏡」,能照出團隊間的溝通問題。
    • 無論是設計者、使用者還是提供者,往往可從中看出問題根源,有些甚至是歷史遺留的政治問題,API First 本身無法解決。
  11. API Spec 就是產品規格文件,應是所有產品成員都看得懂的文件。
    • 但現實是,通常只有撰寫者看得懂,大多數團隊並不會實際撰寫 API Spec,也因此沒人真正了解產品具體規格。
  12. 在沒有正式產品規格的情況下,API Spec 是最基本、最完整、也最具體的形式,甚至比各種簡報或文件更具參考價值。

「規格」是能讓工程師開始動手做,並可透過驗證確認是否正確的文件。就如同 RFC 定義了 DNS、HTTP 的標準。產品規格,是讓團隊可以開發、讓用戶可以驗收的具體依據;而 API Spec 同時具備這兩者的特性,並成為開發者與用戶之間的關鍵橋梁。


結論

設計方法論的發想背景

這幾年實務設計 RESTful API,我習慣以「有限狀態機 (FSM)」的思維搭配 OOD 的 Class 設計進行 API 設計。其核心包含:資源、狀態、動作、角色。

2012 年左右,因緣際會下開始研究 專案管理系統 (Project Management System),那時候接觸了要錢的 JIRA 以及不要錢的 Redmine,後來選擇不要錢的 Redmine。Redmine 的設計非常有彈性,撇除專案管理的功能,最核心的概念就是可以定義每種 Tracker Type 的狀態,以及透過狀態移轉方式控制流程,同時可以針對不同角色,設定不同流程的走向。當時我是負責整個團隊的軟體生命週期規劃,包含需求、設計、開發、測試、部署、維運等等,所以對於流程控制的需求非常強烈。Redmine 的這個設計概念讓我非常滿意,也因此開始研究 FSM 的概念。從 Feature、Bug、Deployment、Task 等等,每個 Tracker Type 都可以定義不同的狀態、狀態移轉的方式、哪個站點可以卡控 … 等。

這段設計過程,在團隊獲得應證,團隊跑的方式不是現在流行的 Scrum,而是自己定義的流程,每個人都知道自己的角色、責任,以及流程的走向。by the way,這概念也運用在管裡整個公司的 IT 資產管理、以及人事狀況,都是用 Redmine 來管理,但我只是把方法教給其他人,我只是在旁邊看著。後來也玩了像是 Azure DevOps (VSTS),其實也都有一樣的概念在。

後來在 2017 年參與 API Gateway 架構設計與導入,在 Review 公司既有 API 時,我注意到 RESTful API 的設計,其實可以用 FSM 的概念來思考,只是針對已經「落地」的 API 就有點晚了。

2020 年因應公司的 API First 策略,執行 API Platform 的規劃,包含三大部分:

  1. API 設計方法論(Design Methodology)
  2. API 架構設計(Architecture)
  3. API 管理與治理(Management)

Design Methodology 目的就是要找出一個設計方法,可以讓公司的 API 設計對標一些大型組織的設計,那時候跟同事一起研究 K8s API、GCP API、Shopify API、AWS API、GitHub API、Azure API … 等。這些 API 都有一個共通點,就是資源的狀態移轉,以及角色的權限控管。加上之前我使用 Redmine 等經驗,所以嘗試把 FSM 的概念帶入 RESTful 設計裡,搭配 OOD 的 Class Diagram 設計,這樣的設計方法論,可以讓 API 設計更有邏輯性,也更容易讓使用者理解。同時可以透過刪去法,減少不必要的 API,讓 API 的語意更清晰。

更多設計的方法,也可以參閱 Andrew Blog 的文章:API First 的開發策略API Design Workshop從狀態圖來驅動 API 的實作範例,基本上跟上述概念是一樣的。

API Architecture 的工作重點則是要規劃 API Gateway 以及內部系統通訊協議,有興趣可以看看 2018 我在 AWS Summit 分享的 邁向 API 經濟 - API Gateway 導入之旅AWS API Gateway 學習的系列文Study Notes - Using an API Gateway

API Management 則是要規劃 API 的生命週期管理,包含 API 設計、API 開發、API 測試、API 上線、API 監控、API 退市等等。這部分也是大坑,有空再來寫 XDD

過程中,啃了不少書,有三本推薦的:

  1. API Design Patterns
  2. Continuous API Management
  3. Mastering API Architecture

而執行面則展開以下兩大部分:

  1. API 實作與模式:包含常見的機制,像是 async、pagination、idempotence、batch、bulk … 等
  2. API 軟體工程:開發方法、測試方法、開發流程、跨團隊協作

延伸閱讀

相關文章

發想

推薦閱讀



Comments

2025/03/08 13:30:00





  • 全站索引
  • 關於這裏
  • 關於作者
  • 學習法則
  • 思考本質
  • 一些領悟
  • 分類哲學
  • ▲ TOP ▲