淺談阿里 Node 框架 Midway 在企業產品中的應用實踐
什麼是 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 機制,優化了開發體驗。