双旦已过, 新年将至, midwayJs 向大家献上贺礼.
之前我们向社区开放了我们的治理工具, 也就是 Pandora.JS 工具包, 用于整个 Node.JS 应用的监控和治理, 我们承诺这不是结束, 只是开源的开始.
随着内部全栈应用数的越来越多, 以及阿里业务不断提升的复杂度, 比如店铺, 搭建以及渲染等服务, 随着人员的不断调动, 产品的结构, 代码的层级都随着不断的调整, 我们急需一个能降低代码复杂度的解决方案, 帮助我们渡过人员寒冬, 这就对我们内部的基础架构体系提出了不同的要求.
以往我们只需要让用户启动服务器, 满足 RPC/HTTP 服务即可, 而在真正的全栈领域, 似乎没有太多的钻研和沉淀. 对此, 我们将内部使用的 midway 整体解决方案进行了一次重塑, 并且在设计之初就提出未来将对外进行开源.
正巧我们的第一款 Typescript 产品 Pandora.JS 开源完毕, 给了我们将代码用 Typescript 重写的信心, 也随着 Egg.JS 社区的壮大, 我们相信, 在不同的领域中, 一定会有不同的产品, 不同的解决方案.
Midway 正式基于这些考虑, 将 IoC 引入到了框架中, 同时学习了 NestJs , 引入了不少自定义的装饰器, 增强开发体验, 也将搭配团队的其他产品, Pandora.JS 和 Sandbox, 将 Node.JS 的开发体验朝着全新的场景发展, 让用户在开发过程中享受到前所未有的愉悦感.
在这里感谢前期的 beta 测试中向我们提意见以及试用的同学, 感谢大家的包容和支持, 特别是 @ZQun 和 @yuu2lee4 两位的积极参与.
下面来介绍新版本 midway 的一些特性.
基于 IoC 体系业务代码进行解耦, 依赖统一管理统一初始化
常见的 web 场景装饰器简化业务开发
支持 Egg.JS 的所有插件体系, 框架装饰器统一编码风格
基于 Typescript , 面向接口编程的编码体验
依赖注入疑问
在一年前, 我们的业务代码是重重耦合, 到处初始化, 实例重复, 但这并不是业务同学在代码架构方面的问题, 而是在不断的业务迭代, 交接下, 早就脱离了最初的设想, 代码的设计跟不上需求的速度.
为此, 我们尝试引入了依赖注入的方案. 依赖注入最早听到是在 Java 端的 spring 框架, 在 JS 方面, 最早我们使用了 xml 做为基础的 IoC 方案, 虽然解决了不少耦合和初始化的问题, 也发现前端在 xml 的感受吐槽颇多.
去年 Typescript 的大力发展之后, 内部的很多项目都切换了过来, 经过我们的调研, 除了 NestJs 进行了自研以及在 Typescript 领域比较出名的 Inversify 模块, 似乎很少有现成的易于扩展的模块.
基于这些情况, 我们进行了这方面的自建, 一方面方便内部的扩展, 能更好的在现有的体系上扩展装饰器, 请求作用域等, 另一方面也可以提升本身的能力, 方便后续迭代.
我们产出了 injection 模块, 作为我们整个框架的依赖注入基础.
如今, injection 承载起了整个 midway 体系, 它将框架代码, 业务代码, 插件等都组合到了一起, 像一个纽带在这些之间传输数据.
通过依赖注入容器的管理, 如上图非常复杂的应用也能良好的维护和运作.
想看完整大图, 可以点击这里.
面向装饰器开发
得益于 Typescript 对 ES6 的良好支持, 提供了一种为类声明和成员添加注释和元编程语法的方法. 装饰器作为 TypeScript 的实验性功能能够让我们在开发中简化代码. 虽然是语法糖, 但是带来的好处却不少.
我们拿一个简单的例子, 从 Controller 一步步经过 Service/Manager 向数据库拿数据, 在多层的架构体系下, 以往的代码大概率需要 new 出不同的实例, 并且需要绑定到路由层, 这边为了方便理解, 代码放到了一起.
- export = (App) => {
- const home = new HomeController();
- App.get('/', home.index);
- }
- class HomeController extends Controller {
- reportService: IReportService;
- constructor() {
- this.reportService = new ReportService();
- }
- async index(ctx) {
- ctx.body = await this.reportService.getReport();
- }
- }
- class ReportService implements IReportService {
- reporter: IReportManager;
- constructor() {
- this.reporter = new ReporterManager();
- }
- async getReport(id: number) {
- return await this.reporter.get(id);
- }
- }
- class ReporterManager implements IReportManager {
- db;
- constructor() {
- this.initDB();
- }
- initDB() {
- // open connection
- }
- async get() {
- // return data from db;
- }
- }
经过 IoC 相关的 @provide 和 @inject 装饰器修饰以及其他 Web 层的装饰器修饰过后, 不仅仅只是代码量的减少, 业务的代码也不再有实例化的过程. 以往还需要考虑在构造器中做异步的操作, 比如初始化时需要做异步连接数据库, 这个时候也不再需要考虑, 直接使用 @init 装饰即可.
至此, 我们会更加专注于面向接口进行编程, 抽象, 将代码设计的时间更多的花在理解需求, 解决问题上.
- @provide()
- @controller()
- export class HomeController {
- @inject()
- reportService: IReportService;
- @get('/')
- async index(ctx) {
- ctx.body = await this.reportService.getReport();
- }
- }
- @provide()
- class ReportService implements IReportService {
- @inject()
- reporter: IReportManager;
- async getReport(id: number) {
- return await this.reporter.get(id);
- }
- }
- @provide()
- class ReporterManager implements IReportManager {
- @inject()
- db;
- @init()
- initDB() {
- // open connection
- }
- async get() {
- // return data from db;
- }
- }
入口能力
就像上面提到的 @controller 装饰器类似, 针对入口型的代码, 我们在框架层面扩展了其他装饰器, 比如针对计划任务形式我们提供了 @schedule 装饰器, 简化用户开发的代码量.
- import { schedule } from 'midway';
- @schedule({
- interval: 2333, // 2.333s 间隔
- type: 'worker', // 指定某一个 worker 执行
- })
- export class HelloCron {
- // 定时执行的具体任务
- async exec(ctx) {
- ctx.logger.info(process.pid, 'hello');
- }
- }
在下一版本中, 我们将开放自定义装饰器的能力, 方便更多场景的使用.
框架扩展
由于在大多数场景下, 使用了装饰器已经依赖注入的写法, 使得自己的业务代码, 乃至三方的模块都能很好的融在一起, 除了这些之外, 有的同学会疑问, 原本的插件, 配置, 上下文部分如何融入到这个体系, 我们这就来解答.
在原本熟悉的体系中, 只要有 App , ctx 对象就无敌了, 所有的东西都可以拿. 而在 midway 中, 为了和 Web 层进行解耦, 我们隐去了这些对象, 只希望业务代码和 IoC 容器打交道.
为此我们提供了 @config 和 @plugin 装饰器用于获取不同的方法, 通过这样的形式和框架进行解耦, 比如在任意代码中如下使用.
- @provide()
- class ReportService implements IReportService {
- @config('env')
- env;
- @plugin('httpclient')
- httpclient;
- @inject()
- reporter: IReportManager;
- async getReport(id: number) {
- const rid = this.httpclient.request('/api/' + id);
- return await this.reporter.get(rid);
- }
- }
正是这样一点点的调整, 我们将整个应用的代码风格保持了到了一致, 不管代码几经易手, 维护的同学也能快速上手, 并且继续迭代下去.
最后
正向我们在 Pandora.JS 发布时说的那样, midway 也是 MidwayJs 团队长期维护的一款产品, 同样不会是最后一款, 前几个月, 我们就计划将我们的监控平台 Sandbox 带出来回馈给社区, 虽然道阻且长, 任务艰辛, 我们依旧在努力前行, 欢迎关注.
最后, https://github.com/midwayjs/pandora/ 的地址在这 https://github.com/midwayjs/midway/ , 归属在 midwayJs Group 下. 欢迎走过路过点个 Star, 给我们提提建议, 提提代码.
Midway 官网: https://midwayjs.org/midway/
来源: https://yq.aliyun.com/articles/686000