前言
使用 Node 做前后端分离的开发模式带来了一些性能及开发流程上的优势, 但同时也面临不少挑战。在淘宝复杂的业务及技术架构下,后端必须依赖 Java 搭建基础架构,同时提供相关业务接口供前端使用。Node 在整个环境中最重要的工作之一就是代理这些业务接口,以方便前端 (Node 端和浏览器端) 整合数据做页面渲染。如何做好代理工作,使得前后端开发分离之后,仍然可以在流程上无缝衔接,是我们需要考虑的问题。本文将就该问题做相关探讨,并提出解决方案。
由于后端提供的接口方式可能多种多样,同时开发人员在编写 Node 端代码访问这些接口的方式也有可能多种多样。如果我们在接口访问方式及使用上不做统一架构处理,则会带来以下一些问题:
1. 每一个开发人员使用各自的代码风格编写接口访问代码,造成工程目录及编码风格混乱,维护相对困难。 2. 每一个开发人员编写自己的 mock 数据方式,开发完毕之后,需要手工修改代码移除 mock。 3. 每一个开发人员为了实现接口的不同环境切换 (日常,预发,线上),可能各自维护了一些配置文件。 4. 数据接口调用方式无法被各个业务 model 非常方便地复用。 5. 对于数据接口的描述约定散落在代码的各个角落,有可能跟后端人员约定的接口文档不一致。 6. 整个项目分离开发之后,对于接口的联调或者测试回归成本依然很高,需要涉及到每一个接口提供者和使用者。 于是我们希望有这样一个框架,通过该框架提供的机制去描述工程项目中依赖的所有外部接口,对他们进行统一管理,同时提供灵活的接口建模及调用方式,并且提供便捷的线上环境和生产环境切换方法,使前后端开发无缝结合。ModelProxy 就是满足这样要求的轻量级框架,它是 Midway Framework 核心构件之一,也可以单独使用。使用 ModelProxy 可以带来如下优点:
1. 不同的开发者对于接口访问代码编写方式统一,含义清晰,降低维护难度。 2. 框架内部采用工厂 + 单例模式,实现接口一次配置多次复用。并且开发者可以随意定制组装自己的业务 Model(依赖注入)。 3. 可以非常方便地实现线上,日常,预发环境的切换。 4. 内置 river-mock 和 mockjs 等 mock 引擎,提供 mock 数据非常方便。 5. 使用接口配置文件,对接口的依赖描述做统一的管理,避免散落在各个代码之中。 6. 支持浏览器端共享 Model,浏览器端可以使用它做前端数据渲染。整个代理过程对浏览器透明。 7. 接口配置文件本身是结构化的描述文档,可以使用 river 工具集合,自动生成文档。也可使用它做相关自动化接口测试,使整个开发过程形成一个闭环。
ModelProxy 工作原理图及相关开发过程图览
在上图中,开发者首先需要将工程项目中所有依赖的后端接口描述,按照指定的 json 格式,写入 interface.json 配置文件。必要时,需要对每个接口编写一个规则文件,也即图中 interface rules 部分。该规则文件用于在开发阶段 mock 数据或者在联调阶段使用 River 工具集去验证接口。规则文件的内容取决于采用哪一种 mock 引擎(比如 mockjs, river-mock 等等)。配置完成之后,即可在代码中按照自己的需求创建自己的业务 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 不仅在 Node 端可以使用,也可以在浏览器端使用。只需要在页面中引入官方包提供的 modelproxy-client.js 即可。 【例四】浏览器端使用 ModelProxy
- <!-- 引入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} ); } ); } );
来源: http://www.phperz.com/article/17/0425/274697.html