nestjs後端開發實戰(一)——依賴注入

前言

js單線程和無阻塞io讓它在處理高併發時有着得天獨厚的優點,node應運而生,今後js進入到後端開發的行列。可是目前js在後端開發領域,並無獲得普遍和深度的應用。緣由可能有這幾點:前端

  1. 異步代碼很是難看,回調地域。
  2. 沒有類型系統,ide不友好,不利於大規模應用的開發和維護。
  3. 缺少相對標準的開發範式和開發框架。

其中第一點,目前async await已經很是成熟,不成構成問題;對於第二點,若是引入ts也將不是問題。ts徹底兼容js,有類型系統又不失靈活,設計優雅適合大規模程序開發;至於第三點,有不少框架正在試圖解決該問題,好比egg、sails以及本文要討論的nest。nest是一個對標spring的後端開發框架,目前還年輕,但發展速度挺快。java

egg和sails沒有深度使用,只是有所關注,沒有太多發言權。他們解決的問題差很少,只是感受實現方式有點「硬」,不夠天然。或許是由於我的早前有java的經歷,因此更適應nest這套。java幾乎是後端開發的標準,nest把那套實踐了多年的理念借鑑過來,或許可以有些奇妙的化學反應。再結合js的靈活性以和性能優點,說不定也是輕量級後端開發的一個好選擇。接下來,筆者準備寫一系列的文章來介紹nest後端開發的實踐,歡迎關注。node

後端開發和依賴注入

我先嚐試着把依賴注入解釋清楚,這是nest的核心,因此從這裏開始。spring

前端開發和後端開發其實很不同。前端開發比較零碎,ui、交互、部分邏輯,然後端主要專一於邏輯。因此後端開發很是須要一種編程範式,以支持複雜的領域模型和業務邏輯管理。目前實踐得比較成熟的是面向對象的思想,而對於前端開發,面向對象的訴求其實並不大。mongodb

有了面向對象這個前提後,對象的依賴、建立、生命週期管理等就成了一個問題,依賴注入(DI)正是提供了一種標準方式來解決此問題。它將依賴的建立和銷燬交給「容器」去管理,使用者只管用,不操心具體細節。這也是控制反轉(IOC)思想的一種實現。數據庫

上面這段話說得比較抽象,現實一點,我的以爲它比較方便的解決了兩類問題:編程

  1. 上下文相關的依賴注入。就是須要根具不一樣的上下文注入不一樣的實例,共享上下文的狀態。
  2. 異步依賴的注入。

下面經過兩個例子來解釋。
例一,解釋上下文相關依賴問題。先看代碼:後端

class OrderDao {
    ...
}

class OrderService {
    private orderDao: OrderDao;
    constructor() {
        // 依賴OrderDao
        this.orderDao = new OrderDao();
    }
    ...
}

OrderService依賴OrderDao,而且在構造函數中實例化了依賴對象。這是一種強依賴關係,若是想在不一樣的上下文改變orderDao的實例就比較麻煩了。實際編程中可能存在相似場景,好比,跑測試用例的時候,想把dao換成mock的實現。設計模式

要達到上面的目的,代碼得先重構一下:併發

interface IOrderDao {
    ...
}

class OrderDaoImpl implements IOrderDao {
    ...
}

class OrderDaoMockImpl implements IOrderDao {
    ...
}

class OrderService {
    private orderDao: IOrderDao;
    // 依賴接口而不是實例
    constructor(orderDao: IOrderDao) {
        this.orderDao = orderDao;
    }
    ...
}

上面的代碼只是一種設計模式,和依賴注入無關。這種模式的思想是面向接口編程,而不是具體實現,從而達到解耦的目的。若是有依賴注入的容器,那麼只需簡單配置,容器會幫你管理依賴的建立和生命週期。具體的配置後面會講到。

例二,解釋異步依賴問題。假設OrderDao依賴mongo訪問數據庫,可是mongo client的建立倒是異步的。同時咱們還但願mongo client是單例,由於不但願頻繁的建立數據庫鏈接。下面是無依賴注入狀況下的一種可能實現:

// 鏈接數據庫的示例代碼
const MongoClient = require('mongodb').MongoClient;
const url = 'mongodb://localhost:27017';
const dbName = 'myproject';
MongoClient.connect(url, function(err, client) {
 // 在這裏才能拿到client操做數據庫
  const db = client.db(dbName);
    // ...
});

class OrderDao {
    private mongo;
    constructor() {
        //異步的方式拿到mongo client
    }
}

能解決問題,只是代碼會難看一點。因爲是異步,還可能存在使用OrderDao的時候,mongo並無鏈接好,此時調用會出錯。若是有依賴注入,就能比較優雅的處理此類問題。

以上說到的兩類場景,實際編程遇到的可能並很少,可能10%都不到,可是一旦趕上又很是難受。使用依賴注入,可以優雅的解決上面的問題,同時代碼也更加規範。但使用依賴注入也是有一點點成本的,須要寫一點點的樣板代碼。依賴注入還具有傳染性,就是某個對象使用了依賴注入,依賴它的對象也必須使用,不然就亂套了。我的的見解是,首先仍是保持簡潔,對象儘可能設計成上下文無關或無狀態,只是在核心層(controller service, dao)使用依賴注入。

在nest中使用依賴注入

前面寫了這麼多,如今看下怎麼在nest中寫依賴注入。樣板代碼很簡單,大體是這樣:
一、依賴方經過@Injectable()修飾,告訴容器,「我是須要注入的」,同時在構造函數中聲明依賴。實例化時,依賴對象將經過構造函數注入。

// order.service.ts
@Injectable()
export class OrderService {
    // 注意這裏是個簡寫,等價於在OrderService下面定義了orderDao字段,同時在構造函數中給與賦值
    constructor(private readony orderDao: OrderDao) {}
}

二、定義providor,服務提供者。nest中有三種providor:class、value、factory。class providor就是普通的class,會被實例化後注入給依賴方;value providor能夠是任意類型的值,直接注入給依賴方;factory providor是一個工廠方法,容器將先執行該方法,而後將返回值注入給依賴方,factory支持支持異步方法。

三、配置依賴關係。nest中有module的概念,主要用於描述在該scope下,具體的依賴和輸出關係。下面的代碼展現了三種providor的配置。

import {OrderDao} from './order.dao';// class providor

const classProvidor = { // 這也是class providor,和👆效果同樣
    provide: OrderDao,
    useClass: OrderDao
}

const valueProvidor = { // value providor
    provide: 'Config',
    useValue: process.env.NODE_ENV === 'prod' ? {...} : {...}
}
const factoryProvidor = { // factory provoidr
    provide: 'Mongo',
    useFactory: async () => {
        const client await MongoClient.connect(...);
        return client.db(dbName);
    }
}
@Module({
    providers: [OrderDao, valueProvidor, factoryProvidor] // 塞到這裏
})
export class OrderModule {}

四、依賴關係的解析。除了全局module(經過@Global()修飾便可成爲全局module),其它module都是一個單獨的scope。容器在建立對象時,會在當前scope和全局scope查找依賴。在決定具體使用哪一個依賴時,會經過類型匹配或者具名的方式查找。兩種使用方式都很簡單,代碼以下:

class OrderService {
    constructor(
        readonly orderDao: OrderDao, // class匹配,經過在scope內搜索同類型class的providor
        @Inject('Config') config,// 具名匹配,經過在scope內搜索該名字的provoid
    ) {}
}

剩下的就交給容器幫你建立和管理對象了。

結語

開篇寫得比較簡單,主要是關於爲何要依賴注入的思考。接下來可能會逐步分享實踐方面的一些東西,好比項目結構,分層,基礎設施等具體問題的解決方案,歡迎關注。

相關文章
相關標籤/搜索