什麼是 Midway
Midway(中途島)品牌是淘寶技術部(前淘寶 UED)前端部門研發的一款基于 Node.js 的全棧開發解決方案。它将搭配團隊的其他産品,Pandora 和 Sandbox,将 Node.js 的開發體驗朝着全新的場景發展,讓使用者在開發過程中享受到前所未有的愉悅感。
Midway 基于 阿裡 Egg.js 架構二開,将 IoC 引入到架構中,借鑒 Nest.js,引入豐富的裝飾器方法,提升開發中的使用者體驗。
midway 的一些特性如下。
依賴注入( IoC )
首先想說說 控制反轉(Inversion of Control,縮寫為IoC),是面向對象程式設計中的一種設計原則,可以用來減低計算機代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱 DI),還有一種方式叫“依賴查找”(Dependency Lookup)。
通過控制反轉,對象在被建立的時候,由一個調控系統内所有對象的外界實體将其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中。通俗地來說,有點像上京東或者淘寶購買商品,你隻需要在搜尋框中輸入你要購買的商品,可以選擇它的分類、品牌、價格等參數,然後軟體就會給你提供你心儀的商品,你隻需要下單、購買、等着收貨即可。簡單明了,如果軟體給你推薦的結果,你并不滿意,我們就會抛出異常,整個過程無需你自己控制,而是電商平台類似容器的結構來控制。
所有的商品都會在電商平台中注冊,你隻需要告訴 Ioc 你是什麼東西,你要買什麼東西,然後 Ioc 就會在系統運作到你需要的時候,将你要的東西傳遞給你。同時也将你售賣的東西,傳遞給其他需要的地方,所有商品的建立、配送、銷毀,全部由 IoC 控制。這就是控制反轉。

Midway 架構采用 injection 這個 Npm 包做 IoC 控制,這個包本身也是淘寶中途島團隊自主開發的,實作了依賴注入。
基于 Egg.js
Midway 是基于阿裡開源的另一款 Node 架構 Egg.js 為基礎開發的。
Egg.js 号稱為企業級架構和應用而生,Egg 采用“約定優于配置”,規劃了一套統一的約定,進行開發,我司現有産品采用 Egg.js 進行開發疊代,Egg.js 限制了一套目錄規範和開發規範和插件規範,減少因為人的差異導緻項目規範混亂,Egg.js 有很高的可擴充性,使用 Egg.js 提供的 loader 機制可以讓架構根據開發者自己的規劃,定義預設配置,亦可覆寫 Egg.js 的預設配置。
因為 Egg.js 的這種高度可擴充性,給開發者提供了基于 Egg.js 封裝上層架構的能力,并且 Egg.js 插件高度可擴充,并且現有生态較為完備,并且相容 koa 的插件,生态環境非常優異。Egg.js 通過 Node 官方的 Cluster 子產品,實作多程序模型,充分利用多核心 CPU 性能。另外 Egg.js 在淘寶雙十一,和阿裡絕大部分的 Web 系統中的表現,充分的說明了,Egg.js 優異的穩定性。
個人覺得 Midway 選擇 Egg.js 的原因有以下幾點:
- 底層基于 koa,提過封裝上層架構能力;
- 雙十一的并發量都可以支撐住(不要給我說,你們平台的流量能夠比淘寶天貓雙十一還要高);
- 相容 koa 插件,Egg.js 插件也有幾百個,一些使用率很高的架構由官方維護,例如,egg-mongoose、egg-sequelize 等等;
- 阿裡維護的項目,并且阿裡自身也在深度使用;
- 問題回答的效率,首先是國内的架構,你可以用中文送出 issue,另外就是回複效率很高(本人提過幾個 issue,基本上一小時内就回複了,不知道是不是我問題簡單的原因)。
采用 Typescript
TypeScript是一種由微軟開發的自由和開源的程式設計語言。它是 JavaScript 的一個超集,而且本質上向這個語言添加了可選的靜态類型和基于類的面向對象程式設計。安德斯·海爾斯伯格,C# 的首席架構師,已工作于 TypeScript 的開發。
- TypeScript 擴充了 JavaScript 的文法,是以任何現有的JavaScript 程式可以不加改變的在 TypeScript 下工作。
- TypeScript 是為大型應用之開發而設計,而編譯時它産生 JavaScript 以確定相容性。
- TypeScript 支援為已存在的 JavaScript 庫添加類型資訊的頭檔案,擴充了它對于流行的庫如 jQuery、MongoDB、Node.js 和 D3.js 的好處。并且我們經常使用的宇宙第一編輯器 Visual studio code 也是 Typescript 開發的。
- Typescript 可以使用 Javascript 中的所有代碼和概念,Typescript 是為了 JavaScript 開發更加容易而誕生的,Typescript 隻從語義核心方面對 JavaScript 原有模型進行擴充,是以 JavaScript 可以無需修改,或少量修改即可和 Typescript 同時工作,也可以使用編譯器将 Typescript 轉換為 JavaScript 代碼。
- Typescript 通過類型注解,提供編譯時的靜态檢查,并且為函數提供了預設參數,并且引入了子產品的概念,可以把聲明、資料、函數和類封裝在子產品中。使用 Typescript 相較于 JavaScript 有以下顯著優勢:
- 靜态檢查
- 大型項目的規範,使用 Typescript 更容易重構
- 協作能力
- 生産力,幹淨的 ES6 代碼,自動完成和動态輸入提高了開發者的工作效率
提供多種裝飾器
因為 Midway 使用 Typescript 開發,是以支援裝飾器方法。在 Java 開發中,裝飾器模式非常常見,通過裝飾器方法可以向一個現有的對象添加新的功能,并且不會改變對象的結構,裝飾器和被裝飾的對象可以獨立,不會互相耦合。
舉例說明,例如 Midway 提供了路由裝飾器,可以直接通過裝飾器方法聲明路由。代碼示例如下(代碼來源于官方示例):
import { provide, controller, inject, get } from 'midway';
@provide()
@controller('/user')
export class UserController {
@inject('userService')
service: IUserService;
@get('/:id')
async getUser(ctx): Promise<void> {
const id: number = ctx.params.id;
const user: IUserResult = await this.service.getUser({id});
ctx.body = {success: true, message: 'OK', data: user};
}
}
如上代碼使用
@controller聲明這個類為控制器類,同時通過标注請求,聲明了請求的方法,除了
@get以外,Midway 還基于 koa-router 封裝了
@post、
@del、
@put、
@patch、
@options、
@head、
@all,其他裝飾器方法的示例可以通過 Midway 官方文檔檢視,本文隻做簡要介紹,後續本人可能會寫一系列的 Midway 實戰教程,敬請期待。
淺談我司現有産品的技術痛點
我司基于 Egg.js 開發了一套通用 OA 産品(持續疊代中),在實際開發過程中,體驗還是不錯,但是也有一些開發體驗不好的地方,因為使用 JavaScript 是以自動補齊,智能提示基本上是無法使用,雖然官方或個人提供了插件但是使用起來,總是有些不盡如人意,另外我們的路由聲明、多檔案 多檔案夾,維護起來很麻煩:比如說我要寫一個新的接口,我需要先去 router 檔案夾裡面建立一份路由檔案,然後在 /app/router.js 檔案裡引用這個檔案。
然後再去控制器裡面建立檔案,編寫方法,好幾個檔案,來回切換。是以我覺得使用裝飾器方法聲明路由還是很友善的。最開始嘗試過使用第三方插件實作裝飾器注冊路由,但是體驗不是很好,後續就沒有繼續使用了。
另外就是 JavaScript 語言本身的痛點,代碼規範、接口聲明、類型效驗問題、多人同時開發,每個人的開發習慣都不相同,是以長期疊代維護的項目可能會因為每個人的習慣不同,可維護性逐漸降低,我們現在的私有化部署項目,很多低級 Bug 都是因為類型不對,資料結構不對等之類的引起。
如果真的要維護這個項目幾年,JavaScript 靈活度很高,是以項目前期可以使用 JavaScript 建構,以換取開發效率高,但是我覺得如果不進行規範限制,後期幾乎不可維護。從企業産品角度出發,因為 Typescript 面向對象程式設計語言的結構,保持了代碼的整潔度,代碼規範的一緻性,是以我個人覺得,在企業項目中使用 Typescript 更加适合。如果隻是小型項目或個人項目,JavaScript 更适合靈活開發,開發效率更高。不過一切技術的選擇都要從實際場景出發。任何不從實際場景出發的技術選型都是耍流氓。
使用 Midway 的意義
根據之前講的 Midway 的特性,我簡單總結了一下,使用 Midway 帶來的好處,有以下幾點。
- 使用 Midway 學習成本低,如果你之前使用過阿裡的 Egg.js 架構,基本上不需要怎麼學習,即可用于實際工作中
- 各種裝飾器方法提升開發效率
- 使用 Typescript 使用
- 使用 Ioc,優化項目依賴管理
- 底層基于 Egg.js 相容 Egg.js 的所有生态
- 采用 Typescript,強類型,面向接口程式設計
- 提供裝飾器方法,簡化開發
如何從 Egg 平穩遷移到 Midway
将項目從一個架構,遷移到另一個架構,并不是一件簡單的事,不過把 Egg 項目重構為 Midway 還算是沒有什麼特别的困難,首先因為 Midway 基于 Egg.js 是以之前項目中使用的 egg 插件或 koa 插件,可以無需修改,或者少量修改,即可在 Midway 中使用,另外因為 Midway 的很多方法與 Egg.js 保持一緻,是以大部分你在 Egg.js 中使用的方法,亦可在 Midway 中繼續使用,目錄結構這部分,與 Egg.js 大緻相同,不過 Midway 在其基礎上,重新對項目結構分層,将項目分為 Web 層和業務邏輯層:
├── README.md
├── README.zh-CN.md
├── dist ---- 編譯後目錄
├── logs ---- 本地日志目錄
│ └── midway6-test ---- 日志應用名開頭
│ ├── common-error.log ---- 錯誤日志
│ ├── midway-agent.log ---- agent 輸出的日志
│ ├── midway-core.log ---- 架構輸出的日志
│ ├── midway-web.log ---- koa 輸出的日志
│ └── midway6-test-web.log
├── package.json
├── src ---- 源碼目錄
│ ├── app ---- web 層目錄
│ │ ├── controller ---- web 層 controller 目錄
│ │ │ ├── home.ts
│ │ │ └── user.ts
│ │ ├── middleware (可選) ---- web 層中間件目錄
│ │ │ └── trace.ts
│ │ ├── public (可選) ---- web 層靜态檔案目錄,可以配置
│ │ ├── view (可選)
│ │ | └── home.tpl ---- web 層模闆
│ ├── config
│ │ ├── config.default.ts
│ │ ├── config.local.ts
│ │ ├── config.prod.ts
│ │ ├── config.unittest.ts
│ │ └── plugin.ts
│ └── lib ---- 業務邏輯層目錄,自由定義
│ │ └── service ---- 業務邏輯層,自由定義
│ │ └── user.ts
│ ├── interface.ts ---- 接口定義檔案,自由定義
│ ├── app.ts ---- 應用擴充檔案,可選
│ └── agent.ts ---- agent 擴充檔案,可選
├── test
│ └── app
│ └── controller
│ └── home.test.ts
├── tsconfig.json
└── tslint.json
另外就是在使用 Typescript 後,開發者需要編寫接口定義,和聲明檔案,不過我相信,大家學習一些 Typescript 知識以後,這些都不是問題。另外,是否需要重構項目也要從實際情況出發,應先實際調研,目前項目是否規範混亂,難以維護,另外就是重構的意義是否大于重構的成本。如果你的項目還沒有開始實施,或剛剛開始實施,如果你想使用 Typescript 作為産品語言,我覺得 Midway 可以作為你的架構選型之一,另外可能有些人會說,如果我都要寫 Typescript 了,為什麼我不用 Nest.js 呢?我覺得架構選型還是要從實際出發,你就直接使用,而是要看,這個架構的性能,穩定性,維護團隊,未來規劃等等方面出發,我選擇 Egg.js,選擇 Midway 首先是因為它的維護團隊是阿裡巴巴,性能穩定,另外就是有 IoC 機制,優化了開發體驗。