Nestjs是一款非常强大的Node.js框架,而且入门非常容易,但是随着项目的增长,各种不便之处就会显现出来,许多代码书写起来不再像项目刚启动时直观。而Vonajs是一款全新的Node.js框架,提供了许多创新性的架构设计,让我们在开发任何规模的项目时,代码都能保持直观和优雅。下面,我们一起来看看这些特性,是否真的比nestjs更好用?

特性

特性 Vona Nest
Typescript
模块化体系 scope对象:config/locale/service
全量esm模块 commonjs
Ioc容器 依赖注入、依赖查找
bean配置
bean全局单例 节约内存
多租户
多数据库、多数据源
数据库事务
cli命令
菜单命令
env:环境变量 多维配置,开箱即用
config 多维配置,开箱即用
单文件打包、集群部署 开箱即用
aop:前置切面: Middleware/Guard/Pipe/Interceptor/Filter 系统中间件、全局中间件、局部中间件
aop:主体切面: @AopMethod
aop:客体切面: @Aop
ssr聚合
demo练习场 REPL

Typescript

Typescript现在已经是开发Node.js框架的标配技能了,勿须多言。

模块化体系

Vona和Nest都提供了模块化体系,而Vona还提供了scope机制。也就是为每个模块提供了一个scope对象,通过scope对象可以非常方便的访问模块的各类资源和能力。限于篇幅,这里仅对config/locale/service加以举例说明:

1. config

首先,在模块的config文件中添加配置项:

src/module/demo-student/src/config/config.ts

export function config() {
return {
+ title: 'hello world',
};
}

然后,在模块的controller中使用config配置,支持类型提示:

src/module/demo-student/src/controller/student.ts

export class ControllerStudent {
async findAll() {
+ console.log(this.scope.config.title);
}
}

2. locale国际化

首先,在模块的locale文件中添加国际化语言资源:

src/module/demo-student/src/config/locale/en-us.ts

export default {
+ Name: 'Name',
};

src/module/demo-student/src/config/locale/zh-cn.ts

export default {
+ Name: '名称',
};

然后,在模块的controller中使用国际化语言资源,支持类型提示:

src/module/demo-student/src/controller/student.ts

export class ControllerStudent {
async findAll() {
+ console.log(this.scope.locale.Name()); // 根据环境值自动翻译为指定的语言
+ console.log(this.scope.locale.Name.locale('en-us')); // Name
+ console.log(this.scope.locale.Name.locale('zh-cn')); // 名称
}
}

3. service

首先,在模块中定义一个student service:

src/module/demo-student/src/service/student.ts

@Service()
export class ServiceStudent {
async findAll() {
return await this.scope.model.student.select();
}
}

然后,在模块的controller中直接使用student service,支持类型提示:

src/module/demo-student/src/controller/student.ts

export class ControllerStudent {
async findAll() {
+ return await this.scope.service.student.findAll();
}
}

全量esm模块

Nest由于开发得比较早,因此仍然采用的是commonjs模块。而Vona是全新的框架,所以全量采用esm模块,项目启动更快。

Ioc容器

Vona和Nest都提供了Ioc容器,而Vona提供了更加快捷的容器操纵能力。我们知道Ioc容器主要提供两个操作:注册bean获取bean。获取bean有两种方式:依赖注入依赖查找。Vona不仅支持依赖注入,同时也提供了依赖查找,使代码书写更加直观、优雅。这里简要演示全局bean和本地bean的依赖查找:

  • 获取全局bean:jwt,并调用create方法:
export class ControllerStudent {
async findAll() {
+ const accessToken = await this.bean.jwt.create();
}
}
  • 获取本地bean:student,并调用findAll方法:
export class ControllerStudent {
async findAll() {
+ return await this.scope.service.student.findAll();
}
}

bean配置

在Ioc语境当中,bean就是可以注入的class,包括controller、service、middleware、guard、interceptor、pipe,filter,等等。Vona提供了一个通用的机制,让我们可以为所有bean提供动态配置能力,从而显著提升整个系统的扩展性,也能够节约大量的与配置相关的代码。下面我们以middleware为例,来演示如何进行bean配置

1. 创建一个middleware

src/module/demo-student/src/bean/middleware.test.ts

export interface IMiddlewareOptionsTest {
+ title: string;
} @Middleware<IMiddlewareOptionsTest>({
+ title: 'hello world',
})
export class MiddlewareTest {
async execute(options: IMiddlewareOptionsTest, next: Next) {
+ console.log(options.title);
// next
return next();
}
}
  • 行2:定义一个参数title
  • 行6:为参数提供缺省值
  • 行10:在实际代码当中直接读取配置选项

2. 使用middleware,并传参

export class ControllerStudent {
+ @Aspect.middleware('demo-student:test', { title: 'hello world!!' })
async findAll() {
}
}
  • 行2:使用装饰器@Aspect.middleware,传入middleware的名称:demo-student:test

    • 同时可以传入新的参数配置,覆盖默认值

3. 通过项目Config来修改middleware配置

src/backend/config/config/config.local.mine.ts

export default function () {
const config = {} as VonaConfigOptional;
// onions
config.onions = {
middleware: {
+ 'demo-student:test': {
+ title: '您好世界!!',
+ },
},
};
  • 直接在系统Config文件中修改中间件的参数配置,支持类型提示

bean全局单例

一般而言,bean的实例化有三个层级:全局单例Request单例总是创建新实例全局单例是最节约内存的。Request单例,有多少用户请求,就要创建多少个bean实例。比如,执行一个完整的api请求需要创建2个bean实例,如果1秒中有1万个请求,那么就需要创建2万个bean实例,对内存的占用非常高,也会严重影响gc的性能。可想而知,采用全局单例还是采用Request单例对系统性能的影响是非常显著的。

Nest对bean实例化有特殊的处理机制:在默认情况下,Controller/Service是全局单例的,但是,如果在代码中注入了与Request相关的对象,那么这些bean就会降级到Request单例。可想而知,在一个实际的业务系统中,基本上这些bean都会访问与Request相关的数据,所以基本上都是按Request单例实例化的。与Request相关的数据包括:当前语言、当前用户、当前租户,等等。

而Vona则是实现了完整的全局单例机制。即便这些bean实例访问了Request相关的对象,也不会降级。从原理来说,Vona底层采用了Async Local Storage机制,从而让整个系统的内存占用非常低,也能显著改善gc的性能。

多租户

Nest本身没有提供多租户的能力,社区有一些多租户的方案,但是代码不够简洁。而Vona则内置了多租户的能力,让代码更加简洁。

src/module/demo-student/src/service/student.ts

export class ServiceStudent {
async findAll() {
+ return await this.scope.model.student.select();
}
}
  • 行3:这行代码就是查询当前租户下的所有学生信息

    • 由此可见,租户能力是完全透明的

多数据库、多数据源

Vona和Nest都支持多数据库、多数据源。此外,Vona还提供了开箱即用的读写分离动态数据源能力。下面我们看一下在Vona中的代码体验:

1. 数据源配置

{
defaultClient: 'pg', // 缺省数据源
clients: { // 所有数据源清单
pg: {
client: 'pg', // 所使用的数据库方言
connection: {...},
},
mysql: {
client: 'mysql2',
connection: {...},
},
},
}

2. 使用数据源

// 获取缺省数据源
const db = app.bean.database.current;
// 创建其他数据源
const dbMysql = app.bean.database.createDb({ clientName: 'mysql' });

数据库事务

Nest本身不考虑数据库事务的问题,而是由第三方库来提供。而Vona则内置了数据库事务。

1. 使用装饰器

export class ControllerStudent {
+ @Database.transaction()
async update() {
}
}

2. 手动启用

export class ControllerStudent {
async update() {
+ const db = app.bean.database.current;
+ const result = await db.transaction.begin(async () => {
+ const data = await dosomething();
+ return data;
+ });
}
}

3. 数据库事务传播机制

Vona支持事务传播机制的设置,通过 propagation 属性来指定传播行为。Vona事务传播机制有以下 7 种:

  1. Propagation.REQUIRED: 默认的事务传播级别. 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则创建一个新的事务
  2. Propagation.SUPPORTS: 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则以非事务的方式继续运行
  3. Propagation.MANDATORY: 强制性. 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则抛出异常
  4. Propagation.REQUIRES_NEW: 创建一个新的事务. 如果当前存在事务, 则把当前事务挂起. 也就是说不管外部方法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部方法都会新开启自己的事务, 且开启的事务相互独立, 互不干扰
  5. Propagation.NOT_SUPPORTED: 以非事务方式运行, 如果当前存在事务, 则把当前事务挂起(不用)
  6. Propagation.NEVER: 以非事务方式运行, 如果当前存在事务, 则抛出异常
  7. Propagation.NESTED: 如果当前存在事务, 则创建一个事务作为当前事务的嵌套事务来运行.如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED

cli命令

Vona和Nest都提供了大量cli命令,用于生成各类资源的代码骨架。

菜单命令

Vona在cli命令的基础上提供了大量菜单命令,通过菜单来执行cli命令,从而显著降低心智负担,提升开发体验:

1. Vona Bean

2. Vona Create

3. Vona Init

4. Vona Meta

5. Vona Tools

env:多维配置

Vona提供了env的多维配置能力,支持更复杂的业务场景。

在默认情况下,可以在Vona中提供三个场景的env配置文件:

.env         // 缺省配置
.env.test // 测试环境
.env.local // 本地开发环境
.env.prod // 生产环境

如果我们想针对业务的需要新增更多场景的env配置,如何操作呢?Vona引入了flavor的概念。比如,我们新增一个flavor,名称为stage1,那么,env配置文件如下:

.env.stage1         // stage1的缺省配置
.env.stage1.test // stage1的测试环境
.env.stage1.local // stage1的本地开发环境
.env.stage1.prod // stage1的生产环境

Config:多维配置

Vona同样也提供了Config的多维配置能力,支持更复杂的业务场景。

config.ts
config.test.ts
config.local.ts
config.prod.ts # flavor: stage1 config.stage1.ts
config.stage1.test.ts
config.stage1.local.ts
config.stage1.prod.ts

单文件打包、集群部署

Vona提供了开箱即用的单文件打包和集群部署能力。Nest虽然也能支持这些能力,但是仍然需要花点功夫进行配置。

1. 单文件打包

$ npm run build
  • 执行此命令,将在dist目录生成唯一的js文件

2. 集群部署

$ npm run start
  • 执行此命令,将基于cpu数量启动多个进程,进行集群部署

2. 单进程模式

$ npm run start:one
  • 执行此命令,将启动单进程,可直接用于docker环境

aop:面向切面编程

在Vona中,将aop编程能力分为三个层次:前置切面主体切面客体切面

1. 前置切面

前置切面,就是在执行Controller Action之前切入逻辑,包括:Middleware、Guard、Pipe、Intercepter,Filter,等等。

2. 主体切面

主体切面,就是在任何Class的任何Action之前切入逻辑。比如,我们为更新数据的方法切入一个数据库事务:

export class ControllerStudent {
+ @Database.transaction()
async update() {
}
}

3. 客体切面

客体切面,就是在不改变源码的前提下,在任何Class的任何Action之前切入逻辑:

首先,我们先定义一个bean:

@Bean()
export class BeanTest {
actionSync() { } async actionAsync() { }
}

然后,创建一个aop bean:

@Aop({ match: 'test')
export class AopTest {
actionSync(_args, next) {
const result = next();
return result;
} async actionAsync(_args, next) {
const result = await next();
return result
}
}
  • 行2:定义一个aop bean
  • 行1:通过match参数,将AopTest与BeanTest建立关联
    • 当系统在执行BeanTest的方法时,会自动将AopTest提供的方法切入
    • 支持同步方法异步方法

ssr聚合

现在ssr渲染很火,但往往用于信息展示类的前台网站,后台admin系统则很少采用,主要原因就是整合有点难度,需要考虑的边界问题比较多,性价比不高。但是,Vona提供了开箱即用的ssr渲染能力,同时支持前台网站,和后台admin系统。

demo练习场

在实际开发当中,我们经常需要写一些临时代码,对一些想法和思路进行验证和评估。Nest提供了REPL机制,采用命令行交互模式。Vona专门提供了demo练习场,让我们可以非常方便的进行代码演练。下面,我们简要演示一下:

1. 书写demo

src/backend/demo/index.ts

export async function main(app: VonaApplication, argv: IArgv) {
... do something
}
  • 通过app可以调用系统所有能力
  • 通过argv接收命令行参数

2. 执行demo

$ npm run demo
  • 执行此命令,就会自动初始化一个app实例,然后执行demo/index.ts文件的main方法

结语

Vonajs作为全新的Node.js框架,顺应技术趋势,大胆使用新技术,提出了许多新的架构设计思路,通过一系列的微创新,解决开发当中的痛点问题,让我们的代码更加直观、优雅,从而能轻松应对大型项目的开发与维护。通过对Vonajs特性的简要介绍,大家是否认为会比nestjs更好用呢?

欲了解更多有关Vonajs的信息,参见B站:濮水代码

你认为Vonajs提供的这些特性会比Nestjs更好用吗?的更多相关文章

  1. 趋势:Chrome为打包应用提供强大新特性

    Chrome 7月9日刚为Chrome打包的应用提供了强大的访问Google服务例如Google统计.GoogleAPI和Google 钱包的能力,除此之外,还能够使用系统层面的服务包括蓝牙和原生应用 ...

  2. Atitit.eclise的ide特性-------abt 编译

    Atitit.eclise的ide特性-------abt 编译 为什么要在Intellij IDEA中使用Eclipse编译器 如果你使用Intellij Idea,你应该考虑使用Eclipse编译 ...

  3. 服务器RAS性能

    服务器的安全性能要求非常高,主要体现在RAS性能上.RAS性能指的是机器的可靠性(Reliability).可用性(Availability)和可服务性(Serviceability).RAS能力主要 ...

  4. 采访ServiceStack的项目领导Demis Bellot——第1部分(网摘)

    ServiceStack是一个开源的.支持.NET与Mono平台的REST Web Services框架.InfoQ有幸与Demis Bellot深入地讨论了这个项目.在这篇两部分报道的第1部分中,我 ...

  5. 采访ServiceStack的项目领导Demis Bellot——第1部分(转)

    ServiceStack是一个开源的.支持.NET与Mono平台的REST Web Services框架.InfoQ有幸与Demis Bellot深入地讨论了这个项目.在这篇两部分报道的第1部分中,我 ...

  6. Oracle 11g 执行计划管理1

    1. 执行计划管理的工作原理 1.1控制执行计划的稳定性 11g之前,可以使用存储大纲(stored outline)和SQL Profile来固定某条SQL语句的执行计划,防止由于执行计划发生变化而 ...

  7. 35.Intellij IDEA设置忽略部分类编译错误

    转自:https://www.aliyun.com/jiaocheng/290360.html 有些时候我们的项目中有些错误,但这些错误并不影响项目的整体运行(或许是没有使用到),默认情况下idea是 ...

  8. 为互联网业务而生:阿里云全球首发云Cassandra服务!

    引言:十年沉淀.全球宽表排名第一.阿里云首发云Cassandra服务 ApsaraDB for Cassandra是基于开源Apache Cassandra,融合阿里云数据库DBaaS能力的分布式No ...

  9. Intellij IDEA设置忽略部分类编译错误

    有些时候我们的项目中有些错误,但这些错误并不影响项目的整体运行(或许是没有使用到),默认情况下idea是无法通过编译的,因此也就无法部署运行,要达到正确运行项目的目的需要作一些设置才行. 设置Inte ...

  10. MVC特性路由的提供机制

    回顾:传统路由是如何提供的? 我们知道最终匹配的路由数据是保存在RouteData中的,而RouteData通常又是封装在RequestContext中的,他们是在哪里被创建的呢?没错,回到了UrlR ...

随机推荐

  1. python3 报错ModuleNotFoundError: No module named 'apt_pkg'

    前言 apt update无法执行,python3 报错 ModuleNotFoundError: No module named 'apt_pkg' 这是因为将 python 版本升级后的问题 正确 ...

  2. HTTP压缩的过程

      1. 浏览器发送Http request 给Web服务器,  request 中有Accept-Encoding: gzip, deflate.(告诉服务器浏览器支持gzip压缩) 2. Web服 ...

  3. 国产化-内存数据库tendis-单机安装(完美替代redis)

    挺好的产品腾讯能开源还是体现了大厂的担当和格局,赞一个.阿里也开源了一些不错的产品后面讲. Tendis 介绍 Tendis 是腾讯公司开源的一款高性能分布式存储系统,基于 Redis 协议开发,具有 ...

  4. final关键字、Object类--java进阶day01

    1.规则 被final修饰的变量,名称都要大写,多单词的名称则需_来分隔 1.修饰方法 method方法已经不能被重写了,因为修饰该方法的是final 2.修饰类 当一个类中所有的成员方法都不想被重写 ...

  5. 【Java】各种代码块的执行顺序

    静态代码块:用staitc声明,jvm加载类时执行,仅执行一次 构造代码块:类中直接用{}定义,每一次创建对象时执行. 执行顺序优先级:静态块,main(),构造块,构造方法. 构造函数 public ...

  6. 【数据库】Java实体类的属性类型与数据库表字段类型对应表

    JDBC类型与Java类型 JDBC类型 Java Object类型 CHAR java.lang.String VARCHAR java.lang.String LONGVARCHAR java.l ...

  7. Python requests代理(Proxy)使用教程

    Python requests代理(Proxy)使用教程 在 Python 的 requests 库中,使用代理服务器可以让你通过不同的网络路由发送 HTTP 请求.代理服务器可以帮助隐藏真实 IP ...

  8. 揭秘AI编排爆火真相:从"人工智障"到"真正智能"的关键一跃

    当行业还在追捧大模型参数竞赛时,领先团队早已转向新战场: AI编排(Agent Orchestration)-- 这个方向是 AI 技术"从聊天到做事"的关键突破口. 1.为什么说 ...

  9. 1、HTML常用标签

    此文章为学习笔记以下内容为HTML常用标签. 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset=&quo ...

  10. 探秘Transformer系列之(30)--- 投机解码

    探秘Transformer系列之(30)--- 投机解码 目录 探秘Transformer系列之(30)--- 投机解码 0x00 概述 0x01 背景 1.1 问题 1.2 自回归解码 0x02 定 ...