热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

nestjs后端开发实战(一)——依赖注入

前言js单线程和无阻塞io让它在处理高并发时有着得天独厚的优势,node应运而生,从此js进入到后端开发的行列。但是目前js在后端开发领域,并没有得到广泛和深度的应用。原因可能有这

前言

js单线程和无阻塞io让它在处理高并发时有着得天独厚的优势,node应运而生,从此js进入到后端开发的行列。但是目前js在后端开发领域,并没有得到广泛和深度的应用。原因可能有这几点:

  1. 异步代码非常难看,回调地域。
  2. 没有类型系统,ide不友好,不利于大规模应用的开发和维护。
  3. 缺乏相对标准的开发范式和开发框架。

其中第一点,目前async await已经非常成熟,不成构成问题;对于第二点,如果引入ts也将不是问题。ts完全兼容js,有类型系统又不失灵活,设计优雅适合大规模程序开发;至于第三点,有很多框架正在试图解决该问题,比如egg、sails以及本文要讨论的nest。nest是一个对标spring的后端开发框架,目前还年轻,但发展速度挺快。

egg和sails没有深度使用,只是有所关注,没有太多发言权。他们解决的问题差不多,只是感觉实现方式有点“硬”,不够自然。或许是因为个人早前有java的经历,所以更适应nest这套。java几乎是后端开发的标准,nest把那套实践了多年的理念借鉴过来,或许能够有些奇妙的化学反应。再结合js的灵活性以和性能优势,说不定也是轻量级后端开发的一个好选择。接下来,笔者准备写一系列的文章来介绍nest后端开发的实践,欢迎关注。

后端开发和依赖注入

我先尝试着把依赖注入解释清楚,这是nest的核心,所以从这里开始。

前端开发和后端开发其实很不一样。前端开发比较零碎,ui、交互、部分逻辑,而后端主要专注于逻辑。所以后端开发非常需要一种编程范式,以支持复杂的领域模型和业务逻辑管理。目前实践得比较成熟的是面向对象的思想,而对于前端开发,面向对象的诉求其实并不大。

有了面向对象这个前提后,对象的依赖、创建、生命周期管理等就成了一个问题,依赖注入(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中写依赖注入。样板代码很简单,大致是这样:
1、依赖方通过@Injectable()修饰,告诉容器,“我是需要注入的”,同时在构造函数中声明依赖。实例化时,依赖对象将通过构造函数注入。

// order.service.ts
@Injectable()
export class OrderService {
// 注意这里是个简写,等价于在OrderService下面定义了orderDao字段,同时在构造函数中给与赋值
constructor(private readony orderDao: OrderDao) {}
}

2、定义providor,服务提供者。nest中有三种providor:class、value、factory。class providor就是普通的class,会被实例化后注入给依赖方;value providor可以是任意类型的值,直接注入给依赖方;factory providor是一个工厂方法,容器将先执行该方法,然后将返回值注入给依赖方,factory支持支持异步方法。

3、配置依赖关系。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 {}

4、依赖关系的解析。除了全局module(通过@Global()修饰即可成为全局module),其它module都是一个单独的scope。容器在创建对象时,会在当前scope和全局scope查找依赖。在决定具体使用哪个依赖时,会通过类型匹配或者具名的方式查找。两种使用方式都很简单,代码如下:

class OrderService {
constructor(
readonly orderDao: OrderDao, // class匹配,通过在scope内搜索同类型class的providor
@Inject('Config') config,// 具名匹配,通过在scope内搜索该名字的provoid
) {}
}

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

结语

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


推荐阅读
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • IB 物理真题解析:比潜热、理想气体的应用
    本文是对2017年IB物理试卷paper 2中一道涉及比潜热、理想气体和功率的大题进行解析。题目涉及液氧蒸发成氧气的过程,讲解了液氧和氧气分子的结构以及蒸发后分子之间的作用力变化。同时,文章也给出了解题技巧,建议根据得分点的数量来合理分配答题时间。最后,文章提供了答案解析,标注了每个得分点的位置。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
  • 本文介绍了一个适用于PHP应用快速接入TRX和TRC20数字资产的开发包,该开发包支持使用自有Tron区块链节点的应用场景,也支持基于Tron官方公共API服务的轻量级部署场景。提供的功能包括生成地址、验证地址、查询余额、交易转账、查询最新区块和查询交易信息等。详细信息可参考tron-php的Github地址:https://github.com/Fenguoz/tron-php。 ... [详细]
  • 本文介绍了Java的公式汇总及相关知识,包括定义变量的语法格式、类型转换公式、三元表达式、定义新的实例的格式、引用类型的方法以及数组静态初始化等内容。希望对读者有一定的参考价值。 ... [详细]
  • C++语言入门:数组的基本知识和应用领域
    本文介绍了C++语言的基本知识和应用领域,包括C++语言与Python语言的区别、C++语言的结构化特点、关键字和控制语句的使用、运算符的种类和表达式的灵活性、各种数据类型的运算以及指针概念的引入。同时,还探讨了C++语言在代码效率方面的优势和与汇编语言的比较。对于想要学习C++语言的初学者来说,本文提供了一个简洁而全面的入门指南。 ... [详细]
  • 本文介绍了Python函数的定义与调用的方法,以及函数的作用,包括增强代码的可读性和重用性。文章详细解释了函数的定义与调用的语法和规则,以及函数的参数和返回值的用法。同时,还介绍了函数返回值的多种情况和多个值的返回方式。通过学习本文,读者可以更好地理解和使用Python函数,提高代码的可读性和重用性。 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
author-avatar
TC维尼_748
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有