去年年初对 Node.JS 比较感兴趣, 也用了很多 Node.JS 的框架, 但是开发体验不是特别好, 我之前也是后端转前端, 然后再接触 Node.JS , 所以用过挺多的服务端框架, 相对 JS 而言, 设计一款服务端框架并不容易, 本人也不太愿意使用 typescript (为什么不用 java, 请勿吐槽)编写并且基于 ES6 对入门的小伙伴会更友好一些, 然后自己动手开发了一个 Node.JS 的 web 框架, 快过年了才有时间写文章(手动狗头), 在这里给大家分享一下开发经历.
注: 目录只是为了好看, 想到什么写什么, 没有文笔可言, 小白文.
选型
对于框架底层, 想过自己开发一套 (成本太高, 并且考虑到生态问题) 被我否决了, 然后比较了 Koa2 与 Express 最终选择 Koa2 作为默认底层(最后由于框架的架构设计, koa2 服务或者 express 都可以作为底层库), 不过最后还是选择了 koa2 默认集成.
既然选择了 koa2 那自然也是兼容 koa2 的生态圈
架构设计
刚开始开发的时候其实是顺着 koa 的路子走的, 以 koa 作为底层, 对 koa 的 ctx 进行扩展, 后来觉得这样子封装一个 koa 的全家桶貌似没什么意义(晚上一大堆, 造轮子没什么意义, 和别的框架有啥区别, 请原谅我这老土的想法), 然后开始思考: 做这个框架的初衷和意义.
作为一个后端过来的, 自然就想到了用 IoC 容器作为底层更优雅, 但是 JS 并没有类型约束, 接口等特性, 也看过很多 typescript 的实现 (和其他后端框架并无明显区别, 不是我吐槽, 其他语言的更完善更安全性能更好), 我下定决心要写一个 JS(ES6) 版的出来(学习成本低, 更好入门就是这么自以为是), 然后就这样开启了我的 Node.JS 之旅.
写偏了.. 下面介绍一下这个框架的架构:
以容器作为底层, 应用类集成容器基类并绑定在容器中, 是应用程序对象也是容器的保姆.
其他所有的服务 (包括 koa ,router,logger,validate,request,response 等等) 都是以提供者的形式在应用程序中注册(实际绑定到了容器).
容器开发
前期没想那么多, 开发容器也很顺利, 在设计依赖注入模式的时候(由于没有接口), 想躲过很多方案, 最后决定使用装饰器(真香), 不是 ts, 使用 ECMA 草案中装饰器(使用 babel 转码), 最后 1.0 定稿以后, 会成为可选方案, 装饰器可以增加开发体验, 但不是必须的, 并且强烈推荐的模式进行设计.
例如我们开放一个端点(路由), 装饰器例子, 不是注入:
- @Router('users')
- class UserController {
- @Get()
- index() {
- // ...
- }
}
这种模式进行开发, 上述例子开放了一个 GET /users 的访问端点
那如果进行注入呢:
- class UserController {
- @Config() config;
- @Request()
- index(request) {
- this.request.param()
- this.config.get('app.port')
- }
}
我们可以对属性, 方法, 构造函数进行注入
原理是使用装饰器标记控制器属性方法等需要注入的参数, 然后调用函数的时候从容器中取出(这里碰到个坑), 由于 http 服务请求的上下文在回调函数中, 所以我绑定了一个回调函数到容器中, 需要获取实例的时候将上下文传入函数中, 生成例如 request 对象的实例.
提供者
这时候开发的框架, 各种服务默认绑定在容器中, 与应用类耦合, 虽然是框架自带的服务, 但是还是不够完美, 所以借鉴了提供者的设计模式, 将所有服务抽离并设计注册服务的 API, 在框架启动时, 自动注册默认服务.
这样子, 我们的所有服务与框架底层核心完全解耦, 保证了底层核心的精简, 并具有强大的可伸缩性.
模块化
既然是一个 Web 框架, 使用的时候肯定会承载不同的业务, 所以我们需要使用模块化功能拆分业务, 提升可维护性, 比如可以设定这个模块包含了哪几个控制器(支持通配符), 这个模块需要加载哪些中间件, 甚至子模块功能
所以我就设计了这么一套方案, 使用模块描述类来定义模块
- module.exports = class ExampleModule {
- // 标示子模块
- modules = [];
- // 标示需要装载的控制器
- controllers = [];
- // 标示需要加载的中间件
- middlewares = [];
}
good
请求
作为 Web 框架, 肯定需要解析请求啊什么的, 既然不是扩展 ctx 属性, 那么我的方案就是使用 Request 类来解析 ctx, 这样的好处就是, 我可以解析 koa 的 ctx,express 的 req, res 或者其他框架的上下文对象, 并且这个类是注册在容器中的, 如果你有其他的解析方案, 当然也可以自己注册一个, 然后为所欲为(没错, IoC 容器就是可以为所欲为).
响应
生产响应的时候肯定也要越方便越好, 方便到你只需要在控制器中 return 就好, 可以 return 各种类型, 除了 koa2 中的支持的数据类型, 还支持直接返回 框架的 View(视图, 即模板)实例或者 Response(响应类)实例等等, 框架都会自动判断.
- class UserController {
- index() {
- return [{ id: 1 }]
- }
}
就是这么方便, 当然不仅仅这样, 还有更多强大的功能.
验证器
我在使用很多框架的时候, 验证请求数据不是很方便, 所以也重新设计了一套方案(狗头),
当然还是使用装饰器模式
- // 定义一个验证类, 放在指定目录
- class UserPostValidate extends Validate {
- @MaxLength(10) username;
- @Length(1, 20) password:
}
然后在控制器中
- class UserController extends Controller {
- store() {
- this.request.validate('UserPostValidate')
- }
- }
狗头
写了好久, 先去吃饭了, 其实还有很多功能模块, 例如日志服务, 多进程服务, 进程间通信服务, 安全相关服务, cookie 和 session 服务等等, 有兴趣的可以留言继续解答, 或者出第二篇, 下次出点技术类的~~~
仓库地址: [GitHub https://github.com/dazejs ]
当然, 框架核心代码在 framework 这个包, 目前还在开发和测试中, 优化一些功能和文档, 工具比较 low 逼, 也需要完善, 希望有大牛可以一起开发.
目前已经到 0.8.x 版本, 已经历时半年(第一个 NPM 包提交开始), 离第一个稳定版不远了!!!! 正在快车道中, 希望明年尽快开放 1.0 版本服务大众.
也希望大家可以尝试下提供各种意见反馈, 由于目前就一个人开发, bug 肯定不少, cli 工具方面已经很久没更新了, 准备下一步完善工具, 如果需要体验的(工具万一有 bug), 直接 clonedaze 仓库就可以.
然后, 希望大家不要吝啬自己的 star 给个鼓励~~~~
最后, 祝大家新年快乐, 新年新气象
来源: https://juejin.im/post/5c569ea7e51d450165279cfc