天天看點

輕量級 Mock 接口及文檔管理平台 - Ming

作者:閃念基因

「Ming」是知乎内部一個 Mock 接口及文檔管理平台。

它設計簡潔, 使用友善, 解決了過去 Mock 接口和文檔缺乏管理, 存放混亂, 以及 Mock 接口和文檔長時間版本落後于線上接口的問題。

雖然業界也有很多類似的開源平台工具, 但「Ming」的設計實作從團隊自身實際出發, 更加精簡,符合團隊的需要。

背景

在「Ming」誕生前, Mock 接口與接口文檔缺乏統一管理, 散落在各種内部系統中,存在一些弊端 。

接口文檔缺乏統一管理

有的團隊喜歡把接口文檔寫在内部協作文檔平台裡, 有的團隊喜歡寫在 git 代碼倉庫裡, 它們的存放目錄及目錄深度各有不同。

當後來人想要看接口的曆史文檔時, 簡直無從查起, 許多文檔就這樣遺失了。

Mock 接口缺乏統一管理

Mock 接口一般是由前端寫在自己代碼倉庫裡, 寫一些 HTTP 服務的 Node.js 代碼, 生成簡單的 Mock 接口。

這樣做帶來的問題首先是繁瑣, 為了使用 Mock 接口, 需要經曆: 啟動本地 Mock 服務, 切換本地開發代理, 修改 Mock 接口代碼等操作, 是以編寫 Mock 接口這個環節往往是能省就省的;

另外還有的問題同樣是曆史接口無從查起, 時間長了沒有人記得那些 Mock 接口代碼是做什麼的。

Mock 接口與文檔落後于線上接口

需求一旦開發完畢并上線, 相應 Mock 接口和接口文檔就會變得無人打理, 開始慢慢被人們遺忘了。

之後需求疊代, 開發人員很有可能忘記更新 Mock 接口和文檔, 使得它們與線上接口越發脫節嚴重, 成為曆史的垃圾堆。

接口變動後缺乏測試

工程師在修改舊接口時, 難免會有疏漏, 使接口産生一些 bug, 導緻線上故障。 為了避免這樣的問題發生, 往往需要付出很大的精力和時間成本編寫測試腳本。

開發時應該寫測試的道理誰都明白, 可實際情況中我們往往沒有這樣的時間和精力。

輕量級的解決方案

在業界, 為了解決上面那些在各公司普遍存在的問題, 已經有很多功能類似的接口管理平台出現,這些平台的側重點各有不同。

「Ming」平台也有獨特的側重點, 那就是「輕量級」。

接口定義

定義接口的過程實際上就是在平台上建立接口的過程, 目的是為了最終生成「文檔」和「 Mock 接口」。

但在如何定義接口這個問題上, 不同的接口管理平台所使用的方案是不同的。

大部分接口管理平台将接口定義了解為産出一份結構化的接口描述 Schema

JSON Schema

有些平台是使用 JSON Schema, 規定每個字段是什麼類型, 有哪些可選值等等較長的描述

一份 JSON Schema 的結構如下:

{
  "type": "object",
  "properties": {
    "data": {
      "items": {
        "properties": {
          "type": { "type": ["number"] },
          "created_at": { "type": ["number"] },
          "updated_at": { "type": ["number"] }
        },
        "type": "object",
        "required": ["type", "created_at", "updated_at"]
      },
      "type": "array"
    }
  },
  "required": ["message", "data", "pagination"]
}           

非常詳細, 但太冗長了, 它所表達的内容其實就是一個包含三個數字類型字段 { type, created_at, updated_at } 的 Object 結構。

隻要結構再稍微複雜一點, 我們一眼看上去, 絕對沒法在短時間內了解, 這不是适合人閱讀的語言, 它适合機器使用。

OpenAPI

有些平台推動維護一種叫做 OpenAPI 的規範, 事無巨細地描述了接口的所有資訊, 比起 JSON Schema, OpenAPI 規範想要描述的内容範圍要更加廣泛, 包括 Header Server 等等。

一份描述接口傳回值的 OpenAPI 内容如下:

responses:
  '200':
    description: A user object.
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/User'   # Reference to an object
        example: 
          # Properties of the referenced object
          id: 10
          name: Jessica Smith           

可以看到, OpenAPI 中甚至有 $ref 即引用的功能, 其功能之強大, 規範之詳細可以預期。

這些規範的确非常詳細, 但卻使得接口的定義無比複雜了, 每個接口的定義都長篇累牍, 可讀性很差。

當我們想在使用這些定義規範的平台中建立一個接口, 究竟要花費多大的精力? 大概有以下三種思路吧:

  • 熟悉文法然後手寫 Schema
  • 操作一個表單, 不斷增加字段, 填寫字段的類型, 描述, 枚舉, 是否 required, 是否可為 null, 值的合法範圍等等
  • 接口傳回值(JSON)自動生成對應的 JSON Schema

第一種熟悉文法是不可能的, 我們不能保證人人都學會它的文法規則。

第二種填寫表單雖然基本不需要懂文法了, 但卻過于繁瑣, 不停地在滑鼠點選和鍵盤輸入之間切換, 消耗大量精力。

并且事實上, 等把接口寫完後會發現, 這個接口定義可能比我們最終實作的接口功能還更加詳細更加規範, 容易脫離實際。

第三種自動生成呢? 其實「傳回值(JSON)」所攜帶的資訊是要比 JSON Schema 少的, 是以這樣自動生成的 JSON Schema 一定會資訊失真, 仍需要手動修補。

接口傳回值即定義

「Ming」的接口定義很簡單, 不會對響應體和請求體做複雜抽象。

是以在「Ming」平台上建立一個接口的速度非常迅速:

首先在表單中填寫接口路由, 接口名稱, 接口描述等必要的基本資訊;

然後在 JSON 代碼編輯器中貼上接口傳回值, 寫點注釋, 點選送出;

一個接口就建立完成了。

輕量級 Mock 接口及文檔管理平台 - Ming

建立接口

注意: 此處的 JSON 代碼編輯器其實是 JSON5 編輯器, 是以可以寫注釋。

權衡利弊

「JSON Schema」和「Open API」作為接口定義更加詳細嚴格, 符合通用标準, 但編輯和建立時過于繁瑣, 使用效率較低, 很容易出現工程師為了快速投入使用而過于趕工的情況。

「Ming」的接口定義使用起來更加簡單, 效率更高, 但沒有嚴格的接口定義限制。

我相信 OpenAPI 和 JSON Schema 一定是将來會越來越廣泛應用的标準, 各種接口管理平台也會盡最大努力減少編寫接口定義時的所有不便之處。

但對于一些對接口文檔定義不需要這麼嚴格詳細的場景, 簡單易用, 快速産出成果才是更适合它們的選擇, 是以類似于「Ming」這樣弱化接口定義的方案會一直存在。

文檔生成與 Mock 生成

不同的接口定義方式決定了文檔生成和 Mock 生成的過程, 各有利弊。

文檔

如果使用 OpenAPI 或 JSON Schema 作為接口定義, 生成的文檔會更加詳細。

但缺點是類似 JSON Schema 的定義并不能反映接口實際傳回的内容, 比如隻知道某個字段是字元串類型, 卻不知道這個字元串具體是什麼樣子, 某些情況下可能不夠直覺。

「Ming」使用實際傳回值作為接口定義, 是以它所生成的文檔中, 各字段并沒有那麼詳細的定義, 僅有的隻是一些注釋。

優點是更加直覺, 接口字段的實際内容會更容易讓人把它和實際業務聯系起來。

Mock

JSON Schema 和 OpenAPI 的接口定義不能解決 Mock 接口生成的問題, 需要引入另一套 Mock 生成規則, 比如建立一種 DSL, 或者寫 js 代碼, 生成 Mock 資料。

「Ming」使用實際傳回值作為接口定義, 天然支援生成 Mock 接口, 非常便利。

自動化測試

不同類型的測試有不同的目的, 這裡暫時隻讨論兩種測試:

  • 可用性測試: 判斷接口是否正常工作, 如通過測試腳本中的一些斷言, 避免引起線上故障
  • 文檔同步自動檢測: 比較接口定義和線上接口實際傳回值, 得出接口文檔是否落後于線上接口的結論

可用性測試

大多數接口管理平台提供的測試功能都是可用性測試,使用方式多為:

建立測試任務, 預設一些 Header 如 Cookie, 然後在測試任務中編寫斷言語句, 讓它每隔一小段時間請求線上接口, 檢查是否能通過測試腳本。

這種測試需要手動建立, 手動編寫測試代碼,手動開啟, 編寫測試腳本的成本較高。

文檔同步自動檢測

似乎很多接口管理平台并沒有「文檔同步檢測」的概念, 或者說是沒有把它拿出來作為一項獨立的功能, 更多的是和「可用性檢測」混在一起, 界限模糊。

事實上, 在已知接口定義的前提下, 「文檔同步檢測」是可以自動進行的, 當接口文檔建立成功, 并且接口也已經聯調結束, 上線投入使用後, 接口平台就可以自動開啟這項檢測了。

「Ming」使用了接口實際傳回值作為接口定義, 如果用來比較線上接口是否同步, 這點資訊肯定是不夠的。

是以「Ming」在自動建立檢測任務時, 使用接口定義中的 JSON 傳回值自動生成了一份 JSON Schema, 用于比較這份 JSON Schema 是否落後于線上接口。

權衡利弊

其實「文檔同步自動檢測」和「可用性測試」并不沖突。

無論哪種接口定義方式,「JSON Schema」還是「OpenAPI」, 或者是「Ming」的「接口傳回值即定義」, 都可以實作這兩種測試。

由于「Ming」比較接口文檔是否同步時使用的 JSON Schema 是自動生成的( 雖然可以通過 JSON 中的「注釋」輔助生成更準确的 JSON Schema, 但編寫注釋不是強制的),

是以它的準确性會有所下降。

Mock 接口的不同狀态

實際情況中, 某些接口的傳回值是十分複雜的, 會根據請求參數的不同, 或是資料庫中資料目前狀态的不同, 傳回不同的響應體。

為了滿足這個需求, 大體上有兩種方案:

  • 給接口增加條件語句的功能, 通過條件語句或 DSL 設定在不同情況下的傳回值
  • 将接口管理平台的資料拷貝多份, 針對每一份拷貝運作一個沙盒服務, 修改沙盒中的接口定義使其傳回不同的資料

實作條件語句

很多接口管理平台都實作了這樣的功能, 為 Mock 接口編寫條件分支邏輯

舉例如下:

if(cookie._type === 'error'){
    mockJson.errcode = 400;
}

if(cookie._type === 'empty'){
    mockJson.data.list = [];
}           

像這樣直接寫 js 代碼使得 Mock 接口傳回不同的響應體, 狀态碼等。

這樣的功能的确很強大, Mock 接口有了像真實接口一樣的動态邏輯, 然而, 随之而來的事情不會像我們想象的那麼簡單。

使用學習成本過高

首先是文法學習的困難, 至少要學會 js 文法。

另外上述代碼中使用了一些不知從何而來的變量和字段, 如 cookie, cookie._type, mockJson, mockJson.data。

想必平台需要提供一份編寫代碼的文檔, 列出所有可以使用的變量及其詳細結構, 所有可以使用的功能函數, 所有字段代表的意義。

使用調試成本失控

代碼是需要調試的, 沒有了本地 IDE, 代碼運作的過程開發完全看不到。

是以, 一旦代碼中出了錯, 調試就變得極為困難, 使用者不明白代碼錯在哪。

畢竟, 我們是在部分地實作真實接口的邏輯, 出錯是難免的。

這會使得這項功能難以使用。

維護成本失控

代碼組合是無窮的,

實際情況中使用者寫出任何代碼都不用奇怪, 為了響應使用者的 oncall 可能會付出很大的人力消耗。

需求是無窮的。

雖然是部分地實作真實接口的邏輯, 但這不代表需求會很簡單, 為了滿足使用者通過條件語句實作接口各種狀态的需求, 需要支援的函數方法和變量會越來越多, 無窮無盡,

這也将帶來非常大的維護成本。

沙盒化平台

使用者可以随時啟動一個臨時的「Ming」沙盒服務, 在沙盒中修改接口傳回值, 狀态碼将不會影響「Ming 」主服務的資料。

一般需要用到這個功能的人是前端和用戶端, 他們把沙盒中對應的接口位址在代碼中寫好, 然後修改沙盒中相應接口, 就可以調試同一接口的不同狀态了。

「Ming」所采用的正是這樣的方案, 上面所提到的學習成本, 使用成本, 維護成本的問題都不存在。

權衡利弊

「條件語句」方案的優點是讓 Mock 接口有能力實作真實接口的部分動态邏輯, 在某些情況下會比較便利,

但缺點是帶來了極大的「學習成本」「使用成本」「維護成本」。

「Ming」所采取的「沙盒化」方案的優點正是避免了上述三個方面的成本,

缺點是遇到少數實在是需要 Mock 接口具備動态邏輯的場景時不夠靈活。

系統實作

最小原型

前端使用背景項目統一開發架構及元件庫快速搭建。

後端使用 Node.js 做接口服務, 利用 Redis 持久化存儲做資料庫。

Node.js 服務從 Redis 擷取所有接口資料, 加載到記憶體中, 通過比對路由傳回對應的 Mock 接口資料。

但這種每次響應 Mock 請求都把所有資料加載到記憶體中的方式性能很差,

是以我們增加了緩存優化, 每隔 10s 重新從 Redis 加載所有 Mock 接口資料到記憶體中, 接口響應時使用緩存中的資料, 接口響應速度大大提升。

相關截圖

輕量級 Mock 接口及文檔管理平台 - Ming

項目清單

輕量級 Mock 接口及文檔管理平台 - Ming

文檔中心

輕量級 Mock 接口及文檔管理平台 - Ming

接口文檔

前端項目內建

後端開發人員開始使用接口平台後, 前端隻需把 Mock 位址寫進項目代碼中就能使用 Mock 接口進行開發了。

但這樣不夠安全, 前端有極大的把 Mock 接口位址誤上線的風險。

是以我們想到接口管理平台可以和前端項目內建, 線上上接口和 Mock 接口之間建立自動映射。

類似 Nginx 反代理路由比對規則, 我們以「路由長度更長」和「路由中存在參數」為高優先級排序規則, 對所有 Mock 接口進行排序, 逐個比對線上位址, 傳回相應 Mock 接口。

前端項目本地開發則根據環境變量自動切換發請求時的域名, 這樣一來, 前端開發時便可放心地使用線上接口的位址。

接口校驗

「Ming」在自動建立檢測任務時, 使用接口定義中的 JSON 傳回值自動生成一份 JSON Schema, 用于比較這份 JSON Schema 是否落後于線上接口。

雖然 JSON Schema 是通過接口傳回值自動生成的, 但使用者可以在 JSON 代碼中添加「格式注解」輔助 JSON Schema 的生成。

「格式注解」舉例如下:

{
  "data": [
    {
      "name": "劉看山" // @optional
    }
  ]
}           

這裡的 @optional 就是表示這個字段是可選的, 這行注釋會影響最終生成的 JSON Schema 結構。

定時發送接口

通過消息隊列與定時器服務實作

輕量級 Mock 接口及文檔管理平台 - Ming

定時校驗消息隊列

輕量級 Mock 接口及文檔管理平台 - Ming

校驗任務狀态機

校驗失敗後給出修改建議

校驗失敗可能是接口本身錯了, 也可能是自動生成的 JSON Schema 有誤。

如果接口本身錯了, 那麼說明線上接口有誤, 提醒相關負責人檢視失敗原因, 關注接口近期變動。

如果 JSON Schema 錯了, 則說明可能需要「格式注解」輔助調整自動生成 JSON Schema 的結果。

所謂的給出修改建議就是提示出可能有效的「格式注解」。

相關截圖

輕量級 Mock 接口及文檔管理平台 - Ming

接口校驗失敗時的通知

沙盒化

啟動沙盒服務

沙盒服務與主服務的建構部署流程基本相同, 如有差別可使用不同的建構腳本加以區分。

沙盒服務與主服務的資料隔離

由于資料存儲于 Redis, 可通過在 Redis Key 前添加不同的字首來與主服務資料加以區分。

資料拷貝時所進行的複制并覆寫操作比較危險, 是以需對這部分邏輯增加單元測試和運作時校驗。

總結

本文介紹了知乎内部使用的 Mock 接口與文檔管理平台 Ming , 它的特點在于輕量級, 關注使用效率和維護成本, 避免引入過于複雜的産品邏輯。

比起業界一些開源平台, 「Ming」的功能還不夠完善,

在「類似 postman 的接口測試」「可用性測試」「壓力測試」「與後端接口開發的深度內建」「公共業務 JSON 結構的引用」「簡單動态邏輯支援」 等方面還有很多拓展的空間。

作者:馬良良良君

出處:https://zhuanlan.zhihu.com/p/100629469

繼續閱讀