iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 6
0

今天主題雖然是 Express,但我的重點其實是想放在 Express 的核心 Middleware 這個東西上面,因為它對程式的執行流程有很重大的影響,希望各位讀者看完本篇後,未來寫 Code 時都能好好善用 Middleware。

Middleware

Middleware 的中文通常翻譯作「中間件」,不過我個人認為翻得不好,從中文字面無法領悟到背後的意義。假設我們有一個程式的執行流程是 A → B → C → D,進入點是 A,結束點是 D,而且後面執行的程式需要依賴前面執行的結果,在這個例子中,A、B、C 都可以算是 Middleware。

上面例子其實很抽象,我們轉換到 Web 的思維,當一個 Request 進到 Server,我們要做的事情可能依序包括:

  1. 檢查使用者是否已經登入
  2. 檢查使用者是否具有足夠權限瀏覽目前頁面
  3. 解析 Header,取出 locale
  4. 解析 Payload,取出使用者填寫的表單資料
  5. 檢查 Recaptcha 值是否正確
  6. 驗證表單資料
  7. 將表單資料轉存資料庫,Server 回傳 Response

而這些流程還夾帶著後者依賴前者的關係:登入過的使用者才需要檢查權限,權限許可的使用者才能進行後續操作,解析 Payload 之後才能知道 Recaptcha 欄位填寫的值,才能驗證 Recaptcha,確認不是機器人後,再繼續往下驗證表單資料是否都符合格式,當前面的每一步都過關之後,才允許真正地將資料寫入資料庫。

每一個路由都要對應不同的執行流程,不同的路由又可能會有重複或雷同的執行流程,如果你對於實作這樣複雜繁瑣的 Server 程式已經感到頭暈目眩,請不要害怕!Middleware 就是為了這樣的情境而生的。你可以將上述的 7 個步驟拆成 7 個不同的 Middleware 模組,在不同路由中只需要載入不同的 Middleware 模組就可以達到複用的效果,同時又能輕鬆管理錯縱的執行流程。

Express Middleware

直接舉一個 Boilerplate 中實際使用的例子:

app.get(
  '/api/users',
  authRequired,
  roleRequired([Roles.ADMIN]),
  userController.list
);

當使用者想要存取 /api/users 這個資源時,先經過 authRequired 這個 Middleware 檢查使用者是否已經登入,再經由 roleRequired 檢查使用者是否具有 Admin 權限,當前面兩個 Middlewares 都通過了,再透過 userController 來回傳結果給 Client 端。

Express 的 Middleware 是一個長這樣的 Function:

let authRequired = (req, res, next) => {
  if (!req.isAuth) {
    return res.json({
      error: '使用者未登入',
    });
  } else {
    return next();
  }
};

reqres 物件是 Express 處理 Request 和 Response 的機制,各位應該不陌生,而 next 是一個 Callback Function,也是掌控 Middleware 流程的核心,當我們呼叫 next 時可以進到下一個 Middleware。所以上面的例子中,當使用者還沒登入時,直接送回 Reject 訊息;當使用者是登入狀態時,我們呼叫 next 進入下一個 Middleware,繼續執行後續程式。這樣的寫法是不是很漂亮呢?

Boilerplate 中的 Middleware

讀者們如果有興趣可以翻翻 Source Code 中的 /src/server/middlewares 資料夾,我們提供了大量的 Middlewares,有的是一次性的(如:initCookie),有的是按照 Request 需求選擇是否要使用(如:bodyParser、authRequired、roleRequired),有的則是對所有 Request 都使用(如:morgan)。

隨處可見的 Middleware

Middleware 的概念其實不只用於 Express 中,例如 Python 的著名架站框架 Django 就是以 Decorator 的形式來管理 Middleware,下例中的 csrf_exemptlogin_required 就是 Middleware:

@csrf_exempt
@login_required
def apiWhiteListRenew(request):
    if request.method == 'POST':
        # ...

另一個例子是 Redux,在 Dispatch 每一個 Action 時,會先經過預先設定好的每一個 Redux Middleware,我就點到為止吧,再講下去就離題太遠了,另外,Redux 是狀態管理(State Management)的 Library,在後面的章節裡會再次提到。

犧牲效率,換取維護性與彈性

在程式語言中,便利通常是犧牲效率換來的,Middleware 也是如此。在 Express 依序進入 Middlewares 時,會產生內部管理必要的 Overhead,在一些探討效率的文章中通常會建議少用 Middleware,但筆者認為,Middleware 帶來的便利也是同等重要,一來程式碼有高度複用性,二來容易維護。若要在效率和便利之間取捨,我還是會選擇便利。


上一篇
Day 05 - Backend - Technique Stack
下一篇
Day 07 - Backend - MVC
系列文
30 天打造 MERN Stack Boilerplate30

尚未有邦友留言

立即登入留言