Nodejs中比nestjs更优雅的ts控制反转策略-依赖查找

发布于 1周前 作者 itying888 来自 nodejs/Nestjs

一、Cabloy5.0 内测预告

Cabloy5.0 采用 TS 对整个全栈框架进行了脱胎换骨般的大重构,并且提供了更加优雅的 ts 控制反转策略,让我们的业务开发更加快捷顺畅

1. 新旧技术栈对比:

后端 前端
旧版 js 、egg2.0 、mysql js 、vue2 、framework7
新版 ts 、egg3.0 、多数据库兼容(支持 mysql 、postgresql ) ts 、vue3 、quasar

2. 框架开发两大趋势

  1. TS 化:这是显而易见的趋势,不必赘言

  2. ESM 化:从目前趋势看,前端框架已经全链路 ESM 化了,但是,大多数后端框架仍然是 Commonjs 。即便是 egg3.0 也仍然是 Commonjs 。由于 egg 的定位仍然是元框架,CabloyJS5.0 在 egg 基础上仍然开发出了全量 ESM 化的业务模块化系统(在 Commonjs 之上进行 ESM 化的具体机制是什么,另有文章阐述)

二、比 nestjs 更优雅的 ts 控制反转策略

基于 TS 的后端框架一般都会提供依赖容器,实现控制反转。控制反转有两种策略:依赖注入依赖查找。CabloyJS5.0 同时支持依赖注入依赖查找,并且通过模块范围的辅助,让依赖查找的代码更加简洁高效,下面挑几个特性举例说明:

  1. Service 服务
  2. Config 配置
  3. 多语言
  4. 错误异常

三、Service 服务

1. 创建一个 Service

在 CabloyJS 中,local bean 相当于 nestjs 中 service 的概念,下面创建一个 local bean

import { BeanBase, Local } from '[@cabloy](/user/cabloy)/core';
import { ScopeModule } from '../resource/this.js';

@Local() export class LocalHome extends BeanBase<ScopeModule> { async echo({ user: _user }) { return Hello World!; } }

  1. 通过[@Local](/user/Local)声明 LocalHome 是一个 local bean

  2. LocalHome 继承自基类 BeanBase

2. Service 的依赖注入

接下来,在 Controller 中采用依赖注入的方式来使用 LocalHome

import { BeanBase, Controller, Use } from '[@cabloy](/user/cabloy)/core';
import { ScopeModule } from '../resource/this.js';
import { LocalHome } from '../local/home.js';

@Controller() export class ControllerHome extends BeanBase<ScopeModule> { @Use() home: LocalHome;

async echo() { const res = await this.home.echo({ user: this.ctx.state.user.op, }); this.ctx.success(res); } }

  1. 使用[@Use](/user/Use)注入 LocalHome

3. Service 的依赖查找

接下来,在 Controller 中采用依赖查找的方式来使用 LocalHome

import { BeanBase, Controller } from '[@cabloy](/user/cabloy)/core';
import { ScopeModule } from '../resource/this.js';

@Controller() export class ControllerHome extends BeanBase<ScopeModule> { async echo() { const res = await this.scope.local.home.echo({ user: this.ctx.state.user.op, }); this.ctx.success(res); } }

  1. 不需要导入 LocalHome ,直接在代码中使用this.scope.local来访问容器中的 local bean 实例

看一下动画演示,提供了完整的类型智能提示:

lookup-config.gif

四、Config 配置

1. 定义 Config

可以为业务模块单独定义一些 Config 配置,如下:

import { CabloyApplication } from '[@cabloy](/user/cabloy)/core';

export const config = (_app: CabloyApplication) => { return {

  • prompt: ‘hello world’, }; };

2. 使用 Config

可以在 LocalHome 中直接使用刚才定义的 config

import { BeanBase, Local } from '[@cabloy](/user/cabloy)/core';
import { ScopeModule } from '../resource/this.js';

@Local() export class LocalHome extends BeanBase<ScopeModule> { async echo({ user: _user }) {

  • return this.scope.config.prompt;
  • return Hello World!; } }
  1. 不需要导入任何类型,直接在代码中使用this.scope.config来访问当前业务模块中的 config 配置

看一下动画演示,提供了完整的类型智能提示:

lookup-config.gif

五、多语言

1. 定义语言资源

可以为业务模块定义语言资源,比如,这里分别定义英文和中文两种语言资源

英文

export default {
  HelloWorld: 'Hello World',
};

中文

export default {
  HelloWorld: '您好世界',
};

2. 使用语言资源

可以在 LocalHome 中直接使用刚才定义的语言资源

import { BeanBase, Local } from '[@cabloy](/user/cabloy)/core';
import { ScopeModule } from '../resource/this.js';

@Local() export class LocalHome extends BeanBase<ScopeModule> { async action({ user: _user }) {

  • // 自动判断当前语言
  • const message = this.scope.locale.HelloWorld();
  • // 强制使用英文资源
  • const message1 = this.scope.locale.HelloWorld.locale(‘en-us’);
  • // 强制使用中文资源
  • const message2 = this.scope.locale.HelloWorld.locale(‘zh-cn’);
  • return ${message}:${message1}:${message2};
  • return this.scope.config.prompt; } }
  1. 不需要导入任何类型,直接在代码中使用this.scope.locale来访问当前业务模块中的语言资源

看一下动画演示,提供了完整的类型智能提示:

lookup-locale.gif

六、错误异常

1. 定义错误码

可以为业务模块定义错误码

export enum Errors {
+ Error001 = 1001,
}
  1. 这里定义了一个错误枚举类型 Error001 ,对应的错误码是 1001

2. 定义错误码对应的语言资源

可以为错误码定义语言资源,比如,这里分别定义英文和中文两种语言资源

英文

export default {
+ Error001: 'This is a test',
  HelloWorld: 'Hello World',
};

中文

export default {
+ Error001: '这是一个错误',
  HelloWorld: 'Hello World',
};

3. 抛出错误异常

可以在 LocalHome 中直接使用刚才定义的错误枚举值,并抛出异常

import { ScopeModule } from '../resource/this.js';

@Local() export class LocalHome extends BeanBase<ScopeModule> { async action({ user: _user }) {

  • // 直接抛出异常
  • this.scope.error.Error001.throw();
  • return this.scope.config.prompt; } }
  1. 不需要导入任何类型,直接在代码中使用this.scope.error来访问当前业务模块中的错误枚举值

看一下动画演示,提供了完整的类型智能提示:

lookup-error.gif

六、后记

Cabloy4.0 中就已经提供了大量业务能力,比如:工作流引擎、表单引擎、权限引擎、字段权限、多级缓存、模块化体系、分布式架构、多租户引擎,等等。随着 Cabloy5.0 Typescript 的赋能,这些业务能力也随之有了全新的表现

欲了解更多,请关注每晚 8 点 B 站直播:濮水代码


Nodejs中比nestjs更优雅的ts控制反转策略-依赖查找

8 回复

看起来用法没有 tegg 简洁


好像 Java 😂

对这些概念完全不感冒,我信仰 FP

大佬信仰 fp 的用哪个后端框架最好?

后端 FP 的话看看 Vert.X 、Spring Webflux ?不过不支持 js 就是了,Java 、kotlin 、Groovy 语言

感谢,我看一下。我现在的选择是 f#……

看 react hooks 去继承化是趋势,使用继承会导致 JS 的内存对象原型链依赖过长,不利于内存的快速回收,加大内存泄露的风险
都用上装饰器了,像 extend BeanBase<ScopeModule> 其实都可以写进装饰器里。否则就太像 JAVA 了

在Node.js生态系统中,TypeScript(TS)结合控制反转(IoC)策略能够显著提升代码的可维护性和可扩展性。虽然NestJS作为一个流行的框架提供了强大的IoC容器,但如果你追求更轻量级、更优雅的TS控制反转策略,可以考虑依赖查找(Dependency Lookup)模式。

以下是一个使用原生TypeScript和简单的依赖查找策略的示例:

// 定义一个接口
interface IService {
  execute(): string;
}

// 实现该接口的服务
class MyService implements IService {
  execute(): string {
    return "Service Executed";
  }
}

// 使用依赖查找的类
class MyController {
  private service: IService;

  constructor(service: IService) {
    this.service = service;
  }

  run(): string {
    return this.service.execute();
  }
}

// 创建依赖并注入
const service = new MyService();
const controller = new MyController(service);

console.log(controller.run()); // 输出: Service Executed

在这个例子中,我们定义了一个接口IService,并通过MyService类实现它。MyController类在构造函数中接收一个IService实例,实现了依赖的显式注入。这种方式虽然不如NestJS的依赖注入框架那样自动化,但它提供了更高的灵活性和轻量级特性,尤其是在小型项目中。

对于更复杂的项目,你可能需要引入更高级的IoC库(如InversifyJS),但在简单场景下,这种手动的依赖查找方式更加直接和优雅。

回到顶部