如何處理好先後端分離的 API 問題(轉載自知乎)



9 個月前html

API 都搞很差,還怎麼當程序員?若是 API 設計只是後臺的活,爲何還須要前端工程師。前端

做爲一個程序員,我討厭那些沒有文檔的庫。咱們就好像在操縱一個黑盒同樣,預期不了它的正常行爲是什麼。輸入了一個 A,預期返回的是一個 B,結果它什麼也沒有。有的時候,還拋出了一堆異常,致使你的應用崩潰。node

由於交付週期的緣由,接入了一個第三方的庫,遇到了這麼一些問題:文檔老舊,而且不夠全面。這個問題相比於沒有文檔來講,越發的可怕。咱們須要的接口不在文檔上,文檔上的接口不存在庫裏,又或者是少了一行關鍵的代碼。git

對於一個庫來講,文檔是多種多樣的:一份 demo、一個入門指南、一個 API 列表,還有一個測試。若是一個 API 有測試,那麼它也至關於有一份簡單的文檔了——若是咱們能夠看到測試代碼的話。而當一個庫沒有文檔的時候,它也不會有測試。程序員

在先後端分離的項目裏,API 也是這樣一個煩人的存在。咱們就常常遇到各類各樣的問題:github

  • API 的字段更新了
  • API 的路由更新了
  • API 返回了未預期的值
  • API 返回因爲某種緣由被刪除了
  • 。。。

API 的維護是一件煩人的事,因此最好能一次設計好 API。但是這是不可能的,API 在其的生命週期裏,應該是要不斷地演進的。它與精益創業的思想是類似的,當一個 API 不合適現有場景時,應該對這個 API 進行更新,以知足需求。也所以,API 自己是面向變化的,問題是這種變化是雙向的、單向的、聯動的?仍是靜默的?json

API 設計是一個很是大的話題,這裏咱們只討論:演進、設計及維護。後端

先後端分離 API 的演進史

剛畢業的時候,工做的主要內容是用 Java 寫網站後臺,業餘寫寫本身喜歡的前端代碼。慢慢的,隨着各個公司的 Mobile First 戰略的實施,項目上的主要語言變成了 JavaScript。項目開始實施了先後端分離,團隊也變成了全功能團隊,前端、後臺、DevOps 變成了每一個人須要提升的技能。因而如咱們所見,當咱們完成一個任務卡的時候,咱們須要本身完成後臺 API,還要編寫相應的前端代碼。api

儘管當時的手機瀏覽器性能,已經有至關大的改善,可是仍然會存在明顯的卡頓。所以,咱們在設計的時候,儘量地便將邏輯移到了後臺,以減小對於前端帶來的壓力。可性能問題在今天看來,差別已經沒有那麼明顯了。跨域

如同我在《RePractise:前端演進史》中所說,前端領域及 Mobile First 的變化,引發了後臺及 API 架構的一系列演進。

最初的時候,咱們只有一個網站,沒有 REST API。後臺直接提供 Model 數據給前端模板,模板處理完後就展現了相關的數據。

當咱們開始須要 API 的時候,咱們就會採用最簡單、直接的方式,直接在原有的系統裏開一個 API 接口出來。

爲了避免破壞現有系統的架構,同時爲了更快的上線,直接開出一個接口來得最爲直接。咱們一直在這樣的模式下工做,直到有一天咱們就會發現,咱們遇到了一些問題:

  • API 消費者:一個接口沒法同時知足不一樣場景的業務。如移動應用,可能與桌面、手機 Web 的需求不同,致使接口存在差別。
  • API 生產者:對接多個不一樣的 API 需求,產生了各類各樣的問題。

因而,這時候就須要 BFF(backend for frontend)這種架構。後臺能夠提供全部的 MODEL 給這一層接口,而 API 消費者則能夠按本身的須要去封裝。

API 消費者能夠繼續使用 JavaScript 去編寫 API 適配器。後臺則慢慢的由於須要,拆解成一系列的微服務。

系統由內部的類調用,拆解爲基於 RESTful API 的調用。後臺 API 生產者與前端 API 消費者,已經區分不出誰纔是真正的開發者。

瀑布式開發的 API 設計

說實話,API 開發這種活就和傳統的瀑布開發差很少:未知的前期設計,痛苦的後期集成。好在,每次這種設計的週期都比較短。

新的業務需求來臨時,前端、後臺是一塊兒開始工做的。而不是後臺在前,又或者前端先完成。他們開始與業務人員溝通,須要在頁面上顯示哪些內容,須要作哪一些轉換及特殊處理。

而後便配合着去設計相應的 API:請求的 API 路徑是哪個、請求裏要有哪些參數、是否須要鑑權處理等等。對於返回結果來講,仍然也須要一系列的定義:返回哪些相應的字段、額外的顯示參數、特殊的 header 返回等等。除此,還須要討論一些異常狀況,如用戶受權失敗,服務端沒有返回結果。

整理出一個相應的文檔約定,前端與後臺便去編寫相應的實現代碼。

最後,再經歷痛苦的集成,便算是能完成了工做。

但是,API 在這個過程當中是不斷變化的,所以在這個過程當中須要的是協做能力。它也能從側面地反映中,團隊的協做水平。

API 的協做設計

API 設計應該由前端開發者來驅動的。後臺只提供前端想要的數據,而不是反過來的。後臺提供數據,前端從中選擇須要的內容。

咱們常報怨後臺 API 設計得不合理,主要即是由於後臺不知道前端須要什麼內容。這就好像咱們接到了一個需求,而 UX 或者美工給老闆見過設計圖,可是並無給咱們看。咱們能設計出符合需求的界面嗎?答案,不用想也知道。

所以,當咱們把 API 的設計交給後臺的時候,也就意味着這個 API 將更符合後臺的需求。那麼它的設計就趨向於對後臺更簡單的結果,好比後臺返回給前端一個 Unix 時間,而前端須要的是一個標準時間。又或者是反過來的,前端須要的是一個 Unix 時間,然後臺返回給你的是當地的時間。

與此同時,按前端人員的假設,咱們也會作相似的、『不正確』的 API 設計。

所以,API 設計這種活動便像是一個博弈。

使用文檔規範 API

不管是異地,或者是坐一塊兒協做開發,使用 API 文檔來確保對接成功,是一個「低成本」、較爲通用的選擇。在這一點上,使用接口及函數調用,與使用 REST API 來進行通信,並無太大的區別。

先寫一個 API 文檔,雙方一塊兒來維護,文檔放在一個公共的地方,方便修改,方便溝通。慢慢的再隨着這個過程當中的一些變化,如沒法提供事先定好的接口、不須要某個值等等,再去修改接口及文檔。

可這個時候由於沒有一個可用的 API,所以前端開發人員便須要本身去 Mock 數據,或者搭建一個 Mock Server 來完成後續的工做。

所以,這個時候就出現了兩個問題:

  • 維護 API 文檔很痛苦
  • 須要一個同步的 Mock Server

而在早期,開發人員有一樣的問題,因而他們有了 JavaDoc、JSDoc 這樣的工具。它能夠一個根據代碼文件中中註釋信息,生成應用程序或庫、模塊的API文檔的工具。

一樣的對於 API 來講,也能夠採起相似的步驟,如 Swagger。它是基於 YAML語法定義 RESTful API,如:

swagger: "2.0"

info:
  version: 1.0.0
  title: Simple API
  description: A simple API to learn how to write OpenAPI Specification

schemes:
  - https
host: simple.api
basePath: /openapi101

paths: {}

它會自動生成一篇排版優美的API文檔,與此同時還能生成一個供前端人員使用的 Mock Server。同時,它還能支持根據 Swagger API Spec 生成客戶端和服務端的代碼。

然而,它並不能解決沒有人維護文檔的問題,而且沒法及時地通知另一方。當前端開發人員修改契約時,後臺開發人員沒法及時地知道,反之亦然。可是持續集成與自動化測試則能夠作到這一點。

契約測試:基於持續集成與自動化測試

當咱們定好了這個 API 的規範時,這個 API 就能夠稱爲是先後端之間的契約,這種設計方式也能夠稱爲『契約式設計』。(定義來自維基百科

這種方法要求軟件設計者爲軟件組件定義正式的,精確的而且可驗證的接口,這樣,爲傳統的抽象數據類型又增長了先驗條件、後驗條件和不變式。這種方法的名字裏用到的「契約」或者說「契約」是一種比喻,由於它和商業契約的狀況有點相似。

按傳統的『瀑布開發模型』來看,這個契約應該由前端人員來建立。由於當後臺沒有提供 API 的時候,前端人員須要本身去搭建 Mock Server 的。但是,這個 Mock API 的準確性則是由後臺來保證的,所以它須要共同去維護。

與其用文檔來規範,不如嘗試用持續集成與測試來維護 API,保證協做方均可以及時知道。

在 2011 年,Martin Folwer 就寫了一篇相關的文章:集成契約測試,介紹了相應的測試方式:

其步驟以下:

  • 編寫契約(即 API)。即規定好 API 請求的 URL、請求內容、返回結果、鑑權方式等等。
  • 根據契約編寫 Mock Server。能夠彩 Moco
  • 編寫集成測試將請求發給這個 Mock Server,並驗證

以下是咱們項目使用的 Moco 生成的契約,再經過 Moscow 來進行 API 測試。

[
    {
        "description": "should_response_text_foo",
        "request": {
          "method": "GET",
          "uri": "/property"
        },
        "response": {
            "status": 401,
            "json": {
                "message": "Full authentication is required to access this resource"
            }
        }
    }
]

只須要在相應的測試代碼裏請求資源,並驗證返回結果便可。

而對於前端來講,則是依賴於 UI 自動化測試。在測試的時候,啓動這個 Mock Server,並藉助於 Selenium 來訪問瀏覽器相應的地址,模擬用戶的行爲進行操做,並驗證相應的數據是否正確。

當契約發生發動的時候,持續集成便失敗了。所以相應的後臺測試數據也須要作相應的修改,相應的前端集成測試也須要作相應的修改。所以,這一改動就能夠即時地通知各方了。

前端測試與 API 適配器

由於前端存在跨域請求的問題,咱們就須要使用代理來解決這個問題,如 node-http-proxy,並寫上不一樣環境的配置:

這個代理就像一個適配器同樣,爲咱們匹配不一樣的環境。

在先後端分離的應用中,對於表單是要通過前端和後臺的雙重處理的。一樣的,對於前端獲取到的數據來講,也應該要常常這樣的雙重處理。所以,咱們就能夠簡單地在數據處理端作一層適配。

寫前端的代碼,咱們常常須要寫下各類各樣的:

if(response && response.data && response.data.length > 0){}

即便後臺向前端保證,必定不會返回 null 的,可是我總想加一個判斷。剛開始寫 React 組件的時候,發現它自帶了一個名爲 PropTypes 的類型檢測工具,它會對傳入的數據進行驗證。而諸如 TypeScript 這種強類型的語言也有其相似的機制。

咱們須要處理同的異常數據,不一樣狀況下的返回值等等。所以,我以前嘗試開發 DDM 來解決這樣的問題,只是輪子沒有造完。諸如 Redux 能夠管理狀態,還應該有個相應的類型檢測及 Adapter 工具。

除此,還有一種狀況是使用第三方 API,也須要這樣的適配層。不少時候,咱們須要的第三方 API 以公告的形式來通知各方,可每每咱們不會及時地根據這些變化。

通常來講這種工做是後臺去作代碼的,不得已由前端來實現時,也須要加一層相應的適配層。

小結

總之,API 使用的第一原則:不要『相信』前端提供的數據,不要『相信』後臺返回的數據。

相關文章
相關標籤/搜索