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
    ) {}
}

剩下的就交给容器帮你建立和管理对象了。

结语

开篇写得比较简单,主要是关于为何要依赖注入的思考。接下来可能会逐步分享实践方面的一些东西,好比项目结构,分层,基础设施等具体问题的解决方案,欢迎关注。

相关文章
相关标签/搜索