本文深入探讨基于 Node.js 的前后端分离架构。通过对比传统模板渲染与纯前端模板方案,分析使用 Node.js 作为前端服务器的优势与挑战。文章详细梳理了 Express、Handlebars、Mock.js 等核心工具链,并重点介绍了基于 MEAN 技术栈构建的 Mock 服务器与接口文档管理方案。此外,还涵盖了通过 json-schema 实现接口契约测试以解决联调冲突的实战经验,旨在降低前后端沟通成本并提升开发效率。
基本介绍与 Node.js 概述
基本介绍

首先从核心概念“模板”展开讨论。广义而言,Web 中的模板是指填充数据后可生成文件的页面;严格来说,则是模板引擎利用特定格式的文件与提供的数据编译生成页面。模板主要分为前端模板(如 ejs)和后端模板(如 freemarker),前者在浏览器端编译,后者在服务器端编译。

鉴于部分读者对 Node.js 了解有限,此处补充相关知识。官网定义强调其事件驱动与异步特性,这里借用朴灵书中的图示解析 Node.js 结构。对于熟悉 Java 的开发者,可将其理解为 JS 版本的 JVM。浏览器通常包含渲染器和 JS 脚本引擎(如 Chrome 使用 WebKit 渲染器和 V8 引擎),而 Node.js 同样采用 V8 引擎。简言之,Node.js 是一个 JS 运行环境,类似于浏览器 F12 调试工具,但缺乏 DOM 和 BOM。

该图展示了 Node.js 生态周边的关键信息,包括出色的包管理器 npm、cnode 社区以及 GitHub。这些资源在多方面促进了 Node.js 的繁荣,提供了坚实的技术保障。

大公司往往是技术风向标,例如 Google 的 Angular 和 Facebook 的 React 目前非常流行。此处仅列举三家大公司作为示例。淘宝的中途岛架构无需多言,国内 Node.js 先行者朴灵即来自淘宝;去哪儿推出了名为“QTX”的技术框架;360 月影带领的 75 团队发布了基于 ES6/ES7 的 Web 服务器框架 thinkjs。当时技术总监对此颇为看好,但由于作者无暇学习 ES6 且插件不够丰富,最终选择了更为成熟的 Express。

前后端分离开发方式对比
回归正题,该表格列出了作者接触过的三种前后端分离开发方式。第一种是最常见的使用 Java 等后端语言模板的方式,SEO 友好,缓存利用率高且能减轻浏览器渲染负担,但最大问题是模板文件耦合度过高,导致前后端互相推诿,前端看不到数据,后端不懂页面,模板文件成为难题。第二种是目前移动端项目的实现方案,利用 Angular 等框架(指令可视为前端模板)和 Nginx 反向代理服务器,使前后端彻底解耦,仅通过 AJAX 交互数据。该方案优缺点与前一种相反,前端模板性能始终是问题,尤其在低端移动设备上更为明显。最后一种为新项目采用的使用 Node.js 作为前端服务器的方式,将前端职责从浏览器划分至模板层,解决了上述所有问题,但也引入了新问题,稍后分析。

当然,全栈开发在小型项目中同样适用。对于传统的 JSP/PHP 开发,全栈开发的沟通成本更低,开发人员能更容易理解整个功能模块,从而更好地还原产品设计。特别是现在出现的以 JS 语言为基础的全栈开发技术:Meteor 和 MEAN,使得前后端开发可以用一种语言直接完成,再配合 MongoDB,数据从浏览器到数据库无需转义直接使用,无需编写 SQL,开发成本大幅降低。

核心技术栈与工具选型
本次搭建 Node.js 服务器所用到的插件包括:大名鼎鼎的 Express,这是一个轻量级 Web 服务器框架;Handlebars 模板引擎也是巧合选用,因为 Express4 默认即为此引擎。Handlebars 作为“弱逻辑”模板引擎,主张减少模板逻辑,尽量仅使用变量和分页,基于其设计理念,作者仅扩充了两个 helper。因其测试代码使用 supertest,而 supertest 基于 superagent,故选用 superagent 来转发和发起请求。superagent 功能较弱,无法建立长连接,故推荐 request 插件。RESTful API 无需多言,前端服务器与浏览器、前端服务器与后端服务器均遵循此规范,即 URL 指向资源,增删改查由具体请求方法表示,状态码表示结果。Gulp 打包工具也被使用,作者研究了 Webpack 很久,发现每增加一个页面都要修改配置文件,过于繁琐,遂放弃。
开发流程与接口管理
开发流程

如果本次分享仅讲述如何将 Node.js 作为前端服务器来实现前后端分离,那内容便乏善可陈,参考淘宝 UED 的文章即可。前后端分离最大的问题在于沟通成本的上升,具体体现为接口的定义和调试。在上图所示的传统开发流程中,接口定义置于接口服务器中,随后前后端各自根据接口文档造假数据进行本地调试,最后进行联调。这一环节往往是前后端冲突的爆发点,参数不对、返回值不符等问题频发,极度浪费时间。接下来探讨该项目中如何解决这一问题。

前后端因接口产生的冲突一直存在,作为保守主义者,作者采取迭代开发策略,第一步是增加了一个 Mock 服务器。该服务器的神奇之处在于根据接口文档自动生成假数据,实现了“接口即 API”,前端同学无需再写死数据进行测试。作为前端开发,首先考虑自身利益也在情理之中。当然,这仅在一定程度上利于前端开发,若后端接口与文档不一致,联调时仍会出现问题。那么,如何解决?

偶然在破浪大神的博客上看到老马一篇专门讲解前后端分离的文章,其中重要概念为契约测试(也称双边测试)。其核心概念旨在解决远程联调问题,对前后端参数均进行校验,要求大家按照接口文档进行开发。受此启发,项目加入 json-schema 规则,实现了对 HTTP 请求参数的校验,谁不按规矩来谁就修改。

这是项目早期使用的接口文档管理器 Redmine,除了记录和查看功能外,再无其他作用。

Swagger 号称世界最流行的接口文档服务器,界面美观,插件较多,可针对后端语言直接生成测试代码。不过在部署时始终未能完全掌握,且 YAML 格式不如 JSON 习惯,最终放弃使用。

这是目前项目上使用的文档服务器和 Mock 服务器,基于 MEAN 技术栈实现,具备以下基本功能:
利用 Mockjs 插件动态生成随机数据,基于 json-schema 对接口参数实行校验和接口测试,并保存测试状态和接口响应时间。提供简单的 JSON 编辑器,带有登录校验功能,登录后可进行接口调试。Mock 服务器按照 API 服务器响应请求,接口更新时自动更新。
常见问题与心得体会
一些问题

Node.js 是前端工程师的翅膀,但插上翅膀后是变成天使还是恶魔,取决于能否解决使用它时带来的问题。
首先,前端工作量无疑会增加,但沟通成本会降低。Node.js 单线程服务器的稳定性确实不够理想,但代码的健壮性和完善的日志可有效规避风险。关于回调问题,解决方法众多,包括 Node.js 的 q/async 模块以及 ES6/ES7。关于 Node.js 调试,虽然作者一直排斥 IDE,但不得不承认 WebStorm 在调试方面确实方便。作者使用的 node-inspector 也尚可,界面类似 Chrome 开发者工具,看起来较为熟悉。

对于后端程序员而言,更应推崇 Node.js。接口整合的工作交由前端服务器处理,同时与前端耦合度大大降低,工作量和工作效率均得到提升。

心得体会有两点
Node.js 的使用虽有一定学习成本,但对前端开发人员十分友好。前端使用 Node.js 的话,无论是技术含量还是工作量都会有所提升,从而提升了岗位的重要性。当前端开发人员能创造更多价值时,才有资格要求更高的薪水。工作中建议少提建议,多给可行性方案,同时进行技术预研,而非仅编写简单的 Hello World。
总结
可能有人质疑这套东西如此复杂,用起来太麻烦,不如面对面沟通。对于此类质疑,作者引用腾讯高级 UI 工程师余果在《web 全栈工程师的自我修养》中提到的一个例子:一次电面小公司前端负责人时,对方询问如何管理代码,回答是直接通过 FTP 上传,并抱怨手下人老是更新错代码;当被问及为何不用 SVN 或 Git 时,对方表示还不如手动更新方便。这个故事所蕴含的道理,正是作者面对质疑的回答。
PPT 下载地址