blog icon indicating copy to clipboard operation
blog copied to clipboard

关于nodejs-web框架的调研.md

Open xingyuzhe opened this issue 7 years ago • 29 comments

以下内容其实是补漏, 算是对1月份一点工作的总结。

加入了新团队, 初次接触, 粗略的查看了一些项目(nodejs server端), 发现存在几个问题:

对于新成立的团队,存在以上问题可以理解, 本次是讨论和解决第一个问题。

对于集团范围内而言,同种技术存在多种不同规范和框架很正常, 但是对于一个几十人的团队而言, 资源有限,还是集中力量统一一下效率高些, 有鉴于此, 觉得做一个种子项目比较合适: 一个企业定制框架, 共同维护, 同步更新, 信息共享和沉淀最佳实践。

跟领导聊了一下, 总结了一下当前状态团队对于这块内容的一些主观诉求:

  1. 框架实现够简单, 团队能全面掌控, 低风险。
  2. 给新手从基础开始学习的机会, 就是希望除了满足业务需求之外, 团队也能得到成长。
  3. 一个企业基础框架应满足的基本要求: 约束规范、扩展机制、安全、高效业务开发
  4. 最终还是希望能沉淀出一套自己的轮子

根据上面的要求, 筛选出了eggjsnestjs2个web框架, 其中nestjs是团队一部分同学比较喜欢和尝试使用了的。

首先从外层信息做些调查。

我通读了eggjs和nestjs的文档,查阅了一些资料, 简单对比如下:

-- eggjs nestjs
github stars 7014 4291
github forks 720 250
gitHub dependents 1591/305 0/0
npm search results 565 53+
github contributors 101 31
github core contributors 4(10+) 1(2+)
github releases 49 19
github issues 86/1416 20/347
基础框架依赖 koa2 express
文档 业界良心 一般
核心原理 载入-挂载 模块容器-依赖注入(通过装饰器和元数据实现)
核心理念 微内核-插件机制 组件树-装饰器-流程控制

eggjs的特点和解决的主要问题:

  1. 通过载入代码-自动挂载代码到对象的方式解决了到处写import/require的问题, 不再需要手动维护模块之间的依赖关系, 核心就是一个loader, 虽然官方没有提及, 但是本人开下脑洞猜测, 这个模块的实现灵感有可能来自于坟头草已经一人高了的seajs, -_-, 这种挂载模式实现起来简单粗暴直接高效, 但是一个小问题就是在使用TS编写代码的时候, 会影响书写体验的流畅度, 毕竟不很OOP。
  2. 微核心 + 插件机制(这个理念其实一直以来都是代码组织的核心手段, 本人在以前开发播放器和IM客户端这块时感同身受), 大部分功能通过插件实现, 从而剥离非核心生命周期功能代码, 达到解耦/降低思维负担的目的; 另外一点就是跨模块API调用比较自由没有限制, 这个地方带来便捷的同时也有可能需要一定约束。
  3. 统一约束和规范, 对开发人员强约束, 保证不会出现千人千面的代码风格和设计。
  4. 种子项目 + 渐进式开发, 可以沉淀出自己的插件和业务框架、最佳实践
  5. 针对业务中遇到的常见问题基本都给出了解决方案, 插件丰富, 配套齐全
  6. 内部实现了进程间管理和通信功能
  7. 文档可是说是比较细致了, 基本把web这块涉及到的点都涵盖了, 仅仅通读文档对新手而言都会有不少收获
  8. 核心开发者较多, 属于团队项目, 整体看来比较严谨
  9. 沉淀时间较长, 正式发版2年, 实际发展了4年
  10. 明确定义了agent和app模式, 也实现了schedule功能

nestjs的特点和解决的主要问题:

  1. 通过模块容器-依赖注入维护组件树的模式解决了到处写import/require的问题, 不再需要手动维护模块之间的依赖关系, 另外编码方式与java十分相似。
  2. 组件树容器模式也达到了解耦效果, 实现方式上很大程度上受angular-module影响, 但是需要指定节点component的依赖关系, 跨模块调用严格依赖于声明. 其实这块功能github有独立的项目专做这个东西, 例如: InversifyJS | bottlejs, nestjs 自己实现了这个模块。
  3. 统一约束和规范, 对开发人员强约束
  4. 大量使用装饰器, 这个未支持的特性需要编译, 对代码阅读上可能会提高一些难度
  5. 流程控制上比较细致, 提出了filter、pipe、guard、interceptor这些明确的概念, 虽然这些工作在正常开发时也会做, 但是明确提出来并制定规范还是很有必要的
  6. 明确了Exception Layer的概念
  7. 默认使用/推荐TS开发
  8. 自带微服务实现
  9. 自带swagger接口文档生成功能
  10. 核心开发基本就作者一人, 属于个人项目, 提交记录比较难看, 前期的提交非常随便, 到后期才慢慢改善
  11. 2017年1月立项, 2017年5月发布第一个正式版本, 尚需时日印证

从上述情况来看, 2个框架都在123这几项做了工作, 完成了一个框架所应具备的基本诉求; 总体上看eggjs更加成熟和全面, 配套/生态相对完善的多, 低风险; nestjs则比较青涩和单一, 但是在组件树、流程控制、错误层级处理上有自己的特色, 理念上更加OOP,学习并吸收这些理念是很可取的, 但暂时不建议在核心产品线上投入使用。

接下来做深入调查, 阅读源码.

eggjs

详细阅读了核心相关代码, 粗略阅读了cluster等一些模块和插件的代码, 代码读起来总体比较流畅, 发现框架的核心实现非常简单明了:

  • 框架基础依赖一个非常独立的loader模块, 功能就是加载代码文件和挂载函数到指定对象。

  • 框架本身核心类只有6个: koa本身的application, context, response, request, egg自身新增的controller, service 这2个类, 后续所有的挂载动作都在这几个类上面进行

  • 框架明确了app, framework, plugin3个概念, 依赖方式大概是这样: egg-layer

  • 框架启动的核心流程主要是这样: egg-flow

由于eggjs自己实现了cluster, 自带进程管理和进程间通信功能, 所以egg自身部署时并不需要pm2这个工具, 官方文档上也对此做了解释。不过由于这块内容的引入(cluster还涉及到schedule、socket等功能),给框架本身带来了大量额外的代码和逻辑, 总体提升了框架的复杂度。

但是有些时候由于某些原因并不希望直接使用框架提供的cluster解决方案, 另外我查看了下pm2的API, 也是有进程间通信的API的, 当然用起来可能没有自定义实现时那样自由, 也尚未听说该API有被广泛使用过, 不过, cluster相关的内容能否作为一个扩展包, 而不是强耦合进框架核心流程中? 这样框架本身更加简单纯洁, 或者更容易被接受一些?

为此尝试抽离了eggjs的核心代码, 目标是仅保留最最核心的代码(移除cluster等周边代码), 具备egg的扩展机制和能正常直接使用eggjs的周边插件, 结果最后只需要几百行代码, 很少几个文件即可完成。后续在此基础上整理了一个上层框架, 预想作为业务项目的基础框架使用,主要涉及以下一些方面:

  1. 抽离eggjs的核心代码, 仅保留其插件机制和对应的约束规范

  2. 集成常用扩展函数、中间件、插件

  3. 集成多节点/进程下消息推送解决方案示例

  4. 集成schedule定时任务模块

之后花了点时间用这个上层框架开发了一个抽奖小项目, 开发体验还算流畅, 虽说是草量级项目, 不过也是五脏俱全, 作为example还挺适合。

nestjs

粗略查看了下nestjs的源码, nestjs的核心其实就是一个IoC模块管理容器的实现, 这块内容的逻辑实现作者处理的还是相对复杂的多, 这里吐槽下作者的代码组织方式和略显随意的注释大量的接口引用和糟糕的历史提交记录...,真是额外提高了阅读的难度. TypeScript的加持和作者本人的光环也不能阻挡这一点。言归正传, 这里说下它的核心原理和流程。

要想理解nestjs的源码先要理解和掌握以下知识:

  1. ES6的proxy,reflect
  2. TypeScript的decorator
  3. inversion of control (IoC)的基本概念
  4. 一定TypeScript基础.
  5. 如何通过decorator和元数据实现依赖注入
  • container类: 用于存储所有模块
  • scanner类: 递归提取出所有模块并存储到容器中; 提取出模块间的关联关系和模块自身的各种类以及内部联系,并存储到模块中;
  • module类: 存储自身的关联模块、组件、可注入类、控制器类
  • injector类: 依赖注入的核心环节, 在所有模块的内容和关系都被扫描出后, 来创建实例,
  • instanceLoader类: 使用Injector来加载模块的各种实例, 这个过程很复杂, 伴随递归和各种判断

这个IoC容器实现的核心流程是这样: scanner扫描所有module并提取关系存入module->module存入 container-> injector创建实例(依赖注入)-> instanceLoader加载实例

我们再拿nestjs实现上面eggjs版本lottery的例子, 大概是这样:

结合源码和实际开发体验来说:

  • eggjs框架本身的模块管理(扩展机制)非常简单, 复杂度主要在于cluster这块内容和为此配套的周边设施(命令行工具、调试工具),但是这个复杂度是脱离于核心之外的东西。

  • nestjs的复杂度主要在于IoC模块管理器这块的实现上, 实际上这个东西理论上可以独立出来, 以此降低框架本身逻辑的复杂度。

从总体上讲, eggjs相对成熟, 更贴合实际开发需求。 nestjs的优势就是在一些细节上的约束和控制以及理念上的新颖(仅相对node-web框架而言), 但是这些并不是核心诉求。

另外eggjs团队做的工作内容相对于nestjs而言相当的多, 这些与人力、时间资源的投入是分不开的。

最后, 对于想要的新框架的处理结论已经有了: 1 社区模式(节省资源)

  • 直接在eggjs基础上做一个上层框架, 吸收nestjs的一些优点, 作为插件/扩展内容去完善框架本身.

2 造轮子模式

  • eggjs插件机制 + nestjs流程控制 + component模式(可选) => 新轮子

3 所应具备的特性

  • 约束规范
  • 沉淀/扩展模式
  • 模块依赖管理
  • 集成typescript开发环境
  • 集成API文档输出方案
  • 环境配置
  • 多节点/cluster解决方案
    • socket.io
    • schedule
  • 集成常用插件功能
    • logger
    • cookie
    • session
    • security
    • i18n
    • redis
    • sequelize
    • multipart
    • oauth
    • onerror
    • passport
    • view
    • role
    • crypto
  • 定义控制流/数据流约束规范
    • schema
    • pipe
    • interceptor
    • guard
  • migration
  • 微服务
  • 错误分级
  • 监控
  • 测试用例/代码覆盖率
  • 调试
  • 压力测试

补充: 随着各种工具的发展, 加上eggjs使用也有一段时日,最初一些模糊的预感变得清晰: 1 egg内置cluster模式带来的额外麻烦太多, 现在各种配套工具逐渐完善, 这个功能并没有什么用 2 egg模块隔离度不够但矛盾的是有时候又有限制, 简单的说就是代码组织模式上作为框架不够好, 需要自行定制上层规范; egg的是plugin模式, 相对于nest的component模式还是不够用; 3 egg的ts开发的流畅度差那么一点 4 实际的项目, 需要各种配套设施、工具、系统的协作联合,框架只是一个组成部分, 就应该只做它应该做的事就可以了

各有优劣, 诉求不一样, 选择就不一样: 1 eggjs: 短平快稳,上手极快, 文档完善,配套齐全, 能极大缩短工期。 2 nestjs: 有点追求, 复杂度高的协作项目, 核心诉求是代码组织模式的

之前egg很好的满足了我们的诉求, 但是下一个项目, 会考虑使用nestjs或者自行开发框架(足够闲的话)。

xingyuzhe avatar Feb 25 '18 03:02 xingyuzhe

出租沙发

konce avatar Feb 26 '18 09:02 konce

火钳留名

hueyhe avatar Feb 26 '18 09:02 hueyhe

先赞为敬

aprilandjan avatar Feb 26 '18 09:02 aprilandjan

我觉得ok

wython avatar Mar 15 '18 09:03 wython

棒棒哒~

soulmate2015 avatar Mar 15 '18 09:03 soulmate2015

那个词叫啥来着,对了,醍醐灌顶。

chhenghua avatar Mar 15 '18 09:03 chhenghua

厉害了

dengnan123 avatar Aug 28 '18 11:08 dengnan123

回头也参照这个给我们团队也安利一下

mehunk avatar Aug 28 '18 14:08 mehunk

写的很不错,赞 10086。

补充下几点:

  • Cluster 里面并没有 schedule
  • Socket 那个是为了实现高级的 IPC
  • Cluster 还是很建议使用的,如果有什么特殊的定制需求,何不如提下 RFC/ISSUE 大家讨论下
  • egg-core + egg-cluster 才成为 egg,你要是想定制,可以类似 egg-core + my-cluster(虽然不建议)
  • 框架复杂度其实反而是降低了,因为 PM2 的代码比 egg-cluster 复杂多了

atian25 avatar Aug 28 '18 14:08 atian25

还要补充一点: 之前在 https://github.com/atian25/blog/issues/20 也提过

Egg 的定位是框架的框架,跟 thinkjs/sails/nest 这些是没法直接对比的,因为不是一个层面的概念。就像你只能对比 Express 和 Koa,而不能对比 nest 和 Koa,因为 nest 是在 Express 之上的封装。

基于 Egg 封装的针对某个领域的上层框架,才能对比。譬如完全可以用官方的 TS 方案,再封装集成几个装饰器,成为一个上层框架。就可以拿来对比了。

atian25 avatar Aug 28 '18 15:08 atian25

star 数 已经更新, 这俩之间的差距从 3K 到现在的 1K 左右了。 npm 下载数量也是 nest.js 居多 (可能跟 cnpm 没纳入统计有关吧) 预计今年 nest 的star 数超过egg ~ 比较看好 nest.js

zuohuadong avatar Aug 30 '18 05:08 zuohuadong

从整体的代码组织方式来看,明显nest.js优于egg。框架的作用无非就是能把代码组织得更优雅,更利于阅读,减少团队成员之间的沟通成本。

coldcafe avatar Sep 03 '18 01:09 coldcafe

@atian25 我们之前拿egg做了个审批流的项目, 就是上的TS方案, 封装了几个装饰器完成路由去中心化, 自动校验等功能. 也做了schema, pipes, exceptions 这些约定。 总体上实施后团队基本没在框架上花什么时间, 项目在紧张的排期中顺利上线。最后暴露出的明显的问题主要是 1 TS支持程度不完善 2 单元测试 3 不少新手不太清楚controller和service的界限。

xingyuzhe avatar Sep 03 '18 07:09 xingyuzhe

@xingyuzhe 我们是用 nest.js 做了大概5个项目,ts 支持完善,开发人员之前从事过 java 开发,所以很顺手。 代码组织方式来看,明显nest.js优于egg,这个我同意。 我们基于此,在开发微服务框架,喜欢 nest.js 和 node 的童鞋一起开发:https://github.com/notadd/notadd

zuohuadong avatar Sep 03 '18 08:09 zuohuadong

@zuohuadong 无论哪个框架, 我都希望能快速发展, 个人觉得现在nodejs上根本就没有和spring, springCloud这种相提并论的存在, 还差的远; 实际上对我这边来说选定egg框架只是一个非常初始的工作, 相对于后期的工作内容来说真是可以忽略不计。我也好久没关注nestjs了...

xingyuzhe avatar Sep 03 '18 08:09 xingyuzhe

@xingyuzhe 生态和相关的东西肯定还是差得很远。。。 但是好在开发成本较低,我们目前慢慢打算打通这套生态体系。

zuohuadong avatar Sep 03 '18 08:09 zuohuadong

@zuohuadong https://github.com/notadd/notadd 这个不错, 把好多东西整合了; 我之前开发IM客户端也有用到protobuf + sqlite3 哈

xingyuzhe avatar Sep 03 '18 08:09 xingyuzhe

@xingyuzhe 有时间的话,欢迎一起开发。最近缺人手。 哈哈

zuohuadong avatar Sep 03 '18 08:09 zuohuadong

@atian25 我们之前拿egg做了个审批流的项目, 就是上的TS方案, 封装了几个装饰器完成路由去中心化, 自动校验等功能. 也做了schema, pipes, exceptions 这些约定。 总体上实施后团队基本没在框架上花什么时间, 项目在紧张的排期中顺利上线。最后暴露出的明显的问题主要是 1 TS支持程度不完善 2 单元测试 3 不少新手不太清楚controller和service的界限。

controller和service的界限是什么

wangjpLancelote avatar Dec 04 '18 01:12 wangjpLancelote

评论star

YangYongAn avatar Apr 16 '19 07:04 YangYongAn

@wangjp19951024 业务逻辑放service,controller只是拼装各个service的结果。controller 只是处理 router 和 service 的一个中间人,本身很轻。

zhangwilling avatar Apr 16 '19 14:04 zhangwilling

建议作者可以每年更新一个版本,现在的情况又大不一样了

liubiqu avatar Jul 11 '19 00:07 liubiqu

@wangjp19951024 业务逻辑放service,controller只是拼装各个service的结果。controller 只是处理 router 和 service 的一个中间人,本身很轻。

controller用于拼装service,那controller是否也会涉及到一些业务逻辑呢? controller层是否会与事务有关系呢?

请问下,在egg的service中,如何用设计模式拆分比较大的service呢? egg的自动service,只能被框架实例化,这是否会影响service的拆分呢?

M1178475702 avatar Jul 29 '19 04:07 M1178475702

@wangjp19951024 业务逻辑放service,controller只是拼装各个service的结果。controller 只是处理 router 和 service 的一个中间人,本身很轻。

controller用于拼装service,那controller是否也会涉及到一些业务逻辑呢? controller层是否会与事务有关系呢?

请问下,在egg的service中,如何用设计模式拆分比较大的service呢? egg的自动service,只能被框架实例化,这是否会影响service的拆分呢?

去看看 midwayjs 或者 nest.js 吧。 阿里给egg 的定义是 框架的框架

zuohuadong avatar Jul 29 '19 05:07 zuohuadong

nestjs 已经全面超越了

npm 下载量 image

star 趋势 image

zuohuadong avatar Jul 29 '19 05:07 zuohuadong

@atian25 我们之前拿egg做了个审批流的项目, 就是上的TS方案, 封装了几个装饰器完成路由去中心化, 自动校验等功能. 也做了schema, pipes, exceptions 这些约定。 总体上实施后团队基本没在框架上花什么时间, 项目在紧张的排期中顺利上线。最后暴露出的明显的问题主要是 1 TS支持程度不完善 2 单元测试 3 不少新手不太清楚controller和service的界限。

controller和service的界限是什么

service就是controller中复杂逻辑的抽象;比如一个users的controller一个commits的controller,都需要获取/写入个人信息的逻辑,然后这段代码长达几百上千行,你可以把这些代码的业务逻辑写到service中,达到两个controller中可以复用这块逻辑。

Jeremy-DX avatar Aug 28 '19 07:08 Jeremy-DX

2021年了,显然nest完胜

tanggd avatar Jun 26 '21 13:06 tanggd

挖坟。三年多过去了,ts已经是大势所趋,egg对ts的支持天生残疾,用ts项目基本不考虑egg,用js的项目有一半多被express和koa抢了市场,真正egg的处境挺尴尬的。

zanminkian avatar Aug 29 '21 21:08 zanminkian

@atian25 我们之前拿egg做了个审批流的项目, 就是上的TS方案, 封装了几个装饰器完成路由去中心化, 自动校验等功能. 也做了schema, pipes, exceptions 这些约定。 总体上实施后团队基本没在框架上花什么时间, 项目在紧张的排期中顺利上线。最后暴露出的明显的问题主要是 1 TS支持程度不完善 2 单元测试 3 不少新手不太清楚controller和service的界限。

controller和service的界限是什么

controller 对应的是路由控制器,也就是服务接口门面。更多的形态是一些入参的接收,返回通常是一个response响应。 service 对应的是具体的逻辑块,一般返回通用数据接口,后端可以对接一些model数据层的包装,也可以对接一些基础功能服务的包装,还可以对接第三方接口的调用和返回。供门面的 controller 去组织调用。

YangYongAn avatar Aug 30 '21 08:08 YangYongAn