HTTP

REST API 架構約束 — HTTP 統一介面

客戶-伺服端 (Client-Server),或俗稱 主從式架構 (我不太喜歡這說法),
是基於網路應用 (network-based applications) 最常見的架構風格 (architectural styles)。
背後的約束原則是 — 關注點分離 (Separation of Concerns, SoC):

一次注意一件事

 
對於 Web 最重要的部分在
— — 分離 使用者介面 (user interface)資料儲存 (data storage) 觀點。
 
 
因此:

  1. Server 能將 使用者介面 (user interface) 的功能,
    交由 Client 元件 (component) 處理,
    藉此簡化 Server 元件,使其提高 可擴展性 (scalability)

  2. 將觀點分為 Client 與 Server,
    代表 Web 元件 之間 (i.e., User Agent、Origin Server、Proxy、Gateway),
    能各自 獨立發展 (evolve independently)
    (e.g., 你能藉由多種平台上網 💻📱⌚️)

 

實現的條件是?

 

介面一致

 


 

統一介面 (uniform interface)

HTTP 旨在:

透過向 客戶端 (Client) 提供與 資源種類無關統一介面 (uniform interface)
藉此 隱藏服務實現的細節

 
同理:

伺服端 (Server) 不需知道每個客戶端的目的 (purpose),
單獨考慮 每個 HTTP 請求,而非 僅與 特定種類 的 Client 或 應用相關。

 
若 HTTP 完全定義了 Client、Server 具體的實作細節,
不但擴充性受限,其中一方一改變,甚至會迫使對方跟著改變。
 
試想:你的瀏覽器只有 IE 可以用..😱
fear
 
 

設計原則 (Design Principles)

以 軟體設計 角度,審視 HTTP 的 統一介面 (uniform interface) 約束:
 
分離 使用者介面 (user interface)資料儲存 (data storage) 觀點,
使各元件能 獨立發展 (evolve independently)

將 「會因為相同理由而改變的東西」封裝起來,「會因為不同理由而改變的東西」分離開來,
使單一模組變更的原因只有一種 — — 單一職責原則 (Single Responsibility Principle, SRP)

 

 
Client 依賴的是 抽象介面 (HTTP)表示 (representation)
而非具體的 Server 或 特定的類型 (e.g., html、txt、json),
因此,不用也不該知道 Server 如何實作資源。
 
如此一來,其中一方改變,便不會影響另一方
例如,配置正確的前提下,Server 更換作業系統、資料庫,
並不會影響到 Client,它甚至毫不知情。

依賴於抽象,而非具體實作 — — 依賴倒置原則 (DIP)

 
也確保 Client 元件 使用不同 的 Server 元件 時,行為可 維持不變:

對擴充開放,對修改封閉 — — 開閉原則 (OCP)
子元件 (component) 能替換 它們的 父元件 (e.g., user agent、gateway) — — Liskov 替代原則 (LSP)

 
 

解耦 (decoupling)

當然,還有許多通用的 軟體工程/套件 設計原則、架構模式…,
應用於元件介面上,簡化了整體系統架構的複雜度,提升了彼此交互的 可見性 (visibility),
 
高內聚力 (high cohesion)鬆散耦合 (loose coupling)
不僅僅適用於程式設計,同時是 架構 至關重要的觀念。
 
例如,Server 能因應需求,實現 技術異質性 (Technology Heterogeneity) [͵hɛtərədʒəˋniətɪ],
能將 貼文、連結、個資 存在 關連式資料庫 (e.g., M$SQL、MySQL),
也能儲存於 文件導向資料庫 (e.g., MongoDB),或將 社交關係 存於 圖學導向資料庫 (e.g., Neo4J)。

 
最重要的是:

有效地將 服務 與 具體實作 解耦 (decoupled)

 


 

無狀態 (Stateless)

上述提及:

伺服端 (Server) 不需知道每個客戶端的目的 (purpose),
單獨考慮 每個 HTTP 請求,而非 僅與 特定種類 的 Client 或 應用相關。

 
其背後的支持基礎在於 — — 無狀態 (stateless) 架構約束。
 
無狀態 (Stateless) 是指從 Client 到 Server 的所有請求,
皆需包含『 用於理解此請求 』的 所有 相關信息,不得利用任何儲存在 Server 的上下文,
(例如, 使用者身份、授權憑證、購買紀錄、購物車選項…)
 
假設,我剛剛登入了 A 網站:

Photo by materiallogindemo.
 
欲前往其他頁面使用服務時 (e.g., 購買、訂閱、儲值),必須提供 授權憑證 (e.g., Cookie、Authorization),而非假定 Server 知道 我們已經登入:

POST /product?id=87 HTTP/1.1

Host: example.com Authorization: Bearer 51d05e71d393f7e228f38d8c8818f079e3dee425 Content-Type: application/json; charset=utf-8 User-Agent: FooBar Content-Length: 136

 

{ "status": "ok", "extended": true, "results": [ {"value": 0, "type": "int64"}, {"value": 1.0e+3, "type": "decimal"} ] }

 
HTTP 被定義為 無狀態協定 (stateless protocol),
意味著 每個請求訊息,可以被單獨地理解,而不需其他 先前知識 (Prior Knowledge)。
 
許多實作依賴於 HTTP 的無狀態設計,
以便 重用代理連線 或 跨多個 Server 的 動態負載平衡 (dynamically load balance) 請求:

會話狀態 (Session) 全部保存在 客戶端 (Client)。

 
因此,除非該連線是安全的 (i.e., HTTPS) 且特定於某 使用者代理:

Server 不得假定 同一連接 上的兩個請求來自同一個 User Agent。

 

可見性、可靠性、可伸縮性

Stateless 架構約束 也帶來了三種架構屬性。
 
提升了 可見性 (visibility)
監視系統不必為了單一請求,前往查看先前的其他請求 (e.g., 登入紀錄)。
 
改善了 可靠性 (reliability)
當系統發生局部故障時,能減輕 復原故障 所需的任務量。
 
帶來了 可伸縮性 (scalability)
不必在多個請求之間保存 (會話) 狀態,從而允許 Server 元件迅速釋放資源,減輕負載,
也因為 Server 不必管理跨請求的資源使用,進一步簡化了實作。
 
 

Server Session Problem

我們來看一下,有狀態 是怎麼一回事。
 
例如,使用者 Jason 在 第一次請求 時進行登入,且 Origin Server A 保存了此狀態:

 
 
接著,使用者 Jason 想查看信箱並執行 第二次請求
Origin Server A 剛好較忙碌,因此被分配流量至 Origin Server B:

 
 
最後,因為 Origin Sever B 並未保存過 Jason 的登入紀錄,而發生超腦殘錯誤:
show_error
Photo by sweet-alert-dialog.
 
 
別擔心,解法百百種! 你仍可以使用 Server 會話機制!
儘管違背了 REST 架構風格,考量業務需求與便利性,
大部分後端框架/語言 皆支援 Session 功能 (e.g., Laraval)。
 
然而,許多傳統 Session 實作,將資料儲存在本機硬碟,
不但易造成上述的 擴展問題,更會導致大量的 Disk I/O 時間,使用上請斟酌。
 
 

表示狀態傳輸
(Representational State Transfer, REST)

上面提及了 表示狀態傳輸 (Representational State Transfer, REST) (跟我唸一次)。
 
REST 是一 架構風格,源自 HTTP/1.1 的主架構師 — Dr. Roy Thomas Fielding,
於 2000 發表的著名論文 《Architectural Styles and the Design of Network-based Software Architectures》。
 
REST 為當時 Web 架構的設計基礎理論,
旨在在提倡良好的 Web Application 行為。
 
要小心的是:

REST 中的『 狀態 』,是指 資源 與 表示 的狀態
而非 無狀態 (Stateless) 架構約束 中的 會話狀態

 
(可惜的是,許多人穿鑿附會、混為一談了 😱)
 
其中,Client-Server、無狀態 (Stateless)、Cache、
統一介面 (uniform interface)、Layered System、可選的 Code-On-Demand,
皆為 REST 重要的 架構約束 之一。
 
統一介面 又包含四點 介面約束 (interface constraints)
以指導 元件 (component) 間的行為:
 

  • 資源 的識別 (identification of resources)

  • 表示 來操作資源 (manipulation of resources through representations)

  • 自我描述 的訊息 (self-descriptive messages)

  • 超媒體 做為應用狀態引擎 (hypermedia as the engine of application state)

 
快吐血的話沒關係,看過有印象即可 😉,
系列文將逐一介紹 REST 架構 與其約束之關係。
 
 

沒有銀彈 (No Silver Bullet)

無狀態 (stateless) 架構約束,同樣反映出 設計的權衡 (design trade-off),
由於 不能依賴 Server 於 共享情境 (shared context) 保存狀態資料,
因此 增加了每次的互動開銷 (一系列請求中發送的 重複資料),例如,使用者授權憑證,
這不僅降低了網路效能,還得依賴不同版本的 Client,要能正確的實作 HTTP 語意。
 
統一介面 (uniform interface) 架構約束,則意味著:
需使用 標準化的格式 傳輸/轉移 (transfer) 訊息。
 
因此,HTTP 對於某些形式的架構交互,不一定是最佳的介面
統一介面 也與客製化的應用需求 產生衝突 (e.g., 非同步), 降低了效率
 
並且,一旦介面更新,我們無法要求所有元件進行 擴展 與 改變,
版本不一,可能帶來災難性破壞,
這也是為何,Web 相容性總令開發者厭煩。
(e.g., 某些奇怪的 javascript 特性、命名 ; 非標準卻普及的表頭欄位)
 

無論是 HTTP 或背後的指導原則 REST
皆是權衡利弊後的產物,而非架構的萬靈丹。

 
 

作者: 鄭中勝

喜愛音樂,但不知為何總在打程式 ?
期許能重新審視、整理自身所學,幫助有需要的人。

在《REST API 架構約束 — HTTP 統一介面》中有 2 則留言

發表迴響