基于 Node.js 前后端分离的一点思考

hello~亲爱的看官老爷们大家好~有一段时间没写文章了,最近忙于为一个对内的数据可视化平台进行完全的前后端分离。原来的项目是一个基于 Vue 的单页应用,重构后接入 Node 作为中间层,达到完全的前后端分离。

由于项目相对简单,成本并不是太高。下文将简单介绍一下使用的技术栈与分离后的收益,重点是对基于 Node 做前后端分离的一点思考。

背景

大约是去年11月底入职新东家,接手一个仅 对内 的数据分析系统。新入职当然是希望做出点成绩,在更改部分 UI 与优化部分功能之后,发现页面性能还是比较低。排查后发现获取数据的接口没有缓存,也不是基于 RESTful 的,浏览器缓存完全不起作用。期间也经历了后端修改接口,前端代码大面积修改的情况。

基于后端同学不熟悉前端机制,我也抱着方便日后搞事情的心态,在多次滚地板之后,部门 Leader 同意接入 Node 作为中间层。

技术选型

在确定接入 Node 后,首先要做的就是技术选型。Node 一般就是在 EpressKoaEgg 中选。在中间件的使用上,个人偏好 Koa 的形式,因而只好和 Express 说再见了。Egg是在Koa` 上作了强约束,规定代码编写、目录结构等。

选型时我与前端 Leader 讨论了无数次,他认为 Egg 约束太多,扩展性较差,如若出现框架底层的 Bug 则难于处理,因而偏向于使用 Koa。这样的顾虑十分合理,然而对于现在的项目而言,不太可能有功能会超出 Egg 所提供的,反而 Egg 所提供的功能能为项目搭建与维护减少不少的成本。至于约束,个人认为这反而是一件好事,一定程度上解决了多人开发时代码的组织问题。

考虑再三后,决定使用 Egg 作为 Node 的框架。

分离收益

由于项目不算十分复杂,接入的过程算是波澜不惊。唯一的麻烦是工期比较紧,因而分两步走:一期先接入 Node,所有页端请求原样转发 Java,返回的结果原样转发页端。二期对数据获取的接口进行整合与优化以提升性能。完成后收益还是客观的,贴两张图展示下成果。

原来某页面的性能(所有请求都是 Post 的):

接入 Node 相同页面的性能(转为方法为 Get):

可以看到,不论是数据下载量还是响应时间等指标,耗时均有降低。当然,这是建立在接入 Node 后我对接口进行了整合与缓存优化等措施后的对比。如果都是首次访问,公司内网 WiFi 环境下与原来的性能会有稍差一点,整体加载时间略高于 Java 直出50ms不到。也用过 Chrome 模拟弱网环境,耗时与 Java 直出基本一致。

而且开发体验上也比之前舒服得多,算是达到了接入 Node 前定下的目标:接入后在支持相同功能的情况下性能提高30%;在相同的开发时间内,完成相同的需求,但有更好的开发体验及更好的页端性能。

小结一下,前端的一切优化都是在模板与获取模板所需数据上进行优化,使用 AngularReactVue 等框架构筑的单页应用,解决了模板的问题,可以不再让后端去动我们的模板。但是获取所需数据仍依赖于后端,不少单页应用交互上已经足够复杂,如果还需维护一套复杂的获取与整合数据的逻辑,还是十分头疼的。因而多接入一层 Node 处理数据的获取与整合,尽最大努力去优化页端的请求接口,让页端专注于交互,在条件成熟的情况下,是十分值得的。

思考

如若就为了推广 Node,本文应该是把上面的步骤写详细,小结完就该结束了,这和很多大佬的实践文章一致(当然我写得不够好~)。然而和两位 Leader 的讨论过程中,感谢他们对 Node 技术抱有怀疑,提出了不少有意思的问题,结合我自己的思考,整理成提问形式呈现给大家。

为何接入 Node 作为中间层,和公司现行的 PHP 有何区别?

没有区别!事实说,Node 能做的 PHP 一样能做。那么问题就转换为 Node 的意义是什么,为何摈弃公司相对成熟的 PHP 方案而转向 Node

我认为后端服务主要是稳定为主,业务调整不会特别频繁。而对于前端而言,业务频繁调整简直司空见惯,如果前后端耦合在一起,频繁让后端发版不是可取之策。同时,前后端对数据结构的要求及对其控制的粒度也大不相同。写得好 PHP 的同学不一定写得好前端,写得好前端的同学不一定写得好 PHP。对于专业的领域,还是应该由专业的人去做,让前端控制整个模板及模板依赖的数据,对提高项目质量有很大的帮助。说句玩笑话,后端就搞搞数据库,吐吐 json 就好,前端就拿一下数据,切切页面就好。

进一步而言,前端若要接入 SSR 之类的功能,Node 还真比 PHP 有优势得多,也算是为日后搞事情做铺垫吧。

接入 Node 后性能会有多大提高?

不一定有提高,甚至有下降。此问题是我一期完成之后,测试页端性能时发现的。测试时,Node 除了将所有的需求原样转发外,还加了协商缓存,然而响应时间却更慢了。

接入前(为 Post 请求):

接入后(为 Get 请求):

正常来说,获取 10k 左右的数据时,协商缓存如若命中即返回 304,省略了下载的过程,理应更快的。通过打点,发现 Node 转发请求耗时额外增加 10ms 左右,然而在全公司内网 WiFi 环境下,整体下载时间不到 160ms。

因而引入 Node 不一定会有性能的提高,反而会因为多引入一层,而导致性能耗损。在对内的项目中或性能尚可的项目中,提高性能不足以成为接入 Node 的关键理由。换句话说,接入 Node 之后,在优化页端请求之前,并不可能为应用性能带来飞跃。

这个问题也算是我对 Node 态度转变的起点,开始从无脑支持接入 Node 到辩证地推敲,也引出之后的的问题。

接入 Node 有什么弊端?

前端的能力越强,意味着责任越多。例如之前可能是 Java 做的安全防护,可能就会下沉到 Node 端,这对大多数前端同学而已都是比较陌生的领域。

投入的成本与产出也是值得商榷的事情,毕竟 Node 再贴近前端,也始终是属于后端的领域。前后端思想不太一样,用前端的思维写后端,很可能写出十分糟糕的代码,轻则影响性能,重则内存泄漏。接入 Node 层后它反而成为拖累,相信大家也不愿看见。

毕竟 Node 对于不少公司而言是比较新的领域。尽管可能知道接入 Node 好处不少,但如何接入,接入后它能做什么,大家可能比较模糊。如何花最少的代价,平滑地接入 Node 的同时,最大限度的复用现有架构,也是个不少的挑战。

既然如此,没有 Node 适用的场景吗?

有。正如前文所说,Node 的意义是在于让前端掌握模板与模板所依赖数据,不妨从这两方面去进行考虑。如若重 SEO 的同时,页面交互相对复杂,这时候接入 Node 作为中间层,我认为就是最佳实践。

其次是后端架构不断演变,开始转向微服务化之后,前端感到接口碎片化开始带来不少麻烦后,就应该考虑接入 Node 整合接口了。

总的来说,什么时候接入 Node, 有点像什么时候引入 ReduxVuex。当你感觉到麻烦后,只要你知道还有这个选项,自然而言地就会想到用它。

小结

综上所述,在以下场景中,我认为接入 Node 是最佳实践:

  • 重视 SEO 且交互复杂。
  • 后端微服务化,前端需要整合接口。
  • 前端主导的项目,需要用到最新技术,如 SSRPWA 等。

如若只是性能问题,引入 Node 不一定会有改善,需要根据实际情况进行分析。至于后端的接口设计太丑陋、返回的数据结构不符合前端使用等问题,在应用规模不大的情况下,其实都是可以和后端同学进行沟通的,以此接入 Node 不一定是最佳的实践,需要好好思量。

但是,坚决反对只是因为现有架构比较熟悉而不愿改变,始终抱着错误的方案不去解决的做法,这绝对是舍本逐末。我始终认为,只要是对的事情,尽管过程再艰难,亦应该朝着对的方向前进。

以上是个人的一点浅见,感谢各位看官大人看到这里。知易行难,希望本文对你有所帮助~谢谢!