V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zhennann
V2EX  ›  Node.js

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

  •  
  •   zhennann · 24 天前 · 1232 次点击

    一、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/core';
    import { ScopeModule } from '../resource/this.js';
    
    @Local()
    export class LocalHome extends BeanBase<ScopeModule> {
      async echo({ user: _user }) {
        return `Hello World!`;
      }
    }
    
    1. 通过@Local声明 LocalHome 是一个 local bean

    2. LocalHome 继承自基类 BeanBase

    2. Service 的依赖注入

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

    import { BeanBase, Controller, Use } from '@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注入 LocalHome

    3. Service 的依赖查找

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

    import { BeanBase, Controller } from '@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/core';
    
    export const config = (_app: CabloyApplication) => {
      return {
    +   prompt: 'hello world',
      };
    };
    

    2. 使用 Config

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

    import { BeanBase, Local } from '@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/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 站直播:濮水代码

    7 条回复    2024-04-07 12:36:11 +08:00
    isbase
        1
    isbase  
       24 天前
    看起来用法没有 tegg 简洁
    zachwei
        2
    zachwei  
       24 天前
    好像 Java 😂
    iPhone15
        3
    iPhone15  
       24 天前 via iPhone   ❤️ 2
    对这些概念完全不感冒,我信仰 FP
    uni
        4
    uni  
       24 天前
    @iPhone15 大佬信仰 fp 的用哪个后端框架最好?
    mmdsun
        5
    mmdsun  
       24 天前 via iPhone   ❤️ 1
    @uni 后端 FP 的话看看 Vert.X 、Spring Webflux ?不过不支持 js 就是了,Java 、kotlin 、Groovy 语言
    uni
        6
    uni  
       23 天前
    @mmdsun 感谢,我看一下。我现在的选择是 f#……
    newghost
        7
    newghost  
       23 天前
    看 react hooks 去继承化是趋势,使用继承会导致 JS 的内存对象原型链依赖过长,不利于内存的快速回收,加大内存泄露的风险
    都用上装饰器了,像 extend BeanBase<ScopeModule> 其实都可以写进装饰器里。否则就太像 JAVA 了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2626 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 13:59 · PVG 21:59 · LAX 06:59 · JFK 09:59
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.