基于NodeJS的前后端分离的思考与实践(五)多终端适配


Posted in NodeJs onSeptember 26, 2014

前言

近年来各站点基于 Web 的多终端适配进行得如火如荼,行业间也发展出依赖各种技术的解决方案。有如基于浏览器原生 CSS3 Media Query 的响应式设计、基于云端智能重排的「云适配」方案等。本文则主要探讨在前后端分离基础下的多终端适配方案。

关于前后端分离

关于前后端分离的方案,在《基于NodeJS的前后端分离的思考与实践(一)》中有非常清晰的解释。我们在服务端接口和浏览器之间引入 NodeJS 作为渲染层,因为 NodeJS 层彻底与数据抽离,同时无需关心大量的业务逻辑,所以十分适合在这一层进行多终端的适配工作。

UA 探测

进行多终端适配首先要解决的是 UA 探测问题,对于一个过来的请求,我们需要知道这个设备的类型才能针对对它输出对应的内容。现在市面上已经有非常成熟的兼容大量设备的 User Agent 特征库和探测工具,这里有 Mozilla 整理的一个列表。其中,既有运行在浏览器端的,也有运行在服务端代码层的,甚至有些工具提供了 Nginx/Apache 的模块,负责解析每个请求的 UA 信息。

实际上我们推荐最后一种方式。基于前后端分离的方案决定了 UA 探测只能运行在服务器端,但如果把探测的代码和特征库耦合在业务代码里并不是一个足够友好的方案。我们把这个行为再往前挪,挂在 Nginx/Apache 上,它们负责解析每个请求的 UA 信息,再通过如 HTTP Header 的方式传递给业务代码。

这样做有几点好处:

我们的代码里面无需再去关注 UA 如何解析,直接从上层取出解析后的信息即可。如果在同一台服务器上有多个应用,则能够共同使用同一个 Nginx 解析后的 UA 信息,节省了不同应用间的解析损耗。

基于NodeJS的前后端分离的思考与实践(五)多终端适配

来自天猫分享的基于 Nginx 的 UA 探测方案

淘宝的 Tengine Web 服务器也提供了类似的模块 ngx_http_user_agent_module。

值得一提的是,选用 UA 探测工具时必须要考虑特征库的可维护性,因为市面上新增的设备类型越来越多,每个设备都会有独立的 User Agent,所以该特征库必须提供良好的更新和维护策略,以适应不断变化的设备。

建立在 MVC 模式中的几种适配方案

取得 UA 信息后,我们就要考虑如果根据指定的 UA 进行终端适配了。即使在 NodeJS 层,虽然没有了大部分的业务逻辑,但我们依然把内部区分为 Model / Controller / View 三个模型。

基于NodeJS的前后端分离的思考与实践(五)多终端适配

我们先利用上面的图,去解析一些已有的多终端适配方案。

建立在 Controller 上的适配方案

基于NodeJS的前后端分离的思考与实践(五)多终端适配

这种方案应该是最简单粗暴的处理方法。通过路由(Router)将相同的 URL 统一传递到同一个控制层(Controller)。控制层再通过 UA 信息将数据和模型(Model)逻辑派发到对应的展现(View)进行渲染,渲染层则按预先的约定提供了适配几个终端的模板。

这种方案的好处是,保持了数据和控制层的统一性,业务逻辑只需处理一次遍可以应用在所有终端上。但这种场景只适合如展示型页面等低交互型的应用,一旦业务比较复杂,各个终端的 Controller 可能有各自的处理逻辑,如果还是共用一个 Controller ,会导致 Controller 非常的臃肿而且难以维护,这无疑是一个错误的选择。

建立在 Router 上的适配方案

为了解决上面遇到的问题,我们可以在 Router 上就将设备区分,针对不同的终端分发到不同的 Controller 上:

基于NodeJS的前后端分离的思考与实践(五)多终端适配

这也是最常见的方案之一,大多表现在针对不同终端使用各自独立的一套应用。如 PC 淘宝首页和 WAP 版的淘宝首页,不同设备访问 www.taobao.com ,服务器会通过 Router 的控制,重定向到 WAP 版的淘宝首页或者 PC 版的淘宝首页,它们各自是完全独立的两套应用。

但这种方案无疑带来了数据和部分逻辑无法共用的问题,各种终端之间无法分享同一份数据和业务逻辑,产生大量重复性工作,效率低下。

为了缓解这个问题,有人提出了优化后的方案:依然是在同一套应用里面,各个数据来源抽象成各个 Model,提供给不同终端的 Controller 组合使用:

基于NodeJS的前后端分离的思考与实践(五)多终端适配

这个方案解决了前面数据无法共用的问题。在 Controller 上各个终端还是相互独立,但能共同使用同一批数据源,至少在数据上无需再针对终端类型开发独立的接口了。

以上两种基于 Router 的方案,由于 Controller 的独立,各个终端可以为自己的页面实现不同的交互逻辑,保证了各终端自身足够的灵活度,这也是为什么大部分应用采用这种方案的主要原因。

建立在 View 层的适配方案

这是淘宝下单页面使用的方案,不过区别是下单页将整体的渲染层放在了浏览器端,而不是 NodeJS 层。不过无论是浏览器还是 NodeJS,整体设计思路还是一致的:

基于NodeJS的前后端分离的思考与实践(五)多终端适配

在这个方案里面,Router、Controller 和 Model 都无需关注设备信息,终端类型的判断完全交给展现层来处理。图中主要的模块是「View Factory」,Model 和 Controller 将数据和渲染逻辑传递过来之后,通过 View Factory 根据设备信息和其它状态(不仅仅是 UA 信息、也可以是网络环境、用户地区等等)从一堆预设好的组件(View Component)中抓取特定的组件,再组合成最终的页面。

这种方案有几个优势:

上层无需关注设备信息(UA),多终端的视频还是交由和最终展现最大关系的 View 层来处理;不仅仅是多终端适配,除了 UA 信息,各个 View Component 还可以根据用户状态决定自身输出何种模版,如低网速下默认隐藏图片、指定地区输出活动 Banner。每个 View Component 的不同模版间可以自行决定是否使用同一份数据、业务逻辑,提供十分灵活的实现方式。

但明显的是,这个方案也是最复杂的,尤其是要考虑一些富交互的应用场景时,Router 和 Controller 也许无法保持这么纯粹。特别对于一些整体性比较强的业务,本身无法被拆分成组件,这种方案也许并不适用;而且对于一些简单的业务,使用这种架构可能不是最佳的选择。

总结

以上几种方案,都各自体现在 MVC 模型中的一个或多个部分,在业务上如果一个方案不满足需求,更可以采取多个方案同时采用的方式。或是可以理解为,业务上的复杂度和交互属性决定了该产品更适合采用哪种多终端适配方案。

对比基于浏览器的响应式设计方案,因为绝大部分终端探测和渲染逻辑迁移到了服务端,所以在 NodeJS 层进行适配无疑带来了更好的性能和用户体验;另外,相对于一些所谓的「云适配」方案带来的转换质量问题,在基于前后端分离的「定制式」方案中也不会存在。前后端分离的适配方案在这些方面有着天然优势。

最后,为了适应更灵活的强大的适配需求,基于前后端分离的适配方案将会面临更多挑战!

NodeJs 相关文章推荐
Nodejs实现多人同时在线移动鼠标的小游戏分享
Dec 06 NodeJs
Nodejs关于gzip/deflate压缩详解
Mar 04 NodeJs
Nodejs全局安装和本地安装的不同之处
Jul 04 NodeJs
NodeJs的优势和适合开发的程序
Aug 14 NodeJs
nodejs开发——express路由与中间件
Mar 24 NodeJs
3分钟快速搭建nodejs本地服务器方法运行测试html/js
Apr 01 NodeJs
nodejs 终端打印进度条实例代码
Apr 22 NodeJs
Nodejs 和Session 原理及实战技巧小结
Aug 25 NodeJs
nodejs基于mssql模块连接sqlserver数据库的简单封装操作示例
Jan 05 NodeJs
详解webpack打包nodejs项目(前端代码)
Sep 19 NodeJs
Nodejs实现的操作MongoDB数据库功能完整示例
Feb 02 NodeJs
nodejs实现获取本地文件夹下图片信息功能示例
Jun 22 NodeJs
基于NodeJS的前后端分离的思考与实践(四)安全问题解决方案
Sep 26 #NodeJs
基于NodeJS的前后端分离的思考与实践(三)轻量级的接口配置建模框架
Sep 26 #NodeJs
基于NodeJS的前后端分离的思考与实践(二)模版探索
Sep 26 #NodeJs
基于NodeJS的前后端分离的思考与实践(一)全栈式开发
Sep 26 #NodeJs
Nodejs Post请求报socket hang up错误的解决办法
Sep 25 #NodeJs
Nodejs实现的一个简单udp广播服务器、客户端
Sep 25 #NodeJs
Nodejs异步回调的优雅处理方法
Sep 25 #NodeJs
You might like
动态生成gif格式的图像要注意?
2006/10/09 PHP
PHP读取数据库并按照中文名称进行排序实现代码
2013/01/29 PHP
一款简单实用的php操作mysql数据库类
2014/12/08 PHP
Yii框架调试心得--在页面输出执行sql语句
2014/12/25 PHP
详解WordPress中用于更新和获取用户选项数据的PHP函数
2016/03/08 PHP
关于 byval 与 byref 的区别分析总结
2007/10/08 Javascript
jQuery之排序组件的深入解析
2013/06/19 Javascript
JS自定义功能函数实现动态添加网址参数修改网址参数值
2013/08/02 Javascript
用jQuery模拟select下拉框的简单示例代码
2014/01/26 Javascript
浅谈js中test()函数在正则中的使用
2016/08/19 Javascript
JavaScript 继承详解(五)
2016/10/11 Javascript
js实现界面向原生界面发消息并跳转功能
2016/11/22 Javascript
jQuery操作json常用方法示例
2017/01/04 Javascript
微信小程序 navbar实例详解
2017/05/11 Javascript
基于javascript实现贪吃蛇经典小游戏
2020/04/10 Javascript
javascript设计模式 ? 抽象工厂模式原理与应用实例分析
2020/04/09 Javascript
python的几种开发工具介绍
2007/03/07 Python
python生成随机mac地址的方法
2015/03/16 Python
python中zip和unzip数据的方法
2015/05/27 Python
Python高级特性与几种函数的讲解
2019/03/08 Python
Pycharm新建模板默认添加个人信息的实例
2019/07/15 Python
Python 剪绳子的多种思路实现(动态规划和贪心)
2020/02/24 Python
Python TestSuite生成测试报告过程解析
2020/07/23 Python
java关于string最常出现的面试题整理
2021/01/18 Python
scrapy-splash简单使用详解
2021/02/21 Python
体育专业个人的求职信范文
2013/09/21 职场文书
刘胡兰的英雄事迹材料
2014/02/11 职场文书
党员一句话承诺大全
2014/03/28 职场文书
交通安全横幅标语
2014/10/07 职场文书
2015年卫生监督工作总结
2015/05/21 职场文书
2015年幼儿园班主任个人工作总结
2015/10/22 职场文书
2019初中学生入团申请书
2019/06/27 职场文书
nginx处理http请求实现过程解析
2021/03/31 Servers
浅谈Python魔法方法
2021/06/28 Java/Android
手把手教你导入Go语言第三方库
2021/08/04 Golang
Elasticsearch 数据类型及管理
2022/04/19 Python