文: 达孚(沪江Web前端架构师)前端
本文原创,转至沪江技术node
首先上一下项目地址(:>):git
Nest:github.com/nestjs/nestgithub
Nesk:github.com/kyoko-df/ne…express
Nest是一个深受angular激发的基于express的node框架,按照官网说明是一个旨在提供一个开箱即用的应用程序体系结构,容许轻松建立高度可测试,可扩展,松散耦合且易于维护的应用程序。编程
在设计层面虽说是深受angular激发,但其实从后端开发角度来讲相似于你们熟悉的Java Spring架构,使用了大量切面编程技巧,再经过装饰器的结合彻底了关注上的分离。同时使用了Typescript(也支持Javascript)为主要开发语言,更保证了整个后端系统的健壮性。bootstrap
那首先为何须要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的内容:bash
@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上像搭积木般使用这些设施,即完成了对业务的完整搭建。
虽然我我的很喜欢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对于咱们来讲只是一个更完美的开发范式,不承接任何公共模块。
因此咱们须要的工做能够简单总结为:
支持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都是一个强制开发规范的框架,只要eggjs还创建在koa的基础上,就能够完成集成,只是eggjs在启动层面的改动较大,并且开发范式和nest差别比较多,二者的融合并无显著的优点。
总之Node做为一个比较灵活的后端开发方式,每一个人心中都有本身以为合适的开发范式,若是你喜欢这种方式,不妨尝试下Nest或者Nesk。
2019年,iKcamp原创新书《Koa与Node.js开发实战》已在京东、天猫、亚马逊、当当开售啦!