[Node.js] 00 - Where do we put Node.js

Ref: 前后端分离的思考与实践(五篇软文)

其实就是在吹淘宝自己的Midway-ModelProxy架构。

 

第一篇


 

起因

为了提升开发效率,前后端分离的需求越来越被重视,

同一份数据接口,我们可以定制开发多个版本。

(1) 后端 - 业务/数据接口,

(2) 前端 - 展现/交互逻辑,

 

措施

探索一套基于 Node.js 的前后端分离方案,过程中有一些不断变化的认识以及思考,记录在这里。

 

 

一、什么是前后端分离?

一个基准例子

每个人对前后端分离的理解不一样,

前后端分离的例子就是SPA(Single Page Application),所有用到的展现数据都是后端通过异步接口(AJAX/JSONP)的方式提供的,前端只管展现。

后端会帮我们处理一些展现逻辑,这就意味着后端还是涉足了 View 层的工作,不是真正的前后端分离。

【后端不能关心界面的展示部分,所以spa分离的不完全】

 

从职责上划分才能满足目前我们的使用场景:

(1) 前端:View 和 Controller 层。

(2) 后端:Model 层 (业务处理/数据等)。

  

二、为什么要前后端分离?

玉伯 -《Web 研发模式演变 

Web 1.0 时代

页面由 JSP、PHP 等工程师在服务端生成,浏览器负责展现。

Service 越来越多,调用关系变复杂。JSP 等代码的可维护性越来越差。

 

提出问题

但可维护性更多是工程含义,有时候需要通过限制带来自由,需要某种约定,使得即便是新手也不会写出太糟糕的代码。

如何让前后端分工更合理高效,如何提高代码的可维护性,在 Web 开发中很重要。

 

后端为主的 MVC 时代

Web Server 层的架构升级,比如 StructsSpring MVC 等,这是后端的 MVC 时代。

 

Web 2.0 时代

2004 年 Gmail 像风一样的女子来到人间,很快 2005 年 Ajax 正式提出,加上 CDN 开始大量用于静态资源存储

于是出现了 JavaScript 王者归来的 SPA(Single Page Application) 的时代。Ajax 带来的 SPA 时代!

开始在浏览器中重视起Javascript

AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。

AJAX 不是新的编程语言,而是一种使用现有标准的新方法。

AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。

AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。

 

浏览器端的分层架构

这种模式下,前后端的分工非常清晰,前后端的关键协作点是 Ajax 接口

看起来美妙,但实际上与 JSP 时代区别不大。

复杂度从 服务端的 JSP 里移到了浏览器的 JavaScript,浏览器端变得很复杂。

类似 Spring MVC,这个时代开始出现浏览器端的分层架构: 

 

前端为主的 MV* 时代

SPA 让前端看到了一丝绿色,但依旧是在荒漠中行走。

为了降低前端开发复杂度,除了 Backbone,还有大量框架涌现,比如 EmberJSKnockoutJSAngularJS 等等。

这些框架总的原则是先按类型分层,比如 Templates、Controllers、Models,然后再在层内做切分,如下图:

但依旧有不足之处:

1、代码不能复用。
2、全异步,对 SEO 不利。往往还需要服务端做同步渲染的降级方案。
3、性能并非最佳,特别是移动互联网环境下。
4、SPA 不能满足所有需求,依旧存在大量多页面应用。URL Design 需要后端配合,前端无法完全掌控。

 

Node 带来的全栈时代

随着 Node.js 的兴起,JavaScript 开始有能力运行在服务端。这意味着可以有一种新的研发模式

在这种研发模式下,前后端的职责很清晰。对前端来说,两个 UI 层各司其职:

1、Front-end UI layer 处理浏览器层的展现逻辑。通过 CSS 渲染样式,通过 JavaScript 添加交互功能,HTML 的生成也可以放在这层,具体看应用场景。

2、Back-end UI layer 处理路由、模板、数据获取、cookie 等。

通过路由,前端终于可以自主把控 URL Design,这样无论是单页面应用还是多页面应用,前端都可以自由调控。后端也终于可以摆脱对展现的强关注,转而可以专心于业务逻辑层的开发。

 

前一种模式的不足,通过这种模式几乎都能完美解决掉:

通过 Node,Web Server 层也是 JavaScript 代码,这意味着部分代码可前后复用,需要 SEO 的场景可以在服务端同步渲染,由于异步请求太多导致的性能问题也可以通过服务端来缓解。

SEO参考:前端后端分离,怎么解决SEO优化的问题呢

 

基于 Node 的全栈模式,依旧面临很多挑战:

1、需要前端对服务端编程有更进一步的认识。比如 network/tcp、PE 等知识的掌握。
2、Node 层与 Java 层的高效通信。Node 模式下,都在服务器端,RESTful HTTP 通信未必高效,通过 SOAP 等方式通信更高效。一切需要在验证中前行。
3、对部署、运维层面的熟练了解,需要更多知识点和实操经验。
4、大量历史遗留问题如何过渡。这可能是最大最大的阻力。

 

小结

回顾历史总是让人感慨,展望未来则让人兴奋。上面讲到的研发模式,除了最后一种还在探索期,其他各种在各大公司都已有大量实践。几点小结:

1、模式没有好坏高下之分,只有合不合适。
2、Ajax 给前端开发带来了一次质的飞跃,Node 很可能是第二次。
3、SoC(关注度分离) 是一条伟大的原则。上面种种模式,都是让前后端的职责更清晰,分工更合理高效。
4、还有个原则,让合适的人做合适的事。比如 Web Server 层的 UI Layer 开发,前端是更合适的人选。

 

把node做为UI渲染的“后端服务器"的使用方式,我觉得还是很有道理,很值得尝试的。

以后端开发而言,我会更加看好go,但目前go在UI渲染方面还是比较弱,把go挪去“真·后端”,UI渲染换成node,我觉得会是挺不错的选择。

 

 

三、怎么做前后端分离?

基于NodeJS“全栈”式开发

提问者

  • SPA 模式中,后端已供了所需的数据接口,View 前端已经可以控制,为什么要多加 Node.js 这一层
  • 多加一层,性能怎么样
  • 多加一层,前端的工作量是不是增加了
  • 多加一层就多一层风险,怎么破?
  • Node.js 什么都能做,为什么还要 Java

回答者 

为什么要增加一层 Node.js?

现阶段我们主要以后端 MVC 的模式进行开发,这种模式严重阻碍了前端开发效率,也让后端不能专注于业务开发。
解决方案是让前端能控制 Controller 层,但是如果在现有技术体系下很难做到,因为不可能让所有前端都学 Java,安装后端的开发环境,写 VM。
Node.js 就能很好的解决这个问题,我们无需学习一门新的语言,就能做到以前开发帮我们做的事情,一切都显得那么自然。

性能问题

分层就涉及每层之间的通讯,肯定会有一定的性能损耗。但是合理的分层能让职责清晰、也方便协作,会大大提高开发效率。分层带来的损失,一定能在其他方面的收益弥补回来。

另外,一旦决定分层,我们可以通过优化通讯方式、通讯协议,尽可能把损耗降到最低。

举个例子:

淘宝宝贝详情页静态化之后,还是有不少需要实时获取的信息,比如物流、促销等等,因为这些信息在不同业务系统中,所以需要前端发送 5,6 个异步请求来回填这些内容。

有了 Node.js 之后,前端可以在 Node.js 中去代理这 5 个异步请求,还能很容易的做 Bigpipe,这块的优化能让整个渲染效率提升很多。

可能在 PC 上你觉得发 5、6 个异步请求也没什么,但是在无线端,在客户手机上建立一个 HTTP 请求开销很大,有了这个优化,性能一下提升好几倍。

淘宝详情基于 Node.js 的优化我们正在进行中,上线之后我会分享一下优化的过程。

前端的工作量是否增加了?

相对于只切页面/做 demo,肯定是增加了一点,但是当前模式下有联调、沟通环节,这个过程非常花时间,也容易出 bug,还很难维护。所以,虽然工作量会增加一点,但是总体开发效率会提升很多。

另外,测试成本可以节省很多。以前开发的接口都是针对表现层的,很难写测试用例。如果做了前后端分离,甚至测试都可以分开,一拨人专门测试接口,一拨人专注测试 UI(这部分工作甚至可以用工具代替)。

增加 Node.js 层带来的风险怎么控制?

随着 Node.js 大规模使用,系统/运维/安全部门的同学也一定会加入到基础建设中,他们会帮助我们去完善各个环节可能出现的问题,保障系的稳定性。

Node.js 什么都能做,为什么还要 Java?

我们的初衷是做前后端分离,如果考虑这个问题就有点违背我们的初衷了。即使用 Node.js 替代 Java,我们也没办法保证不出现今天遇到的种种问题,比如职责不清。

我们的目的是分层开发,专业的人,专注做专业的事。基于 Java 的基础架构已经非常强大而且稳定,而且更适合做现在架构的事情。

 

 

四、淘宝基于 Node.js 的前后端分离

 

  • 最上端是服务端,就是我们常说的后端。后端对于我们来说,就是一个接口的集合,服务端提供各种各样的接口供我们使用。因为有 Node.js 层,也不用局限是什么形式的服务。对于后端开发来说,他们只用关心业务代码的接口实现。
  • 服务端下面是 Node.js 应用。
  • Node.js 应用中有一层 Model Proxy 与服务端进行通讯。这一层主要目前是抹平我们对不同接口的调用方式,封装一些 View 层需要的 Model。
  • Node.js 层还能轻松实现原来 vmcommon, tms(引用淘宝内容管理系统)等需求。
  • Node.js 层要使用什么框架由开发者自己决定。不过推荐使用 Express + XTemplate 的组合,XTemplate 能做到前后端公用。
  • 怎么用 Node.js 大家自己决定,但是令人兴奋的是,我们终于可以使用 Node.js 轻松实现我们想要的输出方式: JSON/JSONP/RESTful/HTML/BigPipe/Comet/Socket/同步、异步,想怎么整就怎么整,完全根据你的场景决定。
  • 浏览器层在我们这个架构中没有变化,也不希望因为引入 Node.js 改变你以前在浏览器中开发的认知。
  • 引入 Node.js,只是把本该就前端控制的部分交由前端掌控。

 

 

 

 

第二篇


 

可以观察到在这几年,大家都倾向将 渲染这件事,从服务器端端移向了浏览器端。

而服务器端则专注于 服务化 ,提供数据接口。

View 这个层面的工作,经过了许多次的变革:

(1) Form Submit 全页刷新 => AJAX 局部刷新

(2) 服务端续染 + MVC => 客户端渲染 + MVC

(3) 传统换页跳转 => 单页面应用

 

浏览器端渲染造成的坏处 

(1) 模版分离在不同的库。有的模版放在服务端(JAVA),而有的放在浏览器端(JS)。前后端模版语言不相通。

(2) 需要等待所有模版与组件在浏览器端载入完成后才能开始渲染,无法即开即看。

(3) 首次进入会有白屏等待渲染的时间,不利于用户体验

(4) 开发单页面应用时,前端 Route 与服务器端 Route 不匹配,处理起来很麻烦。

(5) 重要内容都在前端组装,不利于 SEO

 

一个误区

很多人认定了 后端 = 服务端,前端 = 浏览器端 ,就像下面这张图。

 

一个方案 

在中途岛项目中,我们把前后端分界的那条线,从浏览器端移回到了服务器端

后端,专注于:(1) 服务层。 (2) 数据格式、数据稳定。 (3) 业务逻辑。

前端,专注于:(1) UI 层。 (2) 控制逻辑、渲染逻辑。 (3) 交互、用户体验。 

用着一样的模版语言 XTemplate,一样的渲染引擎 JavaScript。

也因为有了 Node.js 这一层,可以更细致的控制路由。

 

模版共享的实践

通常我们在浏览器端渲染一份模版时,流程不外乎是

在浏览器端載入模版引擎(XTmpleate、Juicer、handlerbar 等)

(2) 在浏览器端载入模版档案,方法可能有

* 使用 <script type="js/tpl"> ... </script> 印在页面上

* 使用模块载入工具,载入模版档案 (KISSY, requireJS, etc.)

* 其他

(3) 取得数据,使用模版引擎产生 HTML

(4) 将 HTML 插入到指定位置。

 

從以上的流程可以观察到,要想要做到模版的跨端共享,重点其实在一致的模块选型这件事。

市面上流行很多种模块标准,例如 KMD、AMD、CommonJS,只要能将NodeJS的模版档案透过一致模块规范输出到 Node.js 端,就可以做基本的模版共享了。

 

案例一 复杂交互应用(如购物车、下单页面)

    • 状况:全部的 HTML 都是在前端渲染完成,服务端仅提供接口。
    • 问题:进入页面时,会有短暂白屏
    • 解答:
      1. 首次进入页面,在 Node.js 端进行全页渲染,并在背景下载相关的模版。
      2. 后续交互操作,在浏览器端完成局部刷新
      3. 用的是同一份模版,产生一样的结果

案例二 单页面应用

    • 状况:使用 Client-side MVC 框架,在浏览器换页。
    • 问题:渲染与换页都在浏览器端完成,直接输入网址进入或 F5 刷新时,无法直接呈现同样的内容
    • 解答:
      1. 在浏览器端与 Node.js 端共享同样的 Route 设定
      2. 浏览器端换页时,在浏览器端进行 Route 变更与页面内容渲染
      3. 直接输入同样的网址时,在 Node.js 端进行“页面框架 + 页面内容”渲染
      4. 不管是浏览器端换页,或直接输入同样的网址,看到的内容都是一样的。
      5. 除了增加体验、减少逻辑複杂度外。更解决了 SEO 的问题

案例三 纯浏览型页面

    • 状况:页面仅提供资讯,较少或没有交互
    • 问题:HTML 在服务端产生,CSS 与 JS 放在另外一个位置,彼此间有依赖。
    • 解答:
      1. 透过 Node.js,统一管理 HTML + CSS + JS
      2. 日后若需要扩展成复杂应用或是单页面应用,也可以轻易转移。

案例四 跨终端页面

    • 状况:同样的应用要在不同端点呈现不同的介面与交互
    • 问题:HTML 管理不易,常常会在服务端产生不一样的HTML,浏览器端又要做不一样的处理
    • 解答:
      1. 跨终端的页面是渲染的问题,统一由前端来处理。
      2. 透过 Node.js 层与后端服务化,可以针对这类型复杂应用,设计最佳的解决方案。

 

 

 


第三篇


《Midway-ModelProxy — 轻量级的接口配置建模框架》

 

Node.js 在整个环境中最重要的工作之一就是代理这些业务接口,以方便前端(Node.js 端和浏览器端)整合数据做页面渲染。 

如何做好代理工作,使得前后端开发分离之后,仍然可以在流程上无缝衔接。

于是我们希望有这样一个框架,通过该框架提供的机制去描述工程项目中依赖的所有外部接口,对他们进行统一管理,同时提供灵活的接口建模及调用方式,并且提供便捷的线上环境和生产环境切换方法,使前后端开发无缝结合。

ModelProxy 就是满足这样要求的轻量级框架,它是 Midway Framework 核心构件之一,也可以单独使用。

(1) 开发者首先需要将工程项目中所有依赖的后端接口描述,按照指定的 JSON 格式,写入 interface.json 配置文件

(2) 必要时,需要对每个接口编写一个规则文件,也即图中 interface rules 部分。

该规则文件用于在开发阶段mock数据或者在联调阶段使用 River 工具集去验证接口。

规则文件的内容取决于采用哪一种 mock 引擎(比如 mockjs, river-mock 等等)。

Ref: 你是如何构建 Web 前端 Mock Server 的?【这一部分算是单独的一块儿知识点】

(3) 配置完成之后,即可在代码中按照自己的需求创建自己的业务 Model。

 

举例子 - 摆事实,讲道理

例一

第一步 在工程目录中创建接口配置文件 interface.json, 并在其中添加主搜接口 JSON 定义。

{
  "title": "pad淘宝项目数据接口集合定义",
  "version": "1.0.0",
  "engine": "mockjs",
  "rulebase": "./interfaceRules/",
  "status": "online",
  "interfaces": [{
    "name": "主搜索接口",
    "id": "Search.getItems",
    "urls": {
      "online": "http://s.m.taobao.com/client/search.do"
    }
  }]
}

第二步 在代码中创建并使用 Model。

// 引入模块
var ModelProxy = require('modelproxy');

// 全局初始化引入接口配置文件  (注意:初始化工作有且只有一次)
ModelProxy.init('./interface.json');

// 创建model 更多创建模式请参后文
var searchModel = new ModelProxy({
  searchItems: 'Search.getItems' // 自定义方法名: 配置文件中的定义的接口ID
});

// 使用model, 注意: 调用方法所需要的参数即为实际接口所需要的参数。
searchModel.searchItems({q: 'iphone6'})
  // !注意 必须调用 done 方法指定回调函数,来取得上面异步调用searchItems获得的数据!
  .done(function(data) {
    console.log(data);
  })
  .error(function(err) {
    console.log(err);
  });

  

ModelProxy 的功能丰富性在于它支持各种形式的 profile 以创建需要业务 Model:

  • 使用接口 ID 创建 > 生成的对象会取ID最后’.’号后面的单词作为方法名
ModelProxy.create('Search.getItem'); 
  • 使用键值 JSON 对象 > 自定义方法名: 接口 ID
ModelProxy.create({
  getName: 'Session.getUserName',
  getMyCarts: 'Cart.getCarts'
});
  • 使用数组形式 > 取最后 . 号后面的单词作为方法名
// 下例中生成的方法调用名依次为: Cart_getItem,getItem,suggest,getName
ModelProxy.create(['Cart.getItem', 'Search.getItem', 'Search.suggest', 'Session.User.getName']);
  • 前缀形式 > 所有满足前缀的接口ID会被引入对象,并取其后半部分作为方法名
ModelProxy.create('Search.*');

同时,使用这些 Model,你可以很轻易地实现合并请求或者依赖请求,并做相关模板渲染。

 

例二

合并请求

var model = new ModelProxy('Search.*');

// 合并请求 (下面调用的 Model 方法除 done 之外,皆为配置接口 Id 时指定)
model.suggest({q: '女'})
  .list({keyword: 'iphone6'})
  .getNav({key: '流行服装'})
  .done(function(data1, data2, data3) {
    // 参数顺序与方法调用顺序一致
    console.log(data1, data2, data3);
  });

 

例三

依赖请求

var model = new ModelProxy({
  getUser: 'Session.getUser',
  getMyOrderList: 'Order.getOrder'
});
// 先获得用户id,然后再根据id号获得订单列表
model.getUser({sid: 'fdkaldjfgsakls0322yf8'})
  .done(function(data) {
    var uid = data.uid;
    // 二次数据请求依赖第一次取得的id号
    this.getMyOrderList({id: uid})
      .done(function(data) {
        console.log(data);
      });
  });

 

例四

浏览器端使用 ModelProxy

此外 ModelProxy 不仅在 Node.js 端可以使用,也可以在浏览器端使用。只需要在页面中引入官方包提供的 modelproxy-client.js 即可。

<!-- 引入modelproxy模块,该模块本身是由KISSY封装的标准模块-->
<script src="modelproxy-client.js"></script>
<script type="text/javascript">
KISSY.use('modelproxy', function(S, ModelProxy) {
  // !配置基础路径,该路径与第二步中配置的拦截路径一致!
  // 且全局配置有且只有一次!
  ModelProxy.configBase('/model/');

  // 创建model
  var searchModel = ModelProxy.create('Search.*');
  searchModel
    .list({q: 'ihpone6'})
    .list({q: '冲锋衣'})
    .suggest({q: 'i'})
    .getNav({q: '滑板'})
    .done(function(data1, data2, data3, data4) {
      console.log({
        'list_ihpone6': data1,
        'list_冲锋衣': data2,
        'suggest_i': data3,
        'getNav_滑板': data4
      });
    });
});
</script>

 

ModelProxy 以一种配置化的轻量级框架存在,提供友好的接口 model 组装及使用方式,同时很好的解决前后端开发模式分离中的接口使用规范问题。

在整个项目开发过程中,接口始终只需要定义描述一次,前端开发人员即可引用,同时使用 River 工具自动生成文档,形成与后端开发人员的契约,并做相关自动化测试,极大地优化了整个软件工程开发过程。

 

 


第四篇


前后端分离模式下的安全解决方案 

只负责浏览器环境中开发的前端同学,需要涉猎到服务端层面,编写服务端代码。

而摆在面前的一个基础性问题就是如何保障 Web 安全? 

 

跨站脚本攻击(XSS)的防御 -  HTML escape

是什么?

跨站脚本攻击(XSS,Cross-site scripting),攻击者可以在网页上发布包含攻击性代码的数据,当浏览者看到此网页时,特定的脚本就会以浏览者用户的身份和权限来执行。

通过 XSS 可以比较容易地修改用户数据、窃取用户信息以及造成其它类型的攻击,例如:CSRF 攻击。

怎么办?

预防 XSS 攻击的基本方法是:确保任何被输出到 HTML 页面中的数据以 HTML 的方式进行转义(HTML escape)。

Ref: HTML转义字符大全

<textarea name="description">
</textarea><script>alert('hello')'</script>
</textarea>

---------------------------------------------------------------------------
将 $description 的值进行 HTML escape,转义后的输出代码如下: --------------------------------------------------------------------------- <textarea name="description"> &lt;/textarea&gt;&lt;script&gt;alert(&quot;hello!&quot;)&lt;/script&gt; </textarea>

 

跨站请求伪造攻击(CSRF)的预防

/* 详见原文 */

 

关于Node.js

XSS 等注入性漏洞是所有漏洞中最容易被忽略,占互联网总攻击的70%以上;

开发者编写 Node.js 代码时,要时刻提醒自己,永远不要相信用户的输入。

 

 

 

第五篇


基于前后端分离的多终端适配

 

基于 Web 的多终端适配进行得如火如荼,行业间也发展出依赖各种技术的解决方案。

1) 基于浏览器原生 CSS3 Media Query 的响应式设计、

2) 基于云端智能重排的「云适配」方案等。

本文则主要探讨在前后端分离基础下的多终端适配方案。

方案:因为 Node.js 层彻底与数据抽离,同时无需关心大量的业务逻辑,所以十分适合在这一层进行多终端的适配工作。

 

UA 探测

进行多终端适配首先要解决的是 UA 探测问题。现在市面上已经有非常成熟的兼容大量设备的 User Agent 特征库和探测工具,这里有 Mozilla 整理的一个Compatibility/UADetectionLibraries

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

其中,既有运行在浏览器端的,也有运行在服务端代码层的,甚至有些工具提供了 nginx/Apache 的模块,负责解析每个请求的 UA 信息

我们把这个行为再往前挪,挂在 nginx/Apache 上,它们负责:(1) 解析每个请求的 UA 信息;(2) 再通过如 HTTP Header 的方式传递给业务代码

这样做有几点好处:

(1) 我们的代码里面无需再去关注 UA 如何解析,直接从上层取出解析后的信息即可。

(2) 如果在同一台服务器上有多个应用,则能够共同使用同一个 nginx 解析后的 UA 信息,节省了不同应用间的解析损耗。a

 

 

【以下内容,日后再细看】

 

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

取得 UA 信息后,我们就要考虑如何根据指定的 UA 进行终端适配了。

即使在 Node.js 层,虽然没有了大部分的业务逻辑,但我们依然把内部区分为 Model / Controller / View 三个模型。

利用该图,去解析一些已有的多终端适配方案。

 

建立在 Controller 上的适配方案

 

建立在 Router 上的适配方案 

优化后: 

  

建立在 View 层的适配方案

 

posted @ 2018-02-06 12:05  郝壹贰叁  阅读(272)  评论(0编辑  收藏  举报