文: 达孚(沪江Web前端架构师)

本文原创,转至沪江技术

首先上一下项目地址(:>):

Nest:https://github.com/nestjs/nest

Nesk:https://github.com/kyoko-df/nesk

Nest初认识

Nest是一个深受angular激发的基于express的node框架,按照官网说明是一个旨在提供一个开箱即用的应用程序体系结构,允许轻松创建高度可测试,可扩展,松散耦合且易于维护的应用程序。

在设计层面虽然说是深受angular激发,但其实从后端开发角度来说类似于大家熟悉的Java Spring架构,使用了大量切面编程技巧,再通过装饰器的结合完全了关注上的分离。同时使用了Typescript(也支持Javascript)为主要开发语言,更保证了整个后端系统的健壮性。

强大的Nest架构

那首先为什么需要Nest框架,我们从去年开始大规模使用Node来替代原有的后端View层开发,给予了前端开发除了SPA以外的前后端分离方式。早期Node层的工作很简单-渲染页面代理接口,但在渐渐使用中大家会给Node层更多的寄托,尤其是一些内部项目中,你让后端还要将一些现有的SOA接口进行包装,对方往往是不愿意的。那么我们势必要在Node层承接更多的业务,包括不限于对数据的组合包装,对请求的权限校验,对请求数据的validate等等,早期我们的框架是最传统的MVC架构,但是我们翻阅业务代码,往往最后变成复杂且很难维护的Controller层代码(从权限校验到页面渲染一把撸到底:))。

那么我们现在看看Nest可以做什么?从一个最简单的官方例子开始看:

async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();

这里就启动了一个nest实例,先不看这个ValidationPipe,看ApplicationModule的内容:

@Module({
imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
configure(consumer: MiddlewaresConsumer): void {
consumer
.apply(LoggerMiddleware)
.with('ApplicationModule')
.forRoutes(CatsController);
}
}
@Module({
controllers: [CatsController],
components: [CatsService],
})
export class CatsModule {}

这里看到nest的第一层入口module,也就是模块化开发的根本,所有的controller,component等等都可以根据业务切分到某个模块,然后模块之间还可以嵌套,成为一个完整的体系,借用张nest官方的图:

在nest中的component概念其实一切可以注入的对象,对于依赖注入这个概念在此不做深入解释,可以理解为开发者不需要实例化类,框架会进行实例化且保存为单例供使用。

@Controller('cats')
@UseGuards(RolesGuard)
@UseInterceptors(LoggingInterceptor, TransformInterceptor)
export class CatsController {
constructor(private readonly catsService: CatsService) {} @Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
} @Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
} @Get(':id')
findOne(
@Param('id', new ParseIntPipe())
id,
): Promise<Cat> {
return this.catsService.findOne(id);
}
}

Controller的代码非常精简,很多重复的工作都通过guards和interceptors解决,第一个装饰器Controller可以接受一个字符串参数,即为路由参数,也就是这个Controller会负责/cats路由下的所有处理。首先RolesGuard会进行权限校验,这个校验是自己实现的,大致结构如下:

@Guard()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {} canActivate(request, context: ExecutionContext): boolean {
const { parent, handler } = context;
const roles = this.reflector.get<string[]>('roles', handler);
if (!roles) {
return true;
} // 自行实现
}
}

context可以获取controller的相关信息,再通过反射拿到handler上是否有定义roles的元信息,如果有就可以在逻辑里根据自己实现的auth方法或者用户类型来决定是否让用户访问相关handler。

interceptors即拦截器,它可以:

  • 在方法执行之前/之后绑定额外的逻辑
  • 转换从函数返回的结果
  • 转换从函数抛出的异常
  • 根据所选条件完全重写函数 (例如, 缓存目的)

本示例有两个拦截器一个用来记录函数执行的时间,另一个对结果进行一层包装,这两个需求都是开发中很常见的需求,而且拦截器会提供一个rxjs的观察者流来处理函数返回,支持异步函数,我们可以通过map()来mutate这个流的结果,可以通过do运算符来观察函数观察序列的执行状况,另外可以通过不返回流的方式,从而阻止函数的执行,LoggingInterceptor例子如下:

@Interceptor()
export class LoggingInterceptor implements NestInterceptor {
intercept(dataOrRequest, context: ExecutionContext, stream$: Observable<any>): Observable<any> {
console.log('Before...');
const now = Date.now(); return stream$.do(
() => console.log(`After... ${Date.now() - now}ms`),
);
}
}

回到最初的ValidationPipe,它是一个强大的校验工具,我们看到前面的controller代码中插入操作中有一个CreateCatDto,dto是一种数据传输对象,一个dto可以这样定义:

export class CreateCatDto {
@IsString() readonly name: string; @IsInt() readonly age: number; @IsString() readonly breed: string;
}

然后ValidationPipe会检查body是否符合这个dto,如果不符合就会就会执行你在pipe中设置的处理方案。具体是如何实现的可以再写一篇文章了,所以我推荐你看nest中文指南(顺便感谢翻译的同学们)

示例的完整代码可以看01-cats-app

也就是说业务团队中的熟练工或者架构师可以开发大量的模块,中间件,异常过滤器,管道,看守器,拦截器等等,而不太熟练的开发者只需要完成controller的开发,在controller上像搭积木般使用这些设施,即完成了对业务的完整搭建。

Nesk-一个落地方案的尝试

虽然我个人很喜欢Nest,但是我们公司已经有一套基于koa2的成熟框架Aconite,而Nest是基于express的,查看了下Nest的源码,对express有一定的依赖,但是koa2和express在都支持async语法后,差异属于可控范围下。另外nest接受一个express的实例,在nesk中我们只需要调整为koa实例,那么也可以是继承于koa的任何项目实例,我们的框架在2.0版本也是一个在koa上继承下来的node框架,基于此,我们只需要一个简单的adapter层就可以无缝接入Aconite到nesk中,这样减少了nesk和内部服务的捆绑,而将所有的公共内部服务整合保留在Aconite中。Nest对于我们来说只是一个更完美的开发范式,不承接任何公共模块。

所以我们需要的工作可以简单总结为:

  1. 支持Koa
  2. 适配Aconite

支持Koa我们在Nest的基础上做了一些小改动完成了Nesk来兼容Koa体系。我们只需要完成Nesk和Aconite中间的Adapter层,就可以完成Nesk的落地,最后启动处的代码变成:

import { NeskFactory } from '@neskjs/core';
import { NeskAconite } from '@hujiang/nesk-aconite';
import { ApplicationModule } from './app.module';
import { config } from './common/config';
import { middwares } from './common/middlware'; async function bootstrap() {
const server = new NeskAconite({
projectRoot: __dirname,
middlewares,
config
});
const app = await NeskFactory.create(ApplicationModule, server);
await app.listen(config.port);
}

最后Nest有很多@nest scope下的包,方便一些工具接入nest,如果他们与express没有关系,我们其实是可以直接使用的。但是包内部往往依赖@nest/common或者@nesk/core,这里可以使用module-alias,进行一个重指向(你可以尝试下graphql的例子):

"_moduleAliases": {
"@nestjs/common": "node_modules/@neskjs/common",
"@nestjs/core": "node_modules/@neskjs/core"
}

Nesk的地址Nesk,我们对Nesk做了基本流程测试目前覆盖了common和core,其它的在等待改进,欢迎一切愿意一起改动的开发者。

不足与期待

其实从一个更好的方面来说,我们应当允许nest接受不同的底层框架,即既可以使用express,也可以使用koa,通过一个adapter层抹平差异。不过这一块的改造成本会大一些。

另一方面nest有一些本身的不足,在依赖注入上,还是选择了ReflectiveInjector,而Angular已经开始使用了StaticInjector,理论上StaticInjector减少了对Map层级的查找,有更好的性能,这也是我们决定分叉出一个nesk的原因,可以做一些更大胆的内部代码修改。另外angular的依赖注入更强大,有例如useFactory和deps等方便测试替换的功能,是需要nest补充的.

最后所有的基于Koa的框架都会问到一个问题,能不能兼容eggjs(

从Nest到Nesk -- 模块化Node框架的实践的更多相关文章

  1. 【Nodejs】392- 基于阿里云的 Node.js 稳定性实践

    前言 如果你看过 2018 Node.js 的用户报告,你会发现 Node.js 的使用有了进一步的增长,同时也出现了一些新的趋势. Node.js 的开发者更多的开始使用容器并积极的拥抱 Serve ...

  2. ABP vnext模块化架构的最佳实践的实现

    在上一篇文章<手把手教你用Abp vnext构建API接口服务>中,我们用ABP vnext实现了WebAPI接口服务,但是并非ABP模块化架构的最佳实践.我本身也在学习ABP,我认为AB ...

  3. 好未来数据中台 Node.js BFF实践(一):基础篇

    好未来数据中台 Node.js BFF实践系列文章列表: 基础篇 实战篇(TODO) 进阶篇(TODO) 好未来数据中台的Node.js中间层从7月份开始讨论可行性,截止到9月已经支持了4个平台,其中 ...

  4. IOC框架Ninject实践总结

    原文地址:http://www.cnblogs.com/jeffwongishandsome/archive/2012/04/15/2450462.html IOC框架Ninject实践总结 一.控制 ...

  5. Slickflow.NET 开源工作流引擎基础介绍(六)--模块化架构设计和实践

    前言:在集成Slickflow.NET 引擎组件过程中,引擎组件需要将用户,角色等资源数据读取进来,供引擎内部调用:而企业客户都是有自己的组织架构模型,在引入模块化架构设计后,引擎组件的集成性更加友好 ...

  6. .NET领域驱动设计—初尝(一:疑问、模式、原则、工具、过程、框架、实践)

     .NET领域驱动设计—初尝(一:疑问.模式.原则.工具.过程.框架.实践) 2013-04-07 17:35:27 标签:.NET DDD 驱动设计 原创作品,允许转载,转载时请务必以超链接形式标明 ...

  7. Appium+python自动化(三十八) - Appium自动化测试框架综合实践 - 框架简介-助你冲击高薪,迎娶白富美(超详解)

    简介 好久没有更新博客了,博友们是不是有点等不及了.不好意思啊,中秋节过后太忙了,这篇是好不容易抽点零碎时间写的.从这一篇开始小伙伴或者童鞋们,就跟随宏哥的脚步,一步步的从无到有,从0到1的搭建一个完 ...

  8. Appium+python自动化(三十九)-Appium自动化测试框架综合实践 - 代码实现(超详解)

    简介 经过一段时间的准备,完善的差不多了,继续分享有关Appium自动化测试框架综合实践.想必小伙伴们有点等不及了吧! driver配置封装 kyb_caps.yaml 配置表 参考代码 platfo ...

  9. Appium+python自动化(四十)-Appium自动化测试框架综合实践 - 代码实现(超详解)

    1.简介 今天我们紧接着上一篇继续分享Appium自动化测试框架综合实践 - 代码实现.由于时间的关系,宏哥这里用代码给小伙伴演示两个模块:注册和登录. 2.业务模块封装 因为现在各种APP的层出不群 ...

随机推荐

  1. BZOJ 2683: 简单题(CDQ分治 + 树状数组)

    BZOJ2683: 简单题(CDQ分治 + 树状数组) 题意: 你有一个\(N*N\)的棋盘,每个格子内有一个整数,初始时的时候全部为\(0\),现在需要维护两种操作: 命令 参数限制 内容 \(1\ ...

  2. POJ 2195 Going Home (费用流)

    题面 On a grid map there are n little men and n houses. In each unit time, every little man can move o ...

  3. MapReduce并行编程模型和框架

    传统的串行处理方式 有四组文本数据: "the weather is good", "today is good", "good weather is ...

  4. mysql 各类操作命令

    1.mysql 命令登陆 形式: mysql -u用户名 -p密码 mysql -uroot -proot 2.mysql 显示数据库 形式: show databases; 3.mysql 进入某一 ...

  5. python web开发-flask访问请求数据request

    Request对象在web应用的开发中是一个非常重要的对象,主要用来获取用户发来的请求数据. 常用属性参考:http://docs.jinkan.org/docs/flask/api.html#fla ...

  6. Path通过Selenium模拟浏览器抓取,Windows 64解决selenium.common.exceptions.WebDriverException: Message: 'geckodriver' executable needs to be in PATH.方法

    1.下载geckodriver.exe: 下载地址:https://github.com/mozilla/geckodriver/releases请根据系统版本选择下载:(如Windows 64位系统 ...

  7. Cesium home键定位的位置

    Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(80, 22, 130, 50);//home定位到中国范围

  8. Linux 新手应该知道的一些求助命令

    Linux 真正的强大所在是他的[命令行].每一个 Linux 命令其实就是一个程序,借助这些命令,我们可以办到非常多的事情.遇到困难时应该用什么命令去解决呢?下面兄弟连教育Linux小编将会为大家介 ...

  9. C++模板类与Qt信号槽混用

    一.正文 目前正在做一个视频处理相关的项目.项目的技术栈是这样的,UI层采用Qt来实现基本的数据展示和交互,底层音视频采用的是一套基于FFmpeg的视频处理框架.这是一套类似Microsoft Med ...

  10. 1-1 maven 学习笔记(1-6章)

    一.基础概念 1.Maven作为Apache组织中颇为成功的开源项目,主要服务于基于Java平台的项目构建,依赖管理和项目信息管理.从清理,编译,测试到生成报告,到打包部署,自动化构建过程. 还可以跨 ...