实践中的前后端分离

25,686

相信前后端分离这个词,早已流传甚广,大家一些自己的理解,但可能有些人的观点有稍许偏差:我们要搞 SPA,全AJAX,那才是前后端分离了。

什么是前后端分离

我们来聊聊什么是前后端分离。

先来看一张WEB系统前后端架构模型图。

WEB系统前后端架构模型
WEB系统前后端架构模型

从图中可以清晰的看到,前后端的界限是按照浏览器和服务器的划分。那么我们经常会发现一些问题:

  1. 模板层归属前端还是后端?
  2. 模板强依赖于后端渲染,前端开发需要等待后端开发吗?

通常情况,模板层归属于前端,因为让后端人员来接触他们不擅长的样式和 js 交互是很蛋疼的事情。

那么,作为前端开发的我们在实际的开发场景中又会遇到以下问题:

  1. 环境:进行本地开发,需要起后端环境,如 Tomcat、PHP,影响开发效率
  2. 流程:前端开发先开发 html,再将 html 改写成指定的模板语法(俗称套模板),影响开发效率
  3. 接口:
    • 接口定义一般使用 word 文档,前端开发时不好理解接口字段,影响开发效率
    • 接口变更需要重新编写文档,并重新发送,影响开发效率
    • 文档散落,影响接口维护
  4. 联调:
    • 联调过程变得很复杂,尤其是没有做热部署的Java工程,改视图还需要重启Tomcat,影响前端联调效率
  5. 效益:
    • 前端开发更关注用户体验,而后端只希望关注数据可靠,为实现如响应式、ssr之类的一些交互,前端需要掌控一定的请求响应能力
    • 如果前后端对接的方式转变成为纯粹的 JSON 交换,对于提升开发效率、更清晰的职责与接口复用都是有好处的

出现影响开发效率的事情,就说明现有的模式存在问题,显然问题的解题思路需要我们重新思考“前后端”的定义。此时,前后端分离的概念便应运而生,目的是将前后端开发人员的合作方式调节到大家都尽可能舒适的姿势。

有哪些实现方案

SPA

SPA
SPA

全称 Single Page Application,使用前端路由的方式代替后端的 controller,并使用前端模板代替后端的模板引擎渲染,使用 restful api 实现与后端的数据交互。

在这个方案中,前后端的交互被转换成了纯粹的 http 方式的 JSON 串交互。

SPA 的优势:

  1. 环境:前端开发者不需要本地起后端环境
  2. 流程:独立的前端开发方式,由于后端返回纯 JSON ,前端想要模拟请求响应的话,只需启动一个纯静态的服务器,响应 JSON 格式的 html 即可
  3. 联调:清晰的对接方式,JSON 对于前后端来说都是比较纯粹的
  4. 效益:对于用户来说,用户体验的提升

SPA 的劣势

  1. SEO 弱
  2. 首屏加载慢,等所有 js 加载完才能出首屏
  3. 前端需要处理一些本不需要在这一层处理的事情,如权限控制交给前端控制

综上,SPA 是一个可以解决前后端分离的有效方案,对于无 SEO 要求的项目大可以尝试。

开发阶段的分离 -- Mock && Proxy

顾名思义,开发阶段的前后端分离,需要依赖工具实现,通常把这个工具叫做 Mock Server(如笔者所开发的一款 Mock Server -- Foxman)。

Mock Server 提供功能

基础功能

  • 拦截同步请求,取 JSON 格式的 Mock 数据,结合本地 Template,通过模板渲染引擎渲染,得出响应的页面
  • 拦截异步请求,取 JSON 格式的 Mock 数据响应

这里我们需要抽象以上操作为两个函数,利于理解:

  1. SyncView = TemplateEngine(Template, MockData)
  2. AsyncView = MockDataTransform(MockData)

优化功能

  • Living Reload -- 监听本地文件,发生修改则通知(一般使用 websocket)浏览器更新资源
  • 修改响应头 -- Mock 阶段,可以做到 js 修改响应情况
  • 代理 -- 前面提到了两个函数,代理的指责是将原本取自本地的 MockData,改成了从服务端以 http 的方式取得的数据

开发流程

我们将一个项目开发划分为三个阶段:接口定义,开发,联调。正好可以和我们 “Mock”、 “Proxy” 两个工具契合。
让我们通过实际的场景来表述这种前后端的合作方式。

接口定义

我们接到一个需求,实现某个功能。在我们理清楚具体的功能之后,应该与后端定义接口及返回,包括:

  1. 有哪些页面,页面的请求路径,模板位置,以及后端返回给我们的 Model 内容
  2. 有哪些 Ajax 接口,Ajax接口 的请求路径,以及后端返回的 JSON 内容

在制定完接口后,我们需要按照 Mock Server 的要求,创建 Mock 文件,并往里面填入与后端约定好的 JSON 数据,并与后端确认。

显然我们的开发中,接口定义变成了一件很具体的事情,而开发阶段可以使用这份 Mock 数据,并做到 Mock 数据即接口文档

开发阶段

在我们完成接口定义后,我们期望的是无打扰、自治的一个开发体验。

正如上图所示,开发阶段前端开发可以完全与后端开发人员隔绝,也不需要本地启动后端环境,我们要做的,只是按照先前指定的接口及本地的 Mock 文件进行需求的开发。

而在开发过程中,遵循 html -> css -> js 的开发顺序,Foxman 拥有一个很人性化的 live reload(更改css 之会 reload css),总之接口定义合理的话,这一步会很顺畅。

联调阶段

在我们开发完页面后,我们期望的是与后端进行联合调试,已验证功能开发是否存在缺陷,即联调阶段。

在这步骤中,我们只需要更换 SyncView = TemplateEngine(Template, MockData) 的 MockData,将原本响应自 Mock 文件的请求,转发到真实的目标服务器(在联调阶段会是 开发主机 或者 测试机)。

此处代理和转发,笔者已抽象成了的另外一个库 koa-api-forward,欢迎交流和使用。

Mock && Proxy 优势

  1. 环境:
    • 前端开发者不需要本地起后端环境
  2. 流程:
    • 独立的前端开发方式,Mock 与 Proxy 结合,流程清晰
    • 前端可以在本地调试 view 层,大幅度提升前端的联调效率
  3. 联调:
    • 清晰的对接方式,JSON 实现前后端来说都是比较纯粹的
  4. 效益:
    • 方便开发的同时,保持线上系统的无侵入

Mock && Proxy 劣势

  1. 未真正掌握线上的接口响应,实现一些前端交互需求(响应式)时仍依赖后端,或无法进行(如 ssr)

Node.js 中间层

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2017/10/5/956f5231f02144f4b6ef87a50abf2ce0~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2017/10/5/956f5231f02144f4b6ef87a50abf2ce0~tplv-t2oaga2asx-image.image

这个模式自然时结合了前面的 Proxy。大家都知道 Node.js Server 里面强调一个 中间件的 概念,对应到设计模式的职责链模式。即只处理自己能处理的情况,否则,继续往后传递,直到被处理。

这个方案中,Proxy 作为了中间件体系中的最后一层,用以转发请求,而在这之前依次是中间件的错误处理、静态资源的响应、路由拦截(routers) 等等。

Node.js 拥有一定的接口控制能力,如处理 PC/Mobile 的响应式渲染,或是 Server-Side-Render 等等。

Node.js 中间层优势

  1. 环境:
    • 前端开发者不需要本地起后端环境
  2. 联调:
    • 清晰的对接方式,JSON 实现前后端来说都是比较纯粹的
  3. 效益:
    • 可渐进式,前期可以将请求全部转发后端服务器,而后可以逐步将 Node.js 层作为用户的直接数据交换层
    • 职责分明,后端服务化,Node.js 层处理接口用户相关的页面响应 及 数据交换
    • 可组合性,后端服务化,Node.js 负责组合拼装,实现接口可复用率

Node.js 中间层劣势

  • 开发阶段仍需要 Mock 支持,如果将 Mock 方式整合进 Node.js 中间层,则造成 Node.js 中间层职责不纯粹
  • 对现有系统的渐进式改造是个较为漫长的过程

总结

还是那句话,所有的前后端分离方案,都是为了前后端开发人员的合作方式调节到大家都尽可能舒适的姿势。

那么一个不错的实践是,我们可以将 (Mock && Proxy) 与 Node.js 中间层 两个方案结合:

  1. Mock && Proxy 只依靠抽象出来的工具,在前端开发阶段,继续使用,避免造成 Node.js 中间层职责不纯粹
  2. Node.js 中间层的存在可以解决(Mock && Proxy)方案的劣势

未完待续。。。

参考资料

by 君羽