從egg.js重新認識node後端開發

求實亭下發表於2019-02-16

前言

node的後端的開發接觸至今也不過4個月,在最近的開發中選用了之前沒有接觸過的egg.js。雖然還沒有深入開發,但是在與之前的專案的node端比較後還是有了比較多的感想。

node後臺開發實戰

之前的專案的主要選型是koa2+sequelize,主要專案結構似乎也是有板有眼,view層模版渲染,router層負責使用控制器,處理引數,控制器層呼叫sequelize或者作出資料處理,model層則是資料表的例項。

開發中的問題

  1. 控制器層的解耦和抽象,這部分在之前的開發中是有一點問題的。原因就是沒有物件導向,而是把控制器直接寫成了一些函式的module,這樣的問題就是很難繼承抽象,控制器的一個方法對應了router層的一個api,非常耦合。
  2. 在api的層的引數校驗沒有做,這層的引數校驗其實很有必要,可以更早的把問題直接在這層丟擲
  3. 沒有考慮事務,這部分其實也和之前開發的系統較為簡單有關。

egg.js實戰

在此次新專案的選型中首要就是解決物件導向的問題,雖然也看了一些別人的專案,但是在把控制器寫成一個單例還是把控制器的函式乾脆作為靜態函式來用的疑惑時,我參考了以下egg.js。之後我發現node後臺開發完全可以和java後臺開發一樣有出色的架構。

控制反轉和依賴注入

相信這兩種設計思想很多人都會了解,而egg.js中同樣有這兩種思想的實戰。這邊只舉一個例子就可以清楚的發現

//app/controller/post.js
const Controller = require(`../core/base_controller`);
class PostController extends Controller {
  async list() {
    const posts = await this.service.listByUser(this.user);
    this.success(posts);
  }
}

在控制器中並不需要去例項化需要的service,而是可以直接通過自身的sevice屬性就可以拿到需要的例項。整個egg其實就是一個容器,類的例項化和依賴管理就是egg的任務了。這一部分的處理其實和spring是非常接近。

另外不得不提的一點是,egg在對controller和service的例項化處理是並不不同的,egg並不會在一開始就把所有的service例項化,而是採用了即用即插的方式。那麼在實際專案中就需要把更多的業務邏輯抽象到service,並且就需要把可以分離的服務拆分好。

egg.js依賴注入的實現

controller的基類

class BaseContextClass {
  constructor(ctx) {
    this.ctx = ctx;
    this.app = ctx.app;
    this.config = ctx.app.config;
    this.service = ctx.service;
  }
}

之後就是ctx.service的由來

// define ctx.service
    Object.defineProperty(app.context, property, {
      get() {
        // distinguish property cache,
        // cache`s lifecycle is the same with this context instance
        // e.x. ctx.service1 and ctx.service2 have different cache
        if (!this[CLASSLOADER]) {
          this[CLASSLOADER] = new Map();
        }
        const classLoader = this[CLASSLOADER];

        let instance = classLoader.get(property);
        if (!instance) {
          instance = getInstance(target, this);
          classLoader.set(property, instance);
        }
        return instance;
      },
    });

這段簡短的程式碼加註釋很容易就發現service如何實現的即用即插。至於service的loader其實也很容易就不上原始碼了,大概就是載入了服務的資訊和路徑。而controller的loader則是在初始化的時候就載入控制器。
服務和控制器的例項化的程式碼

function getInstance(values, ctx) {
  // it`s a directory when it has no exports
  // then use ClassLoader
  const Class = values[EXPORTS] ? values : null;
  let instance;
  if (Class) {
    if (is.class(Class)) {
      instance = new Class(ctx);
    } else {
      // it`s just an object
      instance = Class;
    }
  // Can`t set property to primitive, so check again
  // e.x. module.exports = 1;
  } else if (is.primitive(values)) {
    instance = values;
  } else {
    instance = new ClassLoader({ ctx, properties: values });
  }
  return instance;
}

以上就是egg實現依賴注入的原理。

最後

在詢問了做Java後臺的同學後瞭解到service在很多時候回分散式的部署,並且最佳實踐就是與上層的控制器和下層的model層實現完全解耦。總的來說eggjs的學習對今後在程式碼解耦的實踐中會有很大的啟發,並且可以借這個機會重新認識node的後端開發。

之後會接著去學習egg.js的另一大特色——外掛。

相關文章