Quantcast
Viewing latest article 9
Browse Latest Browse All 428

閒聊 - Web API 是否一定要 RESTful?

傳說 C 語言風格(C#/Java/JavaScript…)程序員依其信仰分為兩大派,自古以來不共戴天:

Image may be NSFW.
Clik here to view.

依我的觀點,寫 WebAPI 的程序員也分成兩派,RESTful 派跟非 REST 派。我屬於後者,是非主流的少數派。

前幾天跟同事聊到 Web API 是否一定要 RESTful,三言兩語說不清,寫篇文章梳理思緒好了。

RESTful API 是指實踐 REST Representational State Transfer精神的 API 設計風格,其核心精神在於借用 HTTP 協定做為基礎,讓 API 規格簡單一致,大致有以下特色 :

  • 透過 URI 指定要存取或操作的資源
  • 可使用 QueryString,但只應拿來傳遞額外過濾條件或參數,不應包含識別資源的鍵值
  • 使用 HTTP 方法 POST、GET、PUT、DELETE 對應到建立、讀取、更新、刪除等動作。
    也有人主張 PUT 是 Relace (Create 或 Update),另外增加 PATCH 用於部分更新( Partial Update )
  • 透過 Accept Header 指明可接收的內容格式,例如:XML 或是 JSON
  • 伺服器透過 HTTP 狀態碼回傳執行結果,例如:200 成功、401 存取被拒、404 找不到資源、500 伺服器錯誤

而 REST 概念的提出者 Roy Fielding是 HTTP 規範的主要作者及 Apache HTTP Server 專案的發起人之一,這也是讓 RESTful API 風格備受推崇的原因之一。

延伸閱讀

採用 RESTful API 最大的好處是風格統一,API 名稱簡潔(不會冒出一堆 QueryThese、UpdateThat、DeleteBlah,動詞隱藏在 HTTP Method ),靠直覺及經驗就能快速上手;除錯時也可由 URL、HTTP 方法及傳回狀態直接解析各項操作的意義及結果。多年來,RESTful 設計已是 Web API  設計的主流,例如:ASP.NET MVC Web API 即是走 RESTful 風格,當在專案新增繼承 ApiController 的 API 類別,預設需實作 Get()、Post()、Put()、Delete() 方法以對映 HTTP GET、POST、PUT、DELETE 等動作。(註:當然,你也可以額外定義路由或使用 [Route("actionName")] 與 [HttpPost] Attribute 加入自訂方法,但要小心濫用會違反 RESTful 精神) ApiController 實做範例可參考:建置使用 ASP.NET Web API 的 RESTful Api - Microsoft Docs

RESFful API 是當今主流,伴隨而來的好處是相關資源豐富(Visual Studio 直接支援,還有自動為 REST API 產生說明文件及測試程式的 Swagger等),無疑是好物,但不幸地,我對它沒有愛。

實做過幾次 ,我最後選擇回歸使用一般的 ASP.NET MVC Action 實做 Web API,不使用 MVC 提供的 ApiController 機制。身為 KISS (Keep It Simple, Stupid) 法則的忠實信徒,說穿只有一項考量 – 簡單!

貫徹 RESTful 精神是件麻煩事,導致在設計 API 介面時會受到諸多限制。舉一個最簡單的例子:我想要刪除四本書,BookId 分別為 9,5,2,7,若要求依循 RESTful 精神,做法就蠻分歧的:

  1. 跑迴圈 DELETE /books/9, DELETE /books/5, DELETE /books/2, DELETE /books/7 (絕對符合 RESTful,但有點蠢...)
  2. 先透過 POST /books/selections 將 9,5,2,7 四本書打包,賦與唯一資源代碼,例如 /books/pack32767,再 DELETE /books/pack32767 參考
  3. Amazon S3 REST 的做法是 POST /?delete,傳入包含要刪除項目識別碼的XML
  4. Facebook Graph API、Parse Server REST API、Google Drive REST API 則採用將多個 DELETE 作業打包成 JSON 放在一個 Request 裡送出,在伺服器收到後再解開一一執行。 參考
  5. 也有人主張 DELETE /books/9,5,2,7 之類的做法,但如此有沒有違背 REST 精神? 我不知道

除此之外,像是混合多個異種資源的更新 URI 該怎麼取?無明確資源對象的作業 URI 該用誰? 用 MVC Action 實做只要取個能望文生義的 Action 名稱,定義好傳入參數及傳回結果就可搞定,一旦被要求符合 RESTful,難度瞬間上升,發生次數多了,RESTful 帶來的優勢是否足以彌補額外增加的成本?到頭來有可能早已無關優劣利弊,流於「當然要 RESTful,不然別人會以為我們不懂」。

而另一方面,要配合 RESTful API,JavaScript 呼叫時也變得較複雜,需要自訂 HTTP Method,解析 HTTP Status Code,雖不是大事,但不能用最簡單的 $.get()、$.post() 搞定,測試偵錯變得麻煩些 。

至於使用 HTTP Status Code 302/401/404/500 傳遞狀態,以 .NET WebClient.UploadData() 呼叫時將被視為 Exception,需要 try ... catch 攔截,會增加些許困援。

上述提到種種問題,其實都能有解,不然 RESTful API 如何能走到今天? 回到前面提到刪除多本書的例子,我總覺得原本單純可用 MVC Action DeleteBooks(string[] bookIds) 就能搞定的事,為了符合 RESTful 得大顯神通,有違 KISS 精神。

基於以上考量,設計 Web API 時我習慣寫成一般 ASP.NET MVC 方法而不用 ApiController,並一律限定 POST (多少降低一些 XSS 風險,參考:隱含殺機的GET式AJAX資料更新 ),執行結果無論成功失敗都傳回統一的 ApiResult 型別件:

    /// <summary>
    /// API呼叫時,傳回的統一物件
    /// </summary>
    public class ApiResult
    {
        /// <summary>
        /// 執行成功與否
        /// </summary>
        public bool Succ { get; set; }
        /// <summary>
        /// 結果代碼(0000=成功,其餘為錯誤代號)
        /// </summary>
        public string Code { get; set; }
        /// <summary>
        /// 錯誤訊息
        /// </summary>
        public string Message { get; set; }
        /// <summary>
        /// 資料時間
        /// </summary>
        public DateTime DataTime  { get; set; }
        /// <summary>
        /// 資料本體
        /// </summary>
        public object Data { get; set; }

        public ApiResult()
        {
        }

        /// <summary>
        /// 建立成功結果
        /// </summary>
        /// <param name="data"></param>
        public ApiResult(object data)
        {
            Code = "0000";
            Succ = true;
            DataTime = DateTime.Now;
            Data = data;
        }

        /// <summary>
        /// 建立失敗結果
        /// </summary>
        /// <param name="code"></param>
        /// <param name="message"></param>
        public ApiResult(string code, string message)
        {
            Code = code;
            Succ = false;
            this.DataTime = DateTime.Now;
            Data = null;
            Message = message;
        }
    }

傳回型別統一,前則可撰寫共用 AJAX 呼叫函式,先統一處理錯誤再將資料拋回原呼叫端,錯誤代碼依系統別統一管理,在前後端產生對應。這樣的做法實際跑過幾過多個專案,沒有遇到什麼大問題,看來是可行的。(API 說明文件及測試程式我是自幹程式產生器搞定,必須承認不能套用現成工具額外多花了工夫,但量身訂做的西裝格外合身,整體上仍屬值得)

總體來看,我偏好用 ASP.NET MVC 寫 Web API 不走 RESTful ApiController,在這個議題上,應該會繼續非主流下去。

Image may be NSFW.
Clik here to view.

Viewing latest article 9
Browse Latest Browse All 428

Trending Articles