使用 svrx 實現更優雅的接口 Mock

導言

目前 Web 開發廣泛都切換到了先後端分離的開發模式。雖然在工程和職能上已經分離了,但在實際工做中,先後端在開發進度上每每會出現不一致的狀況,此時就會極大地影響開發效率。 接口 mock 在此時就發揮出了巨大價值,它磨平了這個時間差,最終實現高效的先後端分離開發。javascript

具體到接口 mock 方案就多種多樣了,但大致不外乎 「硬編碼」 、 「前端攔截」和「後端攔截」這三種。 本文會嘗試簡單分析這三種常見方案的優劣,而後引出主要議題:基於 svrx 的接口 mock 方案。html

硬編碼方案

硬編碼即在前端代碼中直接寫入 mock 數據,好比:前端

function getUser(id) {
  return { username: 'mock username', id: 100 }; //接口mock
  return ajax('/user', { id });
}
複製代碼

提交時移除或註釋掉便可:java

function getUser(id) {
  // return {username: 'mock username', id: 100}
  return ajax(`/user/${id}`);
}
複製代碼

後端硬編碼的 mock 方式亦是如此,不過它的侵入性保留在了後端邏輯中,前端的業務代碼能夠保持乾淨:node

router.get('/user/:id', async ctx => {
  ctx.body = { username: 'mock username', id: 100 };
  // ctx.body = await userService.get(ctx.params.id);
});
複製代碼

注:上述範例基於 Koa 框架webpack

硬編碼的優勢git

  • 簡單靈活,不須要任何工具和框架支持,就地使用。
  • 若是是前端硬編碼,支持修改生效,不須要重啓 server。

硬編碼缺點github

  • 接口 mock 和業務代碼耦合,挖坑一時爽,填坑火葬場

這種騷操做估計不少人年輕時都幹過,提交時忘記刪除致使夾帶私貨上線的車禍現場歷歷在目。 不管是否用一些專業的 mock 框架(好比 mock.js ),這種在業務邏輯中耦合的方式顯然是下下策,線上事故通報中可能每每所以就有了你的名字。web

稍嚴謹的同窗可能會配合構建工具(如 webpack )來實現本地 mock 代碼和業務代碼的隔離,但並未在本質上解決這種耦合關係,隨着項目的迭代,項目一樣也會變得難以維護。ajax

更好的作法實際上是將 mock 邏輯與業務邏輯徹底解耦,並放到獨立的切面中管理, 這樣就能夠避免將非業務代碼提交到倉庫。

這種切面分爲前端攔截和後端攔截兩種方式,以下圖所示,數據響應直接在對應的切面中被攔截返回:

前端攔截

前端攔截即在請求真正發送前作的攔截返回,這種切面一般能夠經過 「Webview 容器定製」 和 「瀏覽器插件」 兩種方式來實現。

Webview 容器定製

Webview 容器定製通常能夠經過「網絡攔截」和「腳本注入」兩種方式,這也是通常混合應用中前端和 Native 交互的主要方式。

網絡攔截

網絡攔截常常會用在相似離線包的功能場景中,配合 mock 管理工具固然也能夠用來接口模擬。 參考 Android,通常會使用下面的方法進行攔截來替換響應

public WebResourceResponse shouldInterceptRequest(final WebView view, final String urlstr) 複製代碼

此內容不是本文主要議題,再也不深刻展開

腳本注入

Android 和 iOS 都有能力向 Webview 直接注入 JS 邏輯,這也是 Hybrid 應用中 Bridge 通訊層的實現方式。

若是在注入腳本中經過魔改 fetch 或 XMLHttpRequest 等原生對象,就能夠達到對響應的攔截改寫。

iOS 關鍵 API 舉例

[self.webView stringByEvaluatingJavaScriptFromString:injectjs];
複製代碼

Android 關鍵代碼片斷

webView.loadUrl("javascript:" + injectjs);
複製代碼

但不管是網絡攔截仍是腳本注入,基於 Webview 容器的攔截不多會用在真實場景中,由於定製和使用成本都過高,並且只在本 App 中能夠被使用。

瀏覽器插件

相較於定製 Webview 容器,瀏覽器插件顯然是一個成本更低的前端容器劫持方案。 以 code-mancers/interceptor 這個項目爲例:

經過 Interceptor 插件,能夠很容易以 GUI 的方式配置咱們的 mock 數據,簡單直觀,且徹底不侵入工程代碼。

前端攔截分析

前端攔截有個兩個自然優點:

  • 可提供配置界面:因爲是在瀏覽器端攔截,可以使用 DOM API 提供例如 Interceptor 插件的可配置界面。
  • 就地生效:修改後無需重啓服務。

但不管是瀏覽器插件仍是定製 Webview 容器,實際上咱們都忽略了一個重要事實:瀏覽器環境實際上是多種多樣的。 這致使了前端攔截的一個典型缺陷:沒法跨瀏覽器使用,如上例的Intercepror插件就沒法在微信瀏覽器中使用。

若是是經過服務端攔截的話就能夠避免這種狀況。

服務端攔截方案

服務端攔截實現接口 mock,主要經過一個單獨的 dev server 層來實現,它通常在訪問真實接口前攔截請求並返回模擬數據。

裸奔的 dev server

方便起見,以 Koa 爲例,裸奔一個 dev server:

const proxy = require('koa-proxy');
const Koa = require('koa');

const app = new Koa();
app.use((ctx, next) => {
  switch (ctx.path) {
    case '/api/blog':
      ctx.body = { type: 'blog' };
      break;
    case '/api/user':
      ctx.body = { type: 'user' };
      break;
    default:
      return next();
  }
});
app.use(
  proxy({
    host: 'http://api.yoursite.com'
  })
);

app.listen(8000, () => {
  console.log(`server start at http://localhost:8000`);
});
複製代碼

如上例所見, 默認會將接口代理到 api.yoursite.com(你的目標 API 或後端基友的服務器)。 mock 數據的優先級大於真實的代理接口,好比咱們訪問https://localhost:8000/api/user,返回的就是咱們的 mock 數據,後續若是須要增長 mock 接口,則須要不斷添加 case 分支。

這種裸奔的方式很不直觀,由於它將 mock 規則和其餘 dev server 的配置邏輯雜糅了,且對於非 Node 選手有較高的學習成本。

專業的 dev server

因爲裸奔 server 的明顯痛點,一些聚焦於 dev server 領域的解決方案就開始大行其道,好比開發者耳熟能詳的 webpack-dev-server

它集成了一些通用服務配置,例如端口、host、代理等等,而且設計爲被集成在 webpack 的構建流程中以實現構建產物的 serve。 這樣咱們就能夠將 mock 邏輯比較獨立的嵌入其中,如下述 webpack 配置爲例:

module.exports = {
  //...
  devServer: {
    port: 9000,
    headers: {
      'X-Custom-Foo': 'bar'
    },
    proxy: {
      '/api': 'http://localhost:3000'
    },
    before(app) {
      // 配置mock邏輯
      app.get('/api/blog', function(req, res) {
        res.json({ custom: 'response' });
      });
    }
  }
};
複製代碼

(專業的 dev server 用預設的配置代替了手工的代碼邏輯,顯著提升了開發效率)

但不管是裸起仍是使用專業的 dev server,本質上仍是存在如下問題:

  • 不支持熱重載: 每次修改 mock 規則,都須要從新啓動服務器。
  • 不直觀: mock 規則和其餘 server 配置雜糅,且對於非 Node 選手有較高的學習成本。
  • 沒法提供界面支持,相較於前端攔截, 它沒法提供 GUI 的界面配置能力。

使用 svrx 實現高效的接口 mock

從以上分析能夠得出:前端攔截與後端攔截,都存在一些本質缺陷。 那是否有一種方式是同時擁有先後端接口 mock 的優點呢?答案就是 svrx

廣告高能預警,看到這一步了,相信你已是 svrx 的潛在客戶了

svrx 簡介

svrx(音:Server-X) 是一個微內核架構、插件化的前端開發服務器,內部功能模塊主要包含三個部分:

  • 前端注入模塊: svrx 劫持全部 html 響應注入種子腳本,此腳本會集成管理所註冊的前端資源(JS、CSS)。
  • 後端注入模塊: svrx 內置一個帶有優先級的中間件註冊模塊。
  • 先後端通訊模塊: 實現前端與後端注入的通訊方式統一(基於 websocket),能夠以同構的方式完成事件或消息通訊。

如上圖所示,經過清晰的模塊劃分,插件能夠以統一的方式來完成插件註冊,靈活使用前端和後端注入功能。

svrx 也抽離了 dev-server 的通用功能,做爲內置插件集成(包括 livereload、proxy、https 等等),其餘專有領域的功能(如 markdown、qrcode 等)則之外部插件的方式提供,最大化實現便捷和靈活的平衡。

其中細分到接口 mock 領域,目前也有一系列開箱即用的配套知足開發者的需求。讓咱們來試一試吧!

安裝

npm install @svrx/cli -g
複製代碼

注: 後續全部插件能力都不須要再顯式安裝了

使用

切換到你的工做目錄並運行svrx,你會發現一個通用的 dev-server 已經運行起來了。

svrx
複製代碼

svrx Routing DSL 實現接口 mock

具體到接口 mock 的需求,咱們能夠直接使用內置的動態路由功能

touch route.js
svrx --route route.js
複製代碼

以上就是成功啓動的界面, 在route.js加入如下代碼:

get('/api/user/:id').to.json({ name: 'svrx' });
複製代碼

瀏覽器打開/api/user/1,能夠看到對應的 JSON 響應。全部在route.js的改動都是支持 hot reload 的,咱們無需重啓服務器。

更多 svrx Routing DSL 的使用指南請點擊這裏

若是你使用 svrx 路由來代替上面的其餘 dev-server,除了路由寫法更直觀高效外,還有一個做用就是能夠更細粒度地管理路由的優先級,好比 mock 和 proxy 的優先級:

get('/api/user/:id').to.json({ name: 'svrx' });
post('/api/blog(.*)').to.proxy('http://path.to.api.com');
get('/(.*)').to.send('404 PAGE IS NOT FOUND');
複製代碼

注:路由規則越前置,優先級越高

使用 mock 插件來快速模擬接口

直接裸用 svrx 路由能解決 mock 的功能性問題,但沒法解決 mock 的效率問題。

基於此,svrx 官方提供了svrx-plugin-mock, 它內置了好用的 mock.js ,幫助咱們實現快速數據模擬:

svrx --mock --route route.js
複製代碼

直接使用 -p mock 或簡寫--mock 來激活這個插件。

如上圖紅框所示,svrx 的插件體系有首次即安裝的特性,被安裝插件會自動進入 svrx 全局管理,後續激活插件無需重複下載,更重要的是不會污染你的工做目錄(包括node_modules)。

route.js中加入如下代碼:

get('/api/user/:id').to.mock({
  name: '@name',
  email: '@email'
});
複製代碼

mock 插件註冊了一個名爲 mock 的路由 Action,可在 Routing DSL 中被使用

再次訪問/api/user/1,你會獲得如下知足必定模式的隨機響應,好比:

{
  "user": "Linda Thomas",
  "email": "g.ykyiexto@toaloso.cc"
}
複製代碼

除此以外,mock 插件也能快速模擬一些列表循環的邏輯, 好比:

get('/api/user/:id').to.mock({
  name: '@name',
  email: '@email',
  'region|1-3': ['@region']
});
複製代碼

對應的響應中region將會是一個長度是 1 到 3 的地區數組,好比:

{
  "name": "Nancy Allen",
  "email": "aopao@qpo.scm",
  "region": ["西北", "華中"]
}
複製代碼

能夠看到使用 mock 插件能夠大大提升咱們的 mock 效率,而且閱讀仍然很直觀。

使用 json-server 建立基於必定規則的批量接口

svrx 的 mock 插件加上內置的動態路由功能基本上能高效的處理 90% 的本地 mock 需求了。

但若是你的服務是基於 json-server 規範的,你也能夠利用 svrx-plugin-json-server 來快速實現海量接口,讓咱們一塊兒來試下吧。

首先在當前目錄建立以下內容的 db.json 文件:

{
  "posts": [{ "id": 1, "title": "json-server", "author": "typicode" }],
  "comments": [{ "id": 1, "body": "some comment", "postId": 1 }]
}
複製代碼

啓動 svrx 並激活 json-server 插件:

svrx -p json-server --route route.js
複製代碼

與 mock 相似,json-server 插件會註冊一個名爲 jsonServer路由 Action

route.js 加入如下配置:

route('/(.*)').to.jsonServer();
複製代碼

以上語句會將全部請求直接代理到內部的 json-server 模塊。

訪問 /posts, 將看到以下響應:

[
  {
    id: 1,
    title: 'json-server',
    author: 'typicode'
  }
];
複製代碼

值得一提的是,其實 json-server 內置了所有 crud 操做,以posts爲例:

POST /posts        => Create 即建立操做
UPDATE /posts/:id  => UPDATE 即更新操做
GET /posts/:id     => READ 即讀操做
GET /posts         => READ LIST 即列表讀操做
DELETE /posts/:id  => DELETE 即刪除操做
複製代碼

舉個栗子,當你發起一個建立請求(之前端 fetch 爲例):

fetch('/posts', {
  method: 'POST',
  body: JSON.stringify({ title: 'svrx', author: 'x-orpheus' }),
  headers: {
    'content-type': 'application/json'
  }
});
複製代碼

你再訪問 /posts 列表,會發現多了一條記錄,且這條記錄會同步持久化到db.json:

[
  {
    id: 1,
    title: 'json-server',
    author: 'typicode'
  },
  {
    title: 'svrx',
    author: 'x-orpheus',
    id: 2
  }
];
複製代碼

請求改寫

經過串連路由的 rewrite 指令,咱們能夠作到只引導部分流量到 json-server 服務,例如:

route('/api/(.*)')
  .rewrite('/{0}')
  .to.jsonServer(); // /api/posts => /posts
複製代碼

這樣只有/api開頭的請求會代理到 json-server,其餘請求能夠走其餘的 mock 邏輯。

使用接口管理平臺

以上全部的 mock 方式其實都有一個較大的問題,就是 mock 規則都是在本地的,咱們沒法共享配置。

而實際上較大的團隊都應該有 API 接口管理平臺來統一管理接口定義,在網易咱們使用NEI:接口管理平臺來管理 API(由雲音樂前端團隊維護,歡迎免費試用)。 通常這類平臺都有接口模擬功能,代理到這類平臺,咱們能夠輕鬆實現規範化的接口 mock:

搭配這種接口管理平臺,雲音樂團隊也封裝了 svrx-plugin-nei (即將開源)來實現代理到 NEI 平臺的數據模擬,以下圖所示:

基於接口管理平臺的接口模擬是與真實接口規範匹配的,因此先後端規範性會更一致,而且它的平臺屬性也方便開發者共享配置。 但這種方式也有巨大的劣勢,就是靈活度遠低於本地接口模擬

值得一提的是此插件利用 svrx 的前端注入能力實現了跨瀏覽器的前端配置界面, svrx 經過內部 injector 模塊自動爲響應是 html 類型的資源注入種子腳本,種子腳本會集成全部 plugin 註冊的腳本內容,從而實現了前端邏輯在 dev-server 側的注入。

經過 mock 解析 svrx 的核心價值

咱們能夠看到,以上全部特性在數據 Mock 領域都是功能互補的,沒有所謂的萬金油方案。

因此 svrx 帶給咱們其實並非 svrx-plugin-mocksvrx-plugin-json-server亦或是svrx-plugin-nei 等等這些隔離的單一功能, 而是基於 svrx 這個平臺,咱們能夠很容易的將這些圍繞在dev-server領域的功能以一種統一的方式集成起來使用,避免重複的安裝和配置工做

舉個栗子 🌰,當開發者但願 JSON 響應的格式輸出更好看時,能夠直接使用-p json-viewer來激活對應插件:

svrx --route router.js \
  -p json-viewer \
  -p json-server \
  -p mock
複製代碼

響應視圖馬上從下面的無序純文本:

無縫切換爲直觀的下圖:

再舉個栗子 🌰,當咱們想將咱們的本地服務暴露到外網使用時,可使用 -p localtunnel 激活 localtunnel 的反向隧道網關服務。

svrx --route route.js \
  -p json-viewer \
  -p json-server \
  -p mock \
  -p "localtunnel?host=https://tunnel.svrx.io"
複製代碼
  • 參數過長時可使用 svrx 配置文件
  • tunnel.svrx.io 是屬於福利性設施,不確保穩定性,請你們悠着點使用以免服務由於各類緣由不可用。

上圖相似 fast-dragon-86.tunnel.svrx.io 的隨機地址便可用於外網訪問的域名了,這種即開即走的使用體驗是碎片化的各類 dev server 平臺沒法提供給你的。

更重要的是,接口 mock 其實僅僅只是咱們平常開發中的一環,svrx 的定位是一個通用開發服務器,它內置集成了serveproxylivereloadroute等等平常前端開發中必不可少的功能, 而且能夠經過社區不斷增長的插件池來進行自由組合使用,這個咱們從上述接口 mock 這一場景的描述中應該已經看到。

徹底能夠這麼說,圍繞 dev-server 的設施越多,svrx 存在的價值就越大

寫在最後

除了徹底不推薦的「硬編碼方案」以外,作到與業務代碼解耦的「純前端攔截」和「純後端攔截」的接口 mock 方案也都存在一些沒法規避的本質性問題。

而使用 svrx 以及它配套的社區插件,咱們除了能夠整合前端和後端攔截的優點,還能夠將各類 mock 功能集成在一個服務中運行,解決了工具的碎片化問題,從而高效的實現接口 mock 需求。

Links

本文發佈自 網易雲音樂前端團隊,可自由轉載,轉載請在標題標明轉載並在顯著位置保留出處。咱們一直在招人,若是你剛好準備換工做,又剛好喜歡雲音樂,那就 加入咱們

相關文章
相關標籤/搜索