看到標題是否覺得很奇怪?休息時間,如果改成 REST time 有沒有一種突然豁然開朗的感覺呢?每次看到 REST 就讓我想起以前念研究所和老師一起想論文題目時,曾提過國外常會玩這種文字遊戲,像是將 Representational State Transfer 變成一個很簡單的單字 REST,但我的東西不管怎麼想,卻想不出什麼有趣的東西 Orz
前言
會想寫 REST,是過去在第一間公司工作時,曾聽到當時的架構師對著團隊說:『不要以為用了 JAX-RS 就是 Restful Web Services,你們 URL 命名根本就亂七八糟』,剛進去的時候,確實只是簡單地看了一下
Wikipedia 的說明,就開始用 JAX-RS 的 annotation 開發後台的 API,也沒多想什麼,所以後來自己在訂 Restful 的 API 總是特別小心,像是複數型的名詞、避免動詞、只使用 HTTP method (GET、POST、PUT 和 DELETE) 等等。
後來換工作,討論 API 時,當只有我特別這麼堅持時,總覺得好像是我比較龜毛似的,但 API 至少還是符合 Restful 風格,後來比較不是前線開發者後,等到 API 會議結束,看結果,幾乎所有的 API 都把動詞放在 URL 上了,曾經詢問了一下原因,但似乎沒引起什麼共鳴,後來想想算了,只要不對外宣稱這是 Restful API 其實也沒什麼不行。
半年後換工作,又從 iOS/Android 工程師變回後端工程師了,由於是我主導整個 API 的設計,在風格上沒什麼問題,但還是得和其他同事解說為什麼 API 是這樣命名的,但說的還是 Wikipedia 上的那一套,直到最近看完《建構微服務》後,才在思考要不要找出原始的論文來讀讀呢?
論文導讀
既然看都看了,就簡單和大家分享一下看完這篇論文的心得 (若我理解有誤歡迎更正) 吧!這篇論文用 Chapter 1 整章的篇幅,在說明怎麼描述一個軟體架構,過去在畫架構圖時,總是畫上幾個方塊和線條,然後就開始跟其他人說明,嚴謹一點就用 UML 的部署圖描述,從來也沒去想過要怎麼描述一個軟體架構。
A software architecture is defined by a configuration of architectural elements — components, connectors, and data — constrained in their relationships in order to achieve a desired set of architectural properties.
如上所述,論文倒是很清楚地用三個元素來描述一個軟體架構:
- A component is an abstract unit of software instructions and internal state that provides a transformation of data via its interface. (也就是平常架構圖上的那些方塊)
- A connector is an abstract mechanism that mediates communication, coordination, or cooperation among components. (圖上連接方塊的線)
- A datum is an element of information that is transferred from a component, or received by a component, via a connector. (在元件之間傳遞的資料)
聽完可能有人覺得,啊~ 不就廢話,我平常就這樣描述軟體架構啊,但這一章花了不少篇幅探討這三個元素是否足夠,這倒是我過去不曾想過的。最後,一個軟體架構風格則是一組限制,限制上述元素的角色或功能與關係,任何架構只要符合這些限制就能稱作是某種軟體架構風格,而 REST 是由六個限制所組成的軟體架構風格。
An architectural style is a coordinated set of architectural constraints that restricts the roles/features of architectural elements and the allowed relationships among those elements within any architecture that conforms to that style.
Chapter 2 則是探討網路應用程式 (network-based application) 的架構,我覺得很有價值的是條列了七種網路應用程式架構的 properties:Performance、Scalability、Simplicity、Modifiability、Visibility、Portability 和 Reliability,也就是一般軟體工程書中稱之為 non-functional requirement attributes,並說明為什麼選這七種做為評估架構的依據。並在 Chapter 3 中使用這些 properties 評估了共 20 種 network-based application architectures,合計 7 類的軟體架構風格,值得一看。
Chapter 4 討論設計 Web Architecture 的需求與會遭遇的問題,在繼續之前,可能要想一下為什麼是 WWW 架構,不就是網頁應用程式嗎?如果仔細看
Wikipedia 上對 WWW 的解釋:
The World Wide Web (WWW) is an information space where documents and other web resources are identified by URLs, interlinked by hypertext links, and can be accessed via the Internet.
只要任何能以超連結串起來的資訊空間就能稱作是 WWW,沒有一定要用 HTTP 協定,也沒有說一定是 HTML,因此,論文提出一種分散式超媒體系統的軟體架構風格能滿足三個假設,這就不細討論三個假設,這是大多數論文的必備要素,說完一堆人就睡著了,基本上,那個方案就是 REST。
Chapter 5 描述 REST 是如何從無開始對 WWW 加入六種限制所形成的一種軟體架構風格。因為是限制,所有描述會變成:
- 必須是 Client-Server,用意為 separation of concerns
- 必須是 Stateless,request 與 request 之間是獨立的,server 不需要記住狀態以處理下個 request
- 必須是可 Cache 的,但那些內容可以 cache 是由 client 與 server 協調的
- 必須是 Uniform Interface,這一點很重要
- 必須是 Layered System,基本上就是用 layer 隱藏實作的細節,client 只需要知道 uniform interface 即可
- 可以是 Code-On-Demand,這是 optional 的限制,所以我說『可以是』,基本上是可以用這個方式讓 client 用取得程式碼的方式擴充功能。
為了達成 uniform interface,論文對介面設計提出四個限制:
- identification of resources
- manipulation of resources through representations
- self-descriptive messages
- hypermedia as the engine of application state
剛剛不是說軟體架構可以用 component、connector 和 data 定義嗎?那 REST 風格的軟體架構呢?有的。
首先看 data,所有可被命名的資訊都是 resource,並給予 identifier (滿足 identification of resources),根據 client 的能力與需求 (透過 control data 描述),給予不同的呈現方式 (representations),像是 JSON、XML 等,並透過呈現方式操作資源,而不是直接操作資源,(滿足 manipulation of resources through representations),由於實際的資源與呈現方式可以相同也可以不同,所以 resource identifier 也可以視為資源與呈現方式 mapping (轉換) 的一種識別。resource 可以透過 representation metadata 與 resource metadata 加以描述,讓 client 可以自行決定如何解讀取得的 resources (滿足 self-descriptive messages)。
data 以 connector (client, server, cache, resolver, tunnel) 提供一致的方式存取,透過 identifier 存取不同的 resources (representation) 及轉換,完成目的 (滿足 hypermedia as the engine of application state)。component (origin server, gateway, proxy, user agent) 透過 connector 溝通,實際上,資源源如何儲存和怎麼處理則是隱藏在 component 中,可以直接提供 representation 或再透過 connector 與其他 component 請求 representation,但介面是一致的。
Chapter 6 則是經驗與評估,描述作者在參與 URI 與 HTTP/1.1 制定時,如何用 REST 找出問題並確定這些擴充沒有違反限制,個人認為有幾個重點:
- 資源的 identifier 應該鮮少變動
- 資源不是一個儲存物件,而是一種抽象的應對,一個 identifier 對應到一種抽象應對的實作
- 辨識使用者的資訊不應包含在 representation 裡的 URI 中
- HTTP 無法完整描述所有 REST 的語意
- 使用 HTTP 本身的 header 描述 representation 及提供 metadata 與 control data
- REST 不是 HTTP,HTTP 也不是 RPC
- 對 resource 的操作,client 和 server 要有一致的認知
Chapter 6 之後便是結論了。
回到 RESTful API
為什麼要花這麼長的篇幅介紹論文的內容呢?主要是,在設計 Restful API 時,有時候會有些猶豫,像是:
- token 或 session ID 應該放在 query parameter 還是 HTTP header 中?這有點 tricky,雖說 URI 上不該帶有使用者資訊,但若 resource representation 中的 URI 不夾帶使用者資訊,而是在發送當下才夾帶,算是有違反嗎?不過,就安全性來說,query parameter 有時候會在 log 時被記錄下來,所以算是盡量避免的另一個原因。
- API 版號應該出現在 URI 中還是 HTTP header 中?以 REST API Versioning 的描述兩種皆可,但看完論文後哪種較好呢?
當初在思考這些問題時,在網路上爬了很多文章和討論,但其實不管哪種作法都有,也有各自的擁護者,但等看完這論文後,我覺得我當初的選擇 (都用 HTTP header) 是比較接近論文的原意 (URI 不夾帶使用者資訊,並盡可能保持不變動,由 client 與 server 透過 control data 協調 representation),而自己對 URL 的命名,除了不太使用 controller 之外,也符合一般大多數的
最佳實務。
至於剛開始說的 URL 中該不該有動詞?以最佳實務來說,可以,用來代表概念性的 controller 資源,但不會用動詞描述 CRUD,因為 HTTP method 本身已經有這個語意了,個人認為 controller 這個概念性資源是對 HTTP 方法的一種擴充 (見論文的 Chapter 6),畢竟,完全用狀態與名詞描述行為確實不容易,以登入與登出這兩個行為來說,若觀察大多數的 API 會發現幾乎都是直接用 login/logout 這兩個動詞放在 URI 上,我當時也是想了很久,才以登入前和登入後狀態的差異,找到一個我覺得合適的名詞,取代用動詞放在 URI 上的做法。但之後是不是堅持不使用 controller 呢?我覺得還是視語意,挑選合適的方法比較重要。最重要的是,是否違反上述的限制,只要不違反就能稱作是 Restful API 了。
- level 0 就是什麼規範都沒有,或是把 HTTP 當成 RPC 使用的階段
- level 1 定義 resource,讓系統能以 resource 描述狀態
- level 2 善用 HTTP 動詞 (方法:GET、POST、PUT 和 DELETE)
- level 3 能以超媒體的方式控制流程,或是從 response 中的資訊可以知道接下來可以做什麼
結語
所以大家的 API 能到 level 幾呢?其實到 level 3 也不代表 API 設計的好,API 的設計牽涉到很多層面,就如同論文 Chapter 6 中也探討很多要考慮的因素,希望這篇長文,可以讓大家在設計 Restful API 時有更多的想法湧出。最後覆上一個有趣的文章讓大家思考一下,您平常是如何看待 API 的。