什么是 Midway
Midway(中途岛)品牌是淘宝技术部(前淘宝 UED)前端部门研发的一款基于 Node.js 的全栈开发解决方案。它将搭配团队的其他产品,Pandora 和 Sandbox,将 Node.js 的开发体验朝着全新的场景发展,让用户在开发过程中享受到前所未有的愉悦感。
Midway 基于 阿里 Egg.js 框架二开,将 IoC 引入到框架中,借鉴 Nest.js,引入丰富的装饰器方法,提升开发中的用户体验。
midway 的一些特性如下。
依赖注入( IoC )
首先想说说 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称 DI),还有一种方式叫“依赖查找”(Dependency Lookup)。
通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。通俗地来说,有点像上京东或者淘宝购买商品,你只需要在搜索框中输入你要购买的商品,可以选择它的分类、品牌、价格等参数,然后软件就会给你提供你心仪的商品,你只需要下单、购买、等着收货即可。简单明了,如果软件给你推荐的结果,你并不满意,我们就会抛出异常,整个过程无需你自己控制,而是电商平台类似容器的结构来控制。
所有的商品都会在电商平台中注册,你只需要告诉 Ioc 你是什么东西,你要买什么东西,然后 Ioc 就会在系统运行到你需要的时候,将你要的东西传递给你。同时也将你售卖的东西,交付给其他需要的地方,所有商品的创建、配送、销毁,全部由 IoC 控制。这就是控制反转。

Midway 框架采用 injection 这个 Npm 包做 IoC 控制,这个包本身也是淘宝中途岛团队自主开发的,实现了依赖注入。
基于 Egg.js
Midway 是基于阿里开源的另一款 Node 框架 Egg.js 为基础开发的。
Egg.js 号称为企业级框架和应用而生,Egg 采用“约定优于配置”,规划了一套统一的约定,进行开发,我司现有产品采用 Egg.js 进行开发迭代,Egg.js 约束了一套目录规范和开发规范和插件规范,减少因为人的差异导致项目规范混乱,Egg.js 有很高的可扩展性,使用 Egg.js 提供的 loader 机制可以让框架根据开发者自己的规划,定义默认配置,亦可覆盖 Egg.js 的默认配置。
因为 Egg.js 的这种高度可扩展性,给开发者提供了基于 Egg.js 封装上层框架的能力,并且 Egg.js 插件高度可扩展,并且现有生态较为完备,并且兼容 koa 的插件,生态环境非常优异。Egg.js 通过 Node 官方的 Cluster 模块,实现多进程模型,充分利用多核心 CPU 性能。另外 Egg.js 在淘宝双十一,和阿里绝大部分的 Web 系统中的表现,充分的说明了,Egg.js 优异的稳定性。
个人觉得 Midway 选择 Egg.js 的原因有以下几点:
- 底层基于 koa,提过封装上层框架能力;
- 双十一的并发量都可以支撑住(不要给我说,你们平台的流量能够比淘宝天猫双十一还要高);
- 兼容 koa 插件,Egg.js 插件也有几百个,一些使用率很高的框架由官方维护,例如,egg-mongoose、egg-sequelize 等等;
- 阿里维护的项目,并且阿里自身也在深度使用;
- 问题回答的效率,首先是国内的框架,你可以用中文提交 issue,另外就是回复效率很高(本人提过几个 issue,基本上一小时内就回复了,不知道是不是我问题简单的原因)。
采用 Typescript
TypeScript是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。安德斯·海尔斯伯格,C# 的首席架构师,已工作于 TypeScript 的开发。
- TypeScript 扩展了 JavaScript 的语法,所以任何现有的JavaScript 程序可以不加改变的在 TypeScript 下工作。
- TypeScript 是为大型应用之开发而设计,而编译时它产生 JavaScript 以确保兼容性。
- TypeScript 支持为已存在的 JavaScript 库添加类型信息的头文件,扩展了它对于流行的库如 jQuery、MongoDB、Node.js 和 D3.js 的好处。并且我们经常使用的宇宙第一编辑器 Visual studio code 也是 Typescript 开发的。
- Typescript 可以使用 Javascript 中的所有代码和概念,Typescript 是为了 JavaScript 开发更加容易而诞生的,Typescript 只从语义核心方面对 JavaScript 原有模型进行扩展,所以 JavaScript 可以无需修改,或少量修改即可和 Typescript 同时工作,也可以使用编译器将 Typescript 转换为 JavaScript 代码。
- Typescript 通过类型注解,提供编译时的静态检查,并且为函数提供了缺省参数,并且引入了模块的概念,可以把声明、数据、函数和类封装在模块中。使用 Typescript 相较于 JavaScript 有以下显著优势:
- 静态检查
- 大型项目的规范,使用 Typescript 更容易重构
- 协作能力
- 生产力,干净的 ES6 代码,自动完成和动态输入提高了开发者的工作效率
提供多种装饰器
因为 Midway 使用 Typescript 开发,所以支持装饰器方法。在 Java 开发中,装饰器模式非常常见,通过装饰器方法可以向一个现有的对象添加新的功能,并且不会改变对象的结构,装饰器和被装饰的对象可以独立,不会相互耦合。
举例说明,例如 Midway 提供了路由装饰器,可以直接通过装饰器方法声明路由。代码示例如下(代码来源于官方示例):
import { provide, controller, inject, get } from 'midway';
@provide()
@controller('/user')
export class UserController {
@inject('userService')
service: IUserService;
@get('/:id')
async getUser(ctx): Promise<void> {
const id: number = ctx.params.id;
const user: IUserResult = await this.service.getUser({id});
ctx.body = {success: true, message: 'OK', data: user};
}
}
如上代码使用
@controller声明这个类为控制器类,同时通过标注请求,声明了请求的方法,除了
@get以外,Midway 还基于 koa-router 封装了
@post、
@del、
@put、
@patch、
@options、
@head、
@all,其他装饰器方法的示例可以通过 Midway 官方文档查看,本文只做简要介绍,后续本人可能会写一系列的 Midway 实战教程,敬请期待。
浅谈我司现有产品的技术痛点
我司基于 Egg.js 开发了一套通用 OA 产品(持续迭代中),在实际开发过程中,体验还是不错,但是也有一些开发体验不好的地方,因为使用 JavaScript 所以自动补齐,智能提示基本上是无法使用,虽然官方或个人提供了插件但是使用起来,总是有些不尽如人意,另外我们的路由声明、多文件 多文件夹,维护起来很麻烦:比如说我要写一个新的接口,我需要先去 router 文件夹里面创建一份路由文件,然后在 /app/router.js 文件里引用这个文件。
然后再去控制器里面创建文件,编写方法,好几个文件,来回切换。所以我觉得使用装饰器方法声明路由还是很方便的。最开始尝试过使用第三方插件实现装饰器注册路由,但是体验不是很好,后续就没有继续使用了。
另外就是 JavaScript 语言本身的痛点,代码规范、接口声明、类型效验问题、多人同时开发,每个人的开发习惯都不相同,所以长期迭代维护的项目可能会因为每个人的习惯不同,可维护性逐渐降低,我们现在的私有化部署项目,很多低级 Bug 都是因为类型不对,数据结构不对等之类的引起。
如果真的要维护这个项目几年,JavaScript 灵活度很高,所以项目前期可以使用 JavaScript 构建,以换取开发效率高,但是我觉得如果不进行规范约束,后期几乎不可维护。从企业产品角度出发,因为 Typescript 面向对象编程语言的结构,保持了代码的整洁度,代码规范的一致性,因此我个人觉得,在企业项目中使用 Typescript 更加适合。如果只是小型项目或个人项目,JavaScript 更适合灵活开发,开发效率更高。不过一切技术的选择都要从实际场景出发。任何不从实际场景出发的技术选型都是耍流氓。
使用 Midway 的意义
根据之前讲的 Midway 的特性,我简单总结了一下,使用 Midway 带来的好处,有以下几点。
- 使用 Midway 学习成本低,如果你之前使用过阿里的 Egg.js 框架,基本上不需要怎么学习,即可用于实际工作中
- 各种装饰器方法提升开发效率
- 使用 Typescript 使用
- 使用 Ioc,优化项目依赖管理
- 底层基于 Egg.js 兼容 Egg.js 的所有生态
- 采用 Typescript,强类型,面向接口编程
- 提供装饰器方法,简化开发
如何从 Egg 平稳迁移到 Midway
将项目从一个框架,迁移到另一个框架,并不是一件简单的事,不过把 Egg 项目重构为 Midway 还算是没有什么特别的困难,首先因为 Midway 基于 Egg.js 所以之前项目中使用的 egg 插件或 koa 插件,可以无需修改,或者少量修改,即可在 Midway 中使用,另外因为 Midway 的很多方法与 Egg.js 保持一致,所以大部分你在 Egg.js 中使用的方法,亦可在 Midway 中继续使用,目录结构这部分,与 Egg.js 大致相同,不过 Midway 在其基础上,重新对项目结构分层,将项目分为 Web 层和业务逻辑层:
├── README.md
├── README.zh-CN.md
├── dist ---- 编译后目录
├── logs ---- 本地日志目录
│ └── midway6-test ---- 日志应用名开头
│ ├── common-error.log ---- 错误日志
│ ├── midway-agent.log ---- agent 输出的日志
│ ├── midway-core.log ---- 框架输出的日志
│ ├── midway-web.log ---- koa 输出的日志
│ └── midway6-test-web.log
├── package.json
├── src ---- 源码目录
│ ├── app ---- web 层目录
│ │ ├── controller ---- web 层 controller 目录
│ │ │ ├── home.ts
│ │ │ └── user.ts
│ │ ├── middleware (可选) ---- web 层中间件目录
│ │ │ └── trace.ts
│ │ ├── public (可选) ---- web 层静态文件目录,可以配置
│ │ ├── view (可选)
│ │ | └── home.tpl ---- web 层模板
│ ├── config
│ │ ├── config.default.ts
│ │ ├── config.local.ts
│ │ ├── config.prod.ts
│ │ ├── config.unittest.ts
│ │ └── plugin.ts
│ └── lib ---- 业务逻辑层目录,自由定义
│ │ └── service ---- 业务逻辑层,自由定义
│ │ └── user.ts
│ ├── interface.ts ---- 接口定义文件,自由定义
│ ├── app.ts ---- 应用扩展文件,可选
│ └── agent.ts ---- agent 扩展文件,可选
├── test
│ └── app
│ └── controller
│ └── home.test.ts
├── tsconfig.json
└── tslint.json
另外就是在使用 Typescript 后,开发者需要编写接口定义,和声明文件,不过我相信,大家学习一些 Typescript 知识以后,这些都不是问题。另外,是否需要重构项目也要从实际情况出发,应先实际调研,当前项目是否规范混乱,难以维护,另外就是重构的意义是否大于重构的成本。如果你的项目还没有开始实施,或刚刚开始实施,如果你想使用 Typescript 作为产品语言,我觉得 Midway 可以作为你的框架选型之一,另外可能有些人会说,如果我都要写 Typescript 了,为什么我不用 Nest.js 呢?我觉得框架选型还是要从实际出发,你就直接使用,而是要看,这个框架的性能,稳定性,维护团队,未来规划等等方面出发,我选择 Egg.js,选择 Midway 首先是因为它的维护团队是阿里巴巴,性能稳定,另外就是有 IoC 机制,优化了开发体验。