`Mkbug.js`的前世此生 - 一款`OOP`風格聲明式`Nodejs`框架

原文來自個人blog

Mkbug.js的前世此生 - 一款OOP風格聲明式Nodejs框架

早在 2014年的時候第一次接觸 Nodejs,當時還在 IBM,而註明的 Express.js框架也是 IBM負責維護。因而理所固然的是用 Express.js開發 Nodejs應用。雖然國內對 Koa很是熱衷,可是我仍是有 1000個理由選擇 Express.js。成熟可靠的維護團隊和企業級應用背景,即便在 2015IBM將維護 Express.js交給了 StrongeLoop

一切的開始都要從一個線上故障提及

當時我所在的團隊負責一個內部工具系統開發,後臺使用Nodejs。可是有一天用戶反饋有一個線上問題。數據錯了。咱們立刻進行迴歸,發現線下是沒問題的。找了好久也沒找到緣由,後來從代碼發現這個分支是由一個環境變量做爲條件進行的分支處理。經過上線跟蹤日誌,發現實際讀取的環境變量不正確。php

原來上一次升級開發人員忘記了修改生產環境環境變量的設置致使的。git

一般咱們會將系統的配置信息配到一個 Shell腳本中。而後注入到環境變量中。因爲這些環境變量是在 Docker內生效。所以並不會影響其它程序,也不會引發衝突和覆蓋,而且環境遷移能力強。是一種很是常見的手動。

爲何咱們不能像Java框架那樣,從配置文件中獲取呢?JavaPHP的框架都會有一個配置管理模塊去自動根據當前的模式獲取對應的配置信息,維護很是方便,很是智能。github

因而Mkbug.js第一個核心模塊誕生了 ---- Config。一個專門用來解決系統配置信息管理的模塊。通過多年的項目實踐和打磨,最終演變成了Mkbug.js的核心模塊。express

// 目錄結構
  ├── src 
      ├── controller 
          ├── ConfigTest.js
      ├── config
          ├── index.conf
          ├── index.dev.conf
  ├── index.js 

  // src/config/index.conf
  TITLE=Mkbug.js
  Content=A OOP style declare Nodejs Web framework base on Express.js

  // src/config/index.dev.conf
  TITLE=Mkbug.js DEV

  // src/controller/ConfigTest.js
  const { BaseController, Config } = require('mkbugjs');

  module.exports = class ConfigTest extends BaseController {
    getAction () {
      const conf = new Config('index')
      return conf
    }
  }

當咱們不設置process.env.NODE_ENV啓動的時候,使用curl請求接口返回的數據咱們發現,在特定環境下的配置信息會繼承沒有指定環境的配置信息。json

$ curl -XGET http://localhost:3001/api/configtest
  {"TITLE":"Mkbug.js","Content":"A OOP style declare Nodejs Web framework base on Express.js"}

而當咱們以process.env.NODE_ENV=dev啓動的時候,使用curl請求接口返回的數據咱們發現,在特定環境下的配置信息會繼承沒有指定環境的配置信息。api

$ curl -XGET http://localhost:3001/api/configtest
  {"TITLE":"Mkbug.js DEV","Content":"A OOP style declare Nodejs Web framework base on Express.js"}
Notice:當咱們沒有指定具體的運行環境的時候,Config會默認加載與初始化參數名相同的conf文件,好比index.conf。可是當指定具體的環境(也就是process.env.NODE_ENV=dev)後,將會用對應環境下的同名配置文件內容對默認配置內容進行覆蓋。好比本例中index.dev.conf的內容會覆蓋index.conf中相同的key對應的內容。

讓繁瑣的配置遠去 -- BaseController路由抽象接口

在離開IBM後加入互聯網公司一直從事Nodejs開發工做,互聯網項目高速迭代的特色讓Nodejs項目的路由龐大,複雜且難以維護。時間久了,哪些路由信息還在使用?哪些已通過時不用了?模塊是否和路由設置一致?一個上百接口服務的路由虐的我痛不欲生。有人會說,你多拆幾個文件就好啦。但是文件越多。管理起來也越繁瑣。瀏覽器

因而第二個模塊在2016年誕生了。也就是BaseController的雛形RouterMgt。能夠自動遍歷文件路徑,加載模塊,生成路由配置信息。本覺得一切都清淨了。可是看着Koa的兼容新ES6語法仍是內心癢癢。可是無奈與Nodejs 8.x暫時還不支持那麼多語法。而團隊內也更傾向於Koa支持新語法的誘惑。可是很快因爲公司業務發展不佳,公司解散了。服務器

再加入新公司後,Nodejs已經發展到了10.x。已經支持了99.7%的新語法。而我須要的功能也都支持了。因而BaseController完成了第二個版本。也就是如今
Mkbug.js核心模塊 ---- OOP風格聲明式路由管理模塊。閉包

// 目錄結構
  ├── src 
      ├── controller 
          ├── _params
              ├── _id.js
          ├── pathtest
              ├── HelloWorld.js
  ├── index.js 

  // src/controller/_params/_id.js
  const { BaseController } = require('mkbugjs');

  module.exports = class IdTest extends BaseController {
    getAction () {
      return 'Hello ! this message from IdTest';
    }
  }
  // src/controller/pathtest/HelloWorld.js
  const { BaseController } = require('mkbugjs');

  module.exports = class HelloWorld extends BaseController {
    // 爲了不與上面的接口路徑衝突,因此在末尾增長了test
    getTestAction () {
      return 'Hello! this message from pathtest/hellowrold!';
    }
  }

它並不像egg或者thinkjs以及其它一些Nodejs框架那樣須要本身手動管理不少配置信息。一切都會幫你配置好。你僅僅須要實現BaseController接口,那麼api的路由信息就都幫你生成了。而且支持路由參數。瀏覽器請求結果:app

$ curl -XGET http://localhost:3001/helloworld/idtest
  Hello ! this message from IdTest

  $ curl -XGET http://localhost:3001/pathtest/helloworld/test
  Hello! this message from pathtest/hellowrold!

是否是很是簡單便捷?不再須要那麼冗餘的配置文件了。工程變得更加簡潔。這個模塊早期版本的輕量級,門檻低,低成本,易於維護的特色,在18年落地了不少公司內部工具,更是在19年成功幫助前東家的新業務線簽下兩份訂單。同時也經受住了大數據量訪問的考驗。同時提供了攔截器接口,能夠獲取接口響應時長等很是重要的信息。

在經歷了3年多的打磨後,Mkbugjs也基於該模塊誕生了。

Notice:由於Controller的方法名是很是關鍵的配置信息,類的方法名必須以HTTP協議的Methods名開頭Action結尾,這樣纔會被識別爲路由信息。若是在MethodsAction之間沒有其它單詞,則沒有對應的路徑。就像上面的例子同樣。

Notice:http協議目前支持9個方法,固然,實際上不一樣瀏覽器還有更多的方法。可是爲了保持與標準同步,Mkbug.js支持9種方法,分別是:GET,HEAD,POST,PUT,DELETE,CONNECT,'OPTIONS,TRACE,PATCH

Expressjs尷尬的響應處理

使用過Express.js的開發者都知道,Express.js必須顯示調用end, json, writeapi去結束響應,不然客戶端會一直掛起,直至超時。而Mkbugjs提供了統一的響應返回機制,即只須要return,或者throw一個MkbugError異常對象,便可返回客戶端請求。也就避免了請求掛起。同時也統一了響應返回的標準。統一系統風格。

// src/controller/StatusTest.js
  const { BaseController, MkbugError } = require('mkbugjs');

  module.exports = class StatusTest extends BaseController {
    getTestAction () {
      throw new MkbugError(500, 'Error Test')
    }
  }

執行結果以下:

$ curl -w "  status=%{http_code}" localhost:3001/statustest/test
  Error Test  status=500
Notice: 在Mkbug.js中,任何實現BaseController的接口,和BasePlugin的中間件均可以經過這種方式自動響應客戶端請求。

下降中間件門檻 -- BasePlugin 中間件抽象接口

我帶過的不少人對JS並非很是深刻,以致於常常把閉包,執行上下文寫錯。致使中間件常常出問題。而BasePlugin只須要用戶實現本身的exec接口邏輯,便可自動完成中間件的配置和執行操做。並不須要開發者過多的參與底層配置。

BasePlugin提供一個exec接口,該接口主要用於實現中間件的業務邏輯,並提供了resreq接口。當咱們須要攔截請求,只須要拋出MkbugError異常便可。不然會執行下一步路由。

// 目錄結構
  ├── src 
      ├── controller 
          ├── MiddleWare.js
      ├── plugin
          ├── TestMiddleware.js
  ├── index.js 

  // src/plugin/TestMiddleware.js
  const { BasePlugin, MkbugError } = require('mkbugjs');

  module.exports = class TestMiddleware extends BasePlugin {
    exec (req, res) {
      if (req.query.test === '1') {
        throw new MkbugError(200, 'Reject from TestMiddleware')
      }
    }
  }

  // src/controller/MiddleWare.js
  const { BaseController } = require('mkbugjs');

  module.exports = class MiddleWare extends BaseController {
    getAction () {
      return 'Hello World'
    }
  }

這裏咱們建立了3箇中間件,其中2個對請求中query.test等於12進行了攔截,分別返回200狀態和401狀態。

咱們先測試第一個中間件:

$ curl -w " status=%{http_code}" localhost:3001/api/MiddleWare?test=1
  {"msg":"Reject from TestMiddleware1"} status=200

在這裏咱們能夠看到經過MkbugError自定義返回的http請求的status和內容。而若是在中間件中什麼也不作的話:

$ curl -w " status=%{http_code}" localhost:3001/api/MiddleWare
  Hello World status=200

能夠看到對應的路由接口數據被正常返回。

Notice:這裏須要注意的是MkbugError對象必須被throw出來,而不是return出來。

Mkbug.js的誕生

在2020年五一假期,由於疫情不能回家探親,一我的在居所無聊,忽然冒出一個想法,爲何不將這些有用的中間件組合成一個完整的框架呢?功能不亞於任何已有的Nodejs框架,並且風格更加新穎,也更接近於廣大JSer對ES6新語法的訴求。

因而花了五天時間,創造了Mkbug.js

// index.js
  const express = require('express');
  const app = express();

  const { Mkbug } = require('mkbugjs');

  new Mkbug(app)
    .create('/') // 請求url前綴
    .use(bodyParser.json()) // 使用express中間件
    .start(3001, (err) => { // 啓動,同app.listen
    if (!err)
      console.log('Server started!')
    else
      console.error('Server start failed!')
  })
  
  // src/controller/HelloWorld.js
  const { BaseController } = require('mkbugjs');

  module.exports = class HelloWorld extends BaseController {
    getAction () {
      return 'Hello World';
    }
  }
Notice:Mkbugjs提供了豐富的Web服務器經常使用的類。只須要繼承並實現對應的類,便可實現自動注入。就像Java流行的Spring Boot或者PHPThinkphp同樣。很是簡單。

最後

我寫了這麼多你還沒心動嗎?目前安裝量已經超過1400次/月。並完成了全部case的測試用例。還不來嘗試一下嗎?

Github
Document

相關文章
相關標籤/搜索