文末 發表於 2025-6-4 06:47:00

你认为Vonajs提供的这些特性会比Nestjs更好用吗?

<p>Nestjs是一款非常强大的Node.js框架,而且入门非常容易,但是随着项目的增长,各种不便之处就会显现出来,许多代码书写起来不再像项目刚启动时直观。而Vonajs是一款全新的Node.js框架,提供了许多创新性的架构设计,让我们在开发任何规模的项目时,代码都能保持直观和优雅。下面,我们一起来看看这些特性,是否真的比nestjs更好用?</p>
<h2 id="特性">特性</h2>
<table>
<thead>
<tr>
<th>特性</th>
<th>Vona</th>
<th>Nest</th>
</tr>
</thead>
<tbody>
<tr>
<td>Typescript</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>模块化体系</td>
<td>✅scope对象:config/locale/service</td>
<td>✅</td>
</tr>
<tr>
<td>全量esm模块</td>
<td>✅</td>
<td>❌commonjs</td>
</tr>
<tr>
<td>Ioc容器</td>
<td>✅依赖注入、依赖查找</td>
<td>✅</td>
</tr>
<tr>
<td>bean配置</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>bean全局单例</td>
<td>✅节约内存</td>
<td>❌</td>
</tr>
<tr>
<td>多租户</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>多数据库、多数据源</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>数据库事务</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>cli命令</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>菜单命令</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>env:环境变量</td>
<td>✅多维配置,开箱即用</td>
<td>✅</td>
</tr>
<tr>
<td>config</td>
<td>✅多维配置,开箱即用</td>
<td>✅</td>
</tr>
<tr>
<td>单文件打包、集群部署</td>
<td>✅开箱即用</td>
<td>❌</td>
</tr>
<tr>
<td>aop:前置切面: Middleware/Guard/Pipe/Interceptor/Filter</td>
<td>✅系统中间件、全局中间件、局部中间件</td>
<td>✅</td>
</tr>
<tr>
<td>aop:主体切面: @AopMethod</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>aop:客体切面: @Aop</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>ssr聚合</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>demo练习场</td>
<td>✅</td>
<td>❌REPL</td>
</tr>
</tbody>
</table>
<h2 id="typescript">Typescript</h2>
<p>Typescript现在已经是开发Node.js框架的标配技能了,勿须多言。</p>
<h2 id="模块化体系">模块化体系</h2>
<p>Vona和Nest都提供了模块化体系,而Vona还提供了scope机制。也就是为每个模块提供了一个scope对象,通过scope对象可以非常方便的访问模块的各类资源和能力。限于篇幅,这里仅对config/locale/service加以举例说明:</p>
<h3 id="1-config">1. config</h3>
<p>首先,在模块的config文件中添加配置项:</p>
<p><code>src/module/demo-student/src/config/config.ts</code></p>
<pre><code class="language-diff">export function config() {
return {
+   title: 'hello world',
};
}
</code></pre>
<p>然后,在模块的controller中使用config配置,支持类型提示:</p>
<p><code>src/module/demo-student/src/controller/student.ts</code></p>
<pre><code class="language-diff">export class ControllerStudent {
async findAll() {
+   console.log(this.scope.config.title);
}
}
</code></pre>
<h3 id="2-locale国际化">2. locale国际化</h3>
<p>首先,在模块的locale文件中添加国际化语言资源:</p>
<p><code>src/module/demo-student/src/config/locale/en-us.ts</code></p>
<pre><code class="language-diff">export default {
+ Name: 'Name',
};
</code></pre>
<p><code>src/module/demo-student/src/config/locale/zh-cn.ts</code></p>
<pre><code class="language-diff">export default {
+ Name: '名称',
};
</code></pre>
<p>然后,在模块的controller中使用国际化语言资源,支持类型提示:</p>
<p><code>src/module/demo-student/src/controller/student.ts</code></p>
<pre><code class="language-diff">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')); // 名称
}
}
</code></pre>
<h3 id="3-service">3. service</h3>
<p>首先,在模块中定义一个student service:</p>
<p><code>src/module/demo-student/src/service/student.ts</code></p>
<pre><code class="language-typescript">@Service()
export class ServiceStudent {
async findAll() {
    return await this.scope.model.student.select();
}
}
</code></pre>
<p>然后,在模块的controller中直接使用student service,支持类型提示:</p>
<p><code>src/module/demo-student/src/controller/student.ts</code></p>
<pre><code class="language-diff">export class ControllerStudent {
async findAll() {
+   return await this.scope.service.student.findAll();
}
}
</code></pre>
<h2 id="全量esm模块">全量esm模块</h2>
<p>Nest由于开发得比较早,因此仍然采用的是commonjs模块。而Vona是全新的框架,所以全量采用esm模块,项目启动更快。</p>
<h2 id="ioc容器">Ioc容器</h2>
<p>Vona和Nest都提供了Ioc容器,而Vona提供了更加快捷的容器操纵能力。我们知道Ioc容器主要提供两个操作:<strong>注册bean</strong>和<strong>获取bean</strong>。获取bean有两种方式:<strong>依赖注入</strong>、<strong>依赖查找</strong>。Vona不仅支持依赖注入,同时也提供了依赖查找,使代码书写更加直观、优雅。这里简要演示全局bean和本地bean的依赖查找:</p>
<ul>
<li>获取全局bean:jwt,并调用create方法:</li>
</ul>
<pre><code class="language-diff">export class ControllerStudent {
async findAll() {
+   const accessToken = await this.bean.jwt.create();
}
}
</code></pre>
<ul>
<li>获取本地bean:student,并调用findAll方法:</li>
</ul>
<pre><code class="language-diff">export class ControllerStudent {
async findAll() {
+   return await this.scope.service.student.findAll();
}
}
</code></pre>
<h2 id="bean配置">bean配置</h2>
<p>在Ioc语境当中,bean就是可以注入的class,包括controller、service、middleware、guard、interceptor、pipe,filter,等等。Vona提供了一个通用的机制,让我们可以为所有bean提供动态配置能力,从而显著提升整个系统的扩展性,也能够节约大量的与配置相关的代码。下面我们以middleware为例,来演示如何进行bean配置</p>
<h3 id="1-创建一个middleware">1. 创建一个middleware</h3>
<p><code>src/module/demo-student/src/bean/middleware.test.ts</code></p>
<pre><code class="language-diff">export interface IMiddlewareOptionsTest {
+title: string;
}

@Middleware&lt;IMiddlewareOptionsTest&gt;({
+title: 'hello world',
})
export class MiddlewareTest {
async execute(options: IMiddlewareOptionsTest, next: Next) {
+    console.log(options.title);
    // next
    return next();
}
}
</code></pre>
<ul>
<li>行2:定义一个参数title</li>
<li>行6:为参数提供缺省值</li>
<li>行10:在实际代码当中直接读取配置选项</li>
</ul>
<h3 id="2-使用middleware并传参">2. 使用middleware,并传参</h3>
<pre><code class="language-diff">export class ControllerStudent {
+ @Aspect.middleware('demo-student:test', { title: 'hello world!!' })
async findAll() {
}
}
</code></pre>
<ul>
<li>行2:使用装饰器@Aspect.middleware,传入middleware的名称:demo-student:test
<ul>
<li>同时可以传入新的参数配置,覆盖默认值</li>
</ul>
</li>
</ul>
<h3 id="3-通过项目config来修改middleware配置">3. 通过项目Config来修改middleware配置</h3>
<p><code>src/backend/config/config/config.local.mine.ts</code></p>
<pre><code class="language-diff">export default function () {
const config = {} as VonaConfigOptional;
// onions
config.onions = {
    middleware: {
+      'demo-student:test': {
+      title: '您好世界!!',
+      },
    },
};
</code></pre>
<ul>
<li>直接在系统Config文件中修改中间件的参数配置,支持类型提示</li>
</ul>
<h2 id="bean全局单例">bean全局单例</h2>
<p>一般而言,bean的实例化有三个层级:<strong>全局单例</strong>、<strong>Request单例</strong>、<strong>总是创建新实例</strong>。<strong>全局单例</strong>是最节约内存的。<strong>Request单例</strong>,有多少用户请求,就要创建多少个bean实例。比如,执行一个完整的api请求需要创建2个bean实例,如果1秒中有1万个请求,那么就需要创建2万个bean实例,对内存的占用非常高,也会严重影响gc的性能。可想而知,采用<strong>全局单例</strong>还是采用<strong>Request单例</strong>对系统性能的影响是非常显著的。</p>
<p>Nest对bean实例化有特殊的处理机制:在默认情况下,Controller/Service是<strong>全局单例</strong>的,但是,如果在代码中注入了与Request相关的对象,那么这些bean就会降级到<strong>Request单例</strong>。可想而知,在一个实际的业务系统中,基本上这些bean都会访问与Request相关的数据,所以基本上都是按<strong>Request单例</strong>实例化的。与Request相关的数据包括:当前语言、当前用户、当前租户,等等。</p>
<p>而Vona则是实现了完整的<strong>全局单例</strong>机制。即便这些bean实例访问了Request相关的对象,也不会降级。从原理来说,Vona底层采用了Async Local Storage机制,从而让整个系统的内存占用非常低,也能显著改善gc的性能。</p>
<h2 id="多租户">多租户</h2>
<p>Nest本身没有提供多租户的能力,社区有一些多租户的方案,但是代码不够简洁。而Vona则内置了多租户的能力,让代码更加简洁。</p>
<p><code>src/module/demo-student/src/service/student.ts</code></p>
<pre><code class="language-diff">export class ServiceStudent {
async findAll() {
+   return await this.scope.model.student.select();
}
}
</code></pre>
<ul>
<li>行3:这行代码就是查询当前租户下的所有学生信息
<ul>
<li>由此可见,租户能力是完全透明的</li>
</ul>
</li>
</ul>
<h2 id="多数据库多数据源">多数据库、多数据源</h2>
<p>Vona和Nest都支持多数据库、多数据源。此外,Vona还提供了开箱即用的<strong>读写分离</strong>和<strong>动态数据源</strong>能力。下面我们看一下在Vona中的代码体验:</p>
<h3 id="1-数据源配置">1. 数据源配置</h3>
<pre><code class="language-typescript">{
defaultClient: 'pg', // 缺省数据源
clients: { // 所有数据源清单
    pg: {
      client: 'pg', // 所使用的数据库方言
      connection: {...},
    },
    mysql: {
      client: 'mysql2',
      connection: {...},
    },
},
}   
</code></pre>
<h3 id="2-使用数据源">2. 使用数据源</h3>
<pre><code class="language-typescript">// 获取缺省数据源
const db = app.bean.database.current;
// 创建其他数据源
const dbMysql = app.bean.database.createDb({ clientName: 'mysql' });
</code></pre>
<h2 id="数据库事务">数据库事务</h2>
<p>Nest本身不考虑数据库事务的问题,而是由第三方库来提供。而Vona则内置了数据库事务。</p>
<h3 id="1-使用装饰器">1. 使用装饰器</h3>
<pre><code class="language-diff">export class ControllerStudent {
+ @Database.transaction()
async update() {
}
}
</code></pre>
<h3 id="2-手动启用">2. 手动启用</h3>
<pre><code class="language-diff">export class ControllerStudent {
async update() {
+   const db = app.bean.database.current;
+   const result = await db.transaction.begin(async () =&gt; {
+   const data = await dosomething();
+   return data;
+   });
}
}
</code></pre>
<h3 id="3-数据库事务传播机制">3. 数据库事务传播机制</h3>
<p>Vona支持事务传播机制的设置,通过 propagation 属性来指定传播行为。Vona事务传播机制有以下 7 种:</p>
<ol>
<li>Propagation.REQUIRED: 默认的事务传播级别. 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则创建一个新的事务</li>
<li>Propagation.SUPPORTS: 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则以非事务的方式继续运行</li>
<li>Propagation.MANDATORY: 强制性. 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则抛出异常</li>
<li>Propagation.REQUIRES_NEW: 创建一个新的事务. 如果当前存在事务, 则把当前事务挂起. 也就是说不管外部方法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部方法都会新开启自己的事务, 且开启的事务相互独立, 互不干扰</li>
<li>Propagation.NOT_SUPPORTED: 以非事务方式运行, 如果当前存在事务, 则把当前事务挂起(不用)</li>
<li>Propagation.NEVER: 以非事务方式运行, 如果当前存在事务, 则抛出异常</li>
<li>Propagation.NESTED: 如果当前存在事务, 则创建一个事务作为当前事务的嵌套事务来运行.如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED</li>
</ol>
<h2 id="cli命令">cli命令</h2>
<p>Vona和Nest都提供了大量cli命令,用于生成各类资源的代码骨架。</p>
<h2 id="菜单命令">菜单命令</h2>
<p>Vona在cli命令的基础上提供了大量菜单命令,通过菜单来执行cli命令,从而显著降低心智负担,提升开发体验:</p>
<h3 id="1-vona-bean">1. Vona Bean</h3>
<p><img src="https://img2024.cnblogs.com/blog/1462384/202506/1462384-20250604064217639-366680700.jpg"></p>
<h3 id="2-vona-create">2. Vona Create</h3>
<p><img src="https://img2024.cnblogs.com/blog/1462384/202506/1462384-20250604064229221-1056365382.jpg"></p>
<h3 id="3-vona-init">3. Vona Init</h3>
<p><img src="https://img2024.cnblogs.com/blog/1462384/202506/1462384-20250604064243095-1143291739.jpg"></p>
<h3 id="4-vona-meta">4. Vona Meta</h3>
<p><img src="https://img2024.cnblogs.com/blog/1462384/202506/1462384-20250604064253872-2135284228.jpg"></p>
<h3 id="5-vona-tools">5. Vona Tools</h3>
<p><img src="https://img2024.cnblogs.com/blog/1462384/202506/1462384-20250604064305358-946747389.jpg"></p>
<h2 id="env多维配置">env:多维配置</h2>
<p>Vona提供了env的多维配置能力,支持更复杂的业务场景。</p>
<p>在默认情况下,可以在Vona中提供三个场景的env配置文件:</p>
<pre><code>.env         // 缺省配置
.env.test    // 测试环境
.env.local   // 本地开发环境
.env.prod    // 生产环境
</code></pre>
<p>如果我们想针对业务的需要新增更多场景的env配置,如何操作呢?Vona引入了flavor的概念。比如,我们新增一个flavor,名称为<code>stage1</code>,那么,env配置文件如下:</p>
<pre><code>.env.stage1         // stage1的缺省配置
.env.stage1.test    // stage1的测试环境
.env.stage1.local   // stage1的本地开发环境
.env.stage1.prod    // stage1的生产环境
</code></pre>
<h2 id="config多维配置">Config:多维配置</h2>
<p>Vona同样也提供了Config的多维配置能力,支持更复杂的业务场景。</p>
<pre><code>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
</code></pre>
<h2 id="单文件打包集群部署">单文件打包、集群部署</h2>
<p>Vona提供了开箱即用的单文件打包和集群部署能力。Nest虽然也能支持这些能力,但是仍然需要花点功夫进行配置。</p>
<h3 id="1-单文件打包">1. 单文件打包</h3>
<pre><code class="language-bash">$ npm run build
</code></pre>
<ul>
<li>执行此命令,将在dist目录生成唯一的js文件</li>
</ul>
<h3 id="2-集群部署">2. 集群部署</h3>
<pre><code class="language-bash">$ npm run start
</code></pre>
<ul>
<li>执行此命令,将基于cpu数量启动多个进程,进行集群部署</li>
</ul>
<h3 id="2-单进程模式">2. 单进程模式</h3>
<pre><code class="language-bash">$ npm run start:one
</code></pre>
<ul>
<li>执行此命令,将启动单进程,可直接用于docker环境</li>
</ul>
<h2 id="aop面向切面编程">aop:面向切面编程</h2>
<p>在Vona中,将aop编程能力分为三个层次:<strong>前置切面</strong>、<strong>主体切面</strong>、<strong>客体切面</strong>。</p>
<h3 id="1-前置切面">1. 前置切面</h3>
<p>前置切面,就是在执行Controller Action之前切入逻辑,包括:Middleware、Guard、Pipe、Intercepter,Filter,等等。</p>
<h3 id="2-主体切面">2. 主体切面</h3>
<p>主体切面,就是在任何Class的任何Action之前切入逻辑。比如,我们为更新数据的方法切入一个数据库事务:</p>
<pre><code class="language-diff">export class ControllerStudent {
+ @Database.transaction()
async update() {
}
}
</code></pre>
<h3 id="3-客体切面">3. 客体切面</h3>
<p>客体切面,就是在不改变源码的前提下,在任何Class的任何Action之前切入逻辑:</p>
<p>首先,我们先定义一个bean:</p>
<pre><code class="language-typescript">@Bean()
export class BeanTest {
actionSync() { }

async actionAsync() { }
}
</code></pre>
<p>然后,创建一个aop bean:</p>
<pre><code class="language-typescript">@Aop({ match: 'test')
export class AopTest {
actionSync(_args, next) {
    const result = next();
    return result;
}

async actionAsync(_args, next) {
    const result = await next();
    return result
}
}
</code></pre>
<ul>
<li>行2:定义一个aop bean</li>
<li>行1:通过match参数,将AopTest与BeanTest建立关联
<ul>
<li>当系统在执行BeanTest的方法时,会自动将AopTest提供的方法切入</li>
<li>支持<strong>同步方法</strong>和<strong>异步方法</strong></li>
</ul>
</li>
</ul>
<h2 id="ssr聚合">ssr聚合</h2>
<p>现在ssr渲染很火,但往往用于信息展示类的前台网站,后台admin系统则很少采用,主要原因就是整合有点难度,需要考虑的边界问题比较多,性价比不高。但是,Vona提供了开箱即用的ssr渲染能力,同时支持前台网站,和后台admin系统。</p>
<h2 id="demo练习场">demo练习场</h2>
<p>在实际开发当中,我们经常需要写一些临时代码,对一些想法和思路进行验证和评估。Nest提供了REPL机制,采用命令行交互模式。Vona专门提供了demo练习场,让我们可以非常方便的进行代码演练。下面,我们简要演示一下:</p>
<h3 id="1-书写demo">1. 书写demo</h3>
<p><code>src/backend/demo/index.ts</code></p>
<pre><code class="language-diff">export async function main(app: VonaApplication, argv: IArgv) {
... do something
}
</code></pre>
<ul>
<li>通过app可以调用系统所有能力</li>
<li>通过argv接收命令行参数</li>
</ul>
<h3 id="2-执行demo">2. 执行demo</h3>
<pre><code class="language-bash">$ npm run demo
</code></pre>
<ul>
<li>执行此命令,就会自动初始化一个<code>app</code>实例,然后执行<code>demo/index.ts</code>文件的<code>main</code>方法</li>
</ul>
<h2 id="结语">结语</h2>
<p>Vonajs作为全新的Node.js框架,顺应技术趋势,大胆使用新技术,提出了许多新的架构设计思路,通过一系列的微创新,解决开发当中的痛点问题,让我们的代码更加直观、优雅,从而能轻松应对大型项目的开发与维护。通过对Vonajs特性的简要介绍,大家是否认为会比nestjs更好用呢?</p>
<p>欲了解更多有关Vonajs的信息,参见B站:濮水代码</p>
<p>Github: https://github.com/vonajs/vona</p><br><br>
来源:https://www.cnblogs.com/zhennann/p/18909435
頁: [1]
查看完整版本: 你认为Vonajs提供的这些特性会比Nestjs更好用吗?