1. 程式人生 > >midway v1.0 社群正式釋出 - 阿里面向未來的全棧開發方案

midway v1.0 社群正式釋出 - 阿里面向未來的全棧開發方案

main

雙旦已過,新年將至,midwayJs 向大家獻上賀禮。

之前我們向社群開放了我們的治理工具,也就是 Pandora.js 工具包,用於整個 Node.js 應用的監控和治理,我們承諾這不是結束,只是開源的開始。

隨著內部全棧應用數的越來越多,以及阿里業務不斷提升的複雜度,比如店鋪,搭建以及渲染等服務,隨著人員的不斷調動,產品的結構,程式碼的層級都隨著不斷的調整,我們急需一個能降低程式碼複雜度的解決方案,幫助我們渡過人員寒冬,這就對我們內部的基礎架構體系提出了不同的要求。

以往我們只需要讓使用者啟動伺服器,滿足 RPC/HTTP 服務即可,而在真正的全棧領域,似乎沒有太多的鑽研和沉澱。對此,我們將內部使用的 midway 整體解決方案進行了一次重塑,並且在設計之初就提出未來將對外進行開源。

正巧我們的第一款 Typescript 產品 Pandora.js 開源完畢,給了我們將程式碼用 Typescript 重寫的信心,也隨著 Egg.js 社群的壯大,我們相信,在不同的領域中,一定會有不同的產品,不同的解決方案。

Midway 正式基於這些考慮,將 IoC 引入到了框架中,同時學習了 NestJs ,引入了不少自定義的裝飾器,增強開發體驗,也將搭配團隊的其他產品,Pandora.js 和 Sandbox,將 Node.js 的開發體驗朝著全新的場景發展,讓使用者在開發過程中享受到前所未有的愉悅感。

在這裡感謝前期的 beta 測試中向我們提意見以及試用的同學,感謝大家的包容和支援,特別是 @ZQun 和 @yuu2lee4 兩位的積極參與。

image-20190109121917392

下面來介紹新版本 midway 的一些特性。

  • 基於 IoC 體系業務程式碼進行解耦,依賴統一管理統一初始化
  • 常見的 web 場景裝飾器簡化業務開發
  • 支援 Egg.js 的所有外掛體系,框架裝飾器統一編碼風格
  • 基於 Typescript ,面向介面程式設計的編碼體驗

依賴注入疑問

在一年前,我們的業務程式碼是重重耦合,到處初始化,例項重複,但這並不是業務同學在程式碼架構方面的問題,而是在不斷的業務迭代,交接下,早就脫離了最初的設想,程式碼的設計跟不上需求的速度。

為此,我們嘗試引入了依賴注入的方案。依賴注入最早聽到是在 Java 端的 spring 框架,在 JS 方面,最早我們使用了 XML 做為基礎的 IoC 方案,雖然解決了不少耦合和初始化的問題,也發現前端在 XML 的感受吐槽頗多。

去年 Typescript 的大力發展之後,內部的很多專案都切換了過來,經過我們的調研,除了 NestJs 進行了自研以及在 Typescript 領域比較出名的 Inversify 模組,似乎很少有現成的易於擴充套件的模組。

基於這些情況,我們進行了這方面的自建,一方面方便內部的擴充套件,能更好的在現有的體系上擴充套件裝飾器,請求作用域等,另一方面也可以提升本身的能力,方便後續迭代。

我們產出了 injection 模組,作為我們整個框架的依賴注入基礎。

如今,injection 承載起了整個 midway 體系,它將框架程式碼,業務程式碼,外掛等都組合到了一起,像一個紐帶在這些之間傳輸資料。

dep_image

通過依賴注入容器的管理,如上圖非常複雜的應用也能良好的維護和運作。

想看完整大圖,可以點選這裡

面向裝飾器開發

得益於 Typescript 對 ES6 的良好支援,提供了一種為類宣告和成員添加註釋和超程式設計語法的方法。裝飾器作為TypeScript的實驗性功能能夠讓我們在開發中簡化程式碼。雖然是語法糖,但是帶來的好處卻不少。

我們拿一個簡單的例子,從 Controller 一步步經過 Service/Manager 向資料庫拿資料,在多層的架構體系下,以往的程式碼大概率需要 new 出不同的例項,並且需要繫結到路由層,這邊為了方便理解,程式碼放到了一起。

export = (app) => {
  const home = new HomeController();
  app.get('/', home.index);
}

class HomeController extends Controller {

  reportService: IReportService;

  constructor() {
    this.reportService = new ReportService();
  }
    
  async index(ctx) {
    ctx.body = await this.reportService.getReport();
  }
}

class ReportService implements IReportService {

  reporter: IReportManager;
  
  constructor() {
    this.reporter = new ReporterManager();
  }


  async getReport(id: number) {
    return await this.reporter.get(id);
  }
}

class ReporterManager implements IReportManager {

  db;

  constructor() {
    this.initDB();
  }

  initDB() {
    // open connection
  }

  async get() {
    // return data from db;
  }
}

經過 IoC 相關的 @provide@inject 裝飾器修飾以及其他 web 層的裝飾器修飾過後,不僅僅只是程式碼量的減少,業務的程式碼也不再有例項化的過程。以往還需要考慮在構造器中做非同步的操作,比如初始化時需要做非同步連線資料庫,這個時候也不再需要考慮,直接使用 @init 裝飾即可。

至此,我們會更加專注於面向介面進行程式設計,抽象,將程式碼設計的時間更多的花在理解需求,解決問題上。

@provide()
@controller()
export class HomeController {

  @inject()
  reportService: IReportService;
  
  @get('/')
  async index(ctx) {
    ctx.body = await this.reportService.getReport();
  }
}

@provide()
class ReportService implements IReportService {

  @inject()
  reporter: IReportManager;
  
  async getReport(id: number) {
    return await this.reporter.get(id);
  }
}

@provide()
class ReporterManager implements IReportManager {

  @inject()
  db;

  @init()
  initDB() {
    // open connection
  }

  async get() {
    // return data from db;
  }
}

入口能力

就像上面提到的 @controller 裝飾器類似,針對入口型的程式碼,我們在框架層面擴充套件了其他裝飾器,比如針對計劃任務形式我們提供了 @schedule 裝飾器,簡化使用者開發的程式碼量。

import { schedule } from 'midway';

@schedule({
  interval: 2333, // 2.333s 間隔
  type: 'worker', // 指定某一個 worker 執行
})

export class HelloCron {
  // 定時執行的具體任務
  async exec(ctx) {
    ctx.logger.info(process.pid, 'hello');
  }
}

在下一版本中,我們將開放自定義裝飾器的能力,方便更多場景的使用。

框架擴充套件

由於在大多數場景下,使用了裝飾器已經依賴注入的寫法,使得自己的業務程式碼,乃至三方的模組都能很好的融在一起,除了這些之外,有的同學會疑問,原本的外掛,配置,上下文部分如何融入到這個體系,我們這就來解答。

在原本熟悉的體系中,只要有 app , ctx 物件就無敵了,所有的東西都可以拿。而在 midway 中,為了和 web 層進行解耦,我們隱去了這些物件,只希望業務程式碼和 IoC 容器打交道。

為此我們提供了 @config@plugin 裝飾器用於獲取不同的方法,通過這樣的形式和框架進行解耦,比如在任意程式碼中如下使用。

@provide()
class ReportService implements IReportService {

  @config('env')
  env;

  @plugin('httpclient')
  httpclient;

  @inject()
  reporter: IReportManager;
  
  async getReport(id: number) {
    const rid = this.httpclient.request('/api/' + id);
    return await this.reporter.get(rid);
  }
}

正是這樣一點點的調整,我們將整個應用的程式碼風格保持了到了一致,不管程式碼幾經易手,維護的同學也能快速上手,並且繼續迭代下去。

最後

正向我們在 Pandora.js 釋出時說的那樣,midway 也是 MidwayJs 團隊長期維護的一款產品,同樣不會是最後一款,前幾個月,我們就計劃將我們的監控平臺 Sandbox 帶出來回饋給社群,雖然道阻且長,任務艱辛,我們依舊在努力前行,歡迎關注。

最後,midway 的地址在這 https://github.com/midwayjs/midway/,歸屬在 midwayJs Group 下。歡迎走過路過點個 Star,給我們提提建議,提提程式碼。

Midway 官網:https://midwayjs.org/midway/

image-20190110141919113