轻量级的接口配置建模框架,前后端分离了

内外端分离了,然后呢?

2015/07/09 · CSS, HTML5, JavaScript · 2 评论 · 前后端

原稿出处: 邱俊涛的博客(@正反反长卡塔 尔(英语:State of Qatar)   

 

据说NodeJS的光景端抽离的思辨与施行(三卡塔尔轻量级的接口配置建立模型框架,nodejs建立模型

前言

运用Node做上下端抽离的开采情势带给了有的特性及开垦流程上的优势, 但同期也面前碰到不菲挑衅。在Tmall复杂的工作及本领架构下,后端必需信任Java搭建根基架构,同一时间提供有关作业接口供前端接受。Node在一切情况中最要害的办事之生机勃勃正是代理那一个专门的学问接口,以便于前端(Node端和浏览器端)整合数据做页面渲染。怎样办好代理专门的学问,使得上下端支付分离之后,如故能够在流程上无缝对接,是大家供给酌量的难点。本文将就该难点做连锁商量,并提议应用方案。

图片 1

由于后端提供的接口方式大概三种两种,同一时间开拓人士在编写Node端代码访问那一个接口的不二秘技也可以有相当的大可能率各个三种。假诺大家在接口访谈方式及选取上不做联合架构处理,则会推动以下部分难题:

1. 每多个开垦人士使用分其他代码风格编写接口访问代码,产生工程目录及编码风格混乱,维护相对劳苦。
2. 每三个开荒职员编写本人的mock数据方式,开垦实现之后,必要手工业余学校正代码移除mock。
3. 每一种开拓人士为了完成接口的两样情状切换(平日,预发,线上),也许各自维护了有的布局文件。

  1. 数量接口调用格局不能被每个业务model极其常有益地复用。
    5. 对此数据接口的叙说约定散落在代码的各样角落,有十分大希望跟后端人士约定的接口文书档案不均等。
    6. 全方位项目分别开垦今后,对于接口的联调只怕测量试验回归开销依旧相当的高,供给涉及到每几个接口提供者和使用者。
    于是乎我们意在有这样多个框架,通过该框架提供的体制去描述工程项目中依靠的保有外部接口,对他们举办联合保管,同期提供灵活的接口建立模型及调用方式,并且提供便利的线上景况和生育遭逢切换情势,使前后端支出无缝结合。ModelProxy正是满意那样需要的轻量级框架,它是Midway Framework 宗旨零件之大器晚成,也足以独立采纳。使用ModelProxy能够拉动如下优点:

  2. 不等的开垦者对于接口访谈代码编写方式统风流罗曼蒂克,含义清晰,减弱维护难度。
    2. 框架之中接收工厂 单例方式,达成接口三遍配置数十次复用。並且开拓者能够轻松定制组装本人的事体Model(注重注入)。

  3. 能够十一分便利地落实线上,通常,预发处境的切换。
  4. 内置river-mock和mockjs等mock引擎,提供mock数据极度常有帮衬。
    5. 用到接口配置文件,对接口的依附描述做联合的田间管理,制止散落在每种代码之中。
    6. 支撑浏览器端分享Model,浏览器端能够利用它做前端数据渲染。整个代理进程对浏览器透明。
    7. 接口配置文件自己是结构化的陈述文书档案,能够使用river工具集结,自动生成文书档案。也可使用它做连锁自动化接口测验,使一切开辟进程变成叁个闭环。

ModelProxy职业规律图及连锁支付进程图览

图片 2

在上海教室中,开垦者首先须要将工程项目中兼有信赖的后端接口描述,根据内定的json格式,写入interface.json配置文件。必要时,须要对每一种接口编写三个法则文件,也即图中interface rules部分。该法则文件用于在开辟阶段mock数据还是在联调阶段接收River工具集去印证接口。准绳文件的内容在于接受哪大器晚成种mock引擎(比方mockjs, river-mock 等等卡塔 尔(英语:State of Qatar)。配置完结今后,就可以在代码中信守自身的必要创制和睦的作业model。

下边是二个轻易的例子:

【例一】

率先步 在工程目录中开创接口配置文件interface.json, 并在其间增加主搜接口json定义

复制代码 代码如下:

{
    "title": "pad天猫项目数量接口集结定义",
    "version": "1.0.0",
    "engine": "mockjs",
    "rulebase": "./interfaceRules/",
    "status": "online",
    "interfaces": [ {
        "name": "主找出接口",
        "id": "Search.getItems",
        "urls": {
            "online": ""
        }
    } ]
}

其次步 在代码中开创并利用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
                } );
            } );
    } );
</script>

同期,ModelProxy能够协作Midway另一中坚组件Midway-XTPL一同行使,完成数据和模板甚至相关渲染进度在浏览器端和劳动器端的全分享。关于ModelProxy的详实教程及文书档案请移步

总结

ModelProxy以黄金年代种配置化的轻量级框架存在,提供温馨的接口model组装及使用方法,同期很好的解决前后端支出情势抽离中的接口使用正式难点。在漫天项目支付进度中,接口始终只必要定义描述二次,前端开采人士就能够援用,同不常候接受River工具自动生成文书档案,造成与后端开辟人士的协议,并做相关自动化测量试验,比非常的大地优化了整套软件工程开荒进程。

【注】River 是阿里公司研究开发的左右端统风流倜傥接口标准及有关工具会集的统称

前言

左右端分离已然是产业界所共鸣的大器晚成种开辟/布署格局了。所谓的光景端分离,并不是历史观行个中的按单位分割,生机勃勃部分人纯做前端(HTML/CSS/JavaScript/Flex卡塔尔国,另风姿罗曼蒂克局地人纯做后端,因为这种方式是不工作的:比方相当多团体利用了后端的模板本事(JSP, Free马克尔, ERB等等),前端的支出和调整须求一个后台Web容器的帮衬,进而没办法变成真正的分开(更毫不提在安插的时候,由于动态内容和静态内容混在一块儿,当设计动态静态分流的时候,管理起来异常麻烦卡塔 尔(英语:State of Qatar)。关于前后端支出的另叁个座谈能够仿效这里。

就是通过API来解耦前端和后端开荒进程,前后端通过RESTFul的接口来通讯,前端的静态内容和后端的动态总结分别支付,分别布置,集成仍是八个绕不开的主题材料— 前端/后端的利用都得以单独的周转,可是集成起来却不坐班。大家供给花销大量的生命力来调治,直到上线前依旧未有人有信心有所的接口都是干活的。

 前言

少数背景

叁个杰出的Web应用的布局看起来是这么的:

图片 3

左右端都各自有自身的开垦流程,创设筑工程具,测验集结等等。前后端仅仅经过接口来编制程序,那个接口恐怕是JSON格式的RESTFul的接口,也可能是XML的,注重是后台只承受数据的提供和计量,而完全不管理表现。而前面多少个则承当获得数量,组织数据并显现的劳作。那样结构清晰,关怀点抽离,前后端会变得相对独立并松耦合。

上述的场地依然相比较可观,大家实在在事实上条件中会有特别复杂的景观,举例异构的互联网,异构的操作系统等等:

图片 4

在其实的风貌中,后端大概还有大概会更头昏眼花,举例用C语言做多少收罗,然后通过Java整合到叁个数据旅舍,然后该数据旅馆又有豆蔻梢头层Web Service,最终若干个这么的Web Service又被贰个Ruby的聚合Service整合在一块儿回来给前端。在如此三个繁琐的系统中,后台自便端点的挫败都大概过不去前端的支出流程,由此我们会动用mock的艺术来缓慢解决那一个主题素材:

图片 5

以此mock服务器能够运行一个粗略的HTTP服务器,然后将一些静态的剧情serve出来,以供前端代码应用。那样的益处多多:

  1. 前后端支付相对独立
  2. 后端的快慢不会影响前端开采
  3. 启船舶的速度度更加快
  4. 前后端都能够运用自身熟识的技巧栈(让前面叁个的学maven,让后端的用gulp都会特别不顺手卡塔 尔(阿拉伯语:قطر‎

但是当集成照例是三个令人头痛的难点。大家反复在合龙的时候才发觉,本来协商的数据结构变了:deliveryAddress字段本来是贰个字符串,今后改成数组了(业务产生了改变,系统现在得以支持八个快递地址卡塔 尔(英语:State of Qatar);price字段造成字符串,协商的时候是number;顾客邮箱地址多了叁个层级等等。这一个改换在所无免,而且产生,那会耗费大量的调治时间和购并时间,更别提改进现在的回归测量试验了。

进而只是使用一个静态服务器,然后提供mock数据是相当远远不足的。大家须求的mock应该还能够到位:

  1. 前端信任钦点格式的mock数据来实行UI开采
  2. 前面三个的开销和测试都基于这几个mock数据
  3. 后端发生钦点格式的mock数据
  4. 后端须要测量检验来作保生成的mock数据正是前端须求的

粗略,大家须求签订一些公约,并将那几个协议作为能够被测量检验的中级格式。然后前后端都供给有测量试验来利用那么些协议。风华正茂旦左券发生变化,则另一方的测量检验会退步,那样就可以使得双方合计,并裁减集成时的荒疏。

二个实际的场景是:前端开掘已部分有个别协议中,紧缺了三个address的字段,于是就在公约中增多了该字段。然后在UI少将以此字段正确的显现了(当然还安装了字体,字号,颜色等等卡塔 尔(阿拉伯语:قطر‎。可是后台湾学生成该协议的劳务并从未感知到那后生可畏变型,当运营生成合同部分测验(后台卡塔尔国时,测量检验会退步了 — 因为它并不曾变化这几个字段。于是后端程序猿就找前带给合计,领会职业逻辑之后,他会修正代码,并保险测量检验通过。那样,当集成的时候,就不会并发UI上少了四个字段,可是哪个人也不知情是前者难题,后端难题,依旧数据库难题等。

何况事实上的品类中,往往都以多个页面,多少个API,多少个版本,四个团体还要扩充付出,这样的左券会减弱相当多的调节和测验时间,使得集成相对平缓。

在执行中,协议能够定义为叁个JSON文件,也许三个XML的payload。只须求保险前后端分享同叁个合同集合来做测验,那么集成专业就能从当中收益。多个最简便易行的样式是:提供部分静态的mock文件,而前面三个有着发未来台的央浼都被某种机制拦截,并调换到对该静态财富的号召。

  1. moco,基于Java
  2. wiremock,基于Java
  3. sinatra,基于Ruby

总的来看sinatra被列在此处,大概精通Ruby的人会辩驳:它可是一个后端全职能的的程序库啊。之所以列它在此边,是因为sinatra提供了黄金时代套简洁精彩的DSL,这么些DSL非常切合Web语言,作者找不到更神奇的法子来驱动这些mock server特别易读,所以就应用了它。

  前后端分离已是产业界所共鸣的风流倜傥种开垦/布署形式了。所谓的内外端分离,并不是守旧行当中的按机关分割,风度翩翩部分人纯做前端(HTML/CSS/JavaScript/Flex卡塔 尔(阿拉伯语:قطر‎,另意气风发有的人纯做后端,因为这种艺术是不坐班的:比方比超多协会利用了后端的模板技能(JSP, Free马克尔, ERB等等卡塔尔国,前端的成本和调整要求三个后台Web容器的支撑,进而无法成功真正的握别(更不用提在陈设的时候,由于动态内容和静态内容混在一起,当设计动态静态分流的时候,管理起来特别费力卡塔 尔(阿拉伯语:قطر‎。关于前后端支出的另一个商量能够参考这里。

前言 使用Node做上下端抽离的支付情势带给了部分质量及...

三个事例

笔者们以那一个动用为示范,来阐明怎么着在前后端分离之后,保障代码的成色,并减少集成的花销。这几个动用处景很简单:全体人都能够看到四个条文列表,每个登录客户都得以采用本身喜好的条文,并为之加星。加星之后的规行矩步会保留到顾客本人的民用宗旨中。客户分界面看起来是如此的:

图片 6

不过为了专心在我们的中坚上,作者去掉了诸如登入,个人基本之类的页面,若是你是叁个已报到客户,然后大家来看看哪些编写测量检验。

  纵然通过API来解耦前端和后端开辟进度,前后端通过RESTFul的接口来通信,前端的静态内容和后端的动态总计分别支付,分别配备,集成仍然为多少个绕不开的难点— 前端/后端的运用都能够独自的运作,不过集成起来却不干活。我们须要开销大量的肥力来调解,直到上线前依然没有人有信念有所的接口都以做事的。

前端开拓

听大人说平常的做法,前后端抽离之后,我们超轻巧mock一些数码来本身测量检验:

XHTML

[ { "id": 1, "url": "", "title": "Python中的 list comprehension 以及 generator", "publicDate": "2015年3月20日" }, { "id": 2, "url": "", "title": "使用inotify/fswatch塑造自动监察和控制脚本", "publicDate": "二零一六年四月1日" }, { "id": 3, "url": "", "title": "使用underscore.js构建前端选取", "publicDate": "2016年四月18日" } ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
    {
        "id": 1,
        "url": "http://abruzzi.github.com/2015/03/list-comprehension-in-python/",
        "title": "Python中的 list comprehension 以及 generator",
        "publicDate": "2015年3月20日"
    },
    {
        "id": 2,
        "url": "http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/",
        "title": "使用inotify/fswatch构建自动监控脚本",
        "publicDate": "2015年2月1日"
    },
    {
        "id": 3,
        "url": "http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/",
        "title": "使用underscore.js构建前端应用",
        "publicDate": "2015年1月20日"
    }
]

然后,三个可能的不二等秘书技是通过诉求那些json来测量检验前台:

JavaScript

$(function() { $.get('/mocks/feeds.json').then(function(feeds) { var feedList = new Backbone.Collection(extended); var feedListView = new FeedListView(feedList); $('.container').append(feedListView.render()); }); });

1
2
3
4
5
6
7
8
$(function() {
  $.get('/mocks/feeds.json').then(function(feeds) {
      var feedList = new Backbone.Collection(extended);
      var feedListView = new FeedListView(feedList);
 
      $('.container').append(feedListView.render());
  });
});

像这种类型自然是足以干活的,不过此间发送央浼的url而不是最终的,当集成的时候大家又要求修正为真正的url。四个精简的做法是使用Sinatra来做三遍url的调换:

Shell

get '/api/feeds' do content_type 'application/json' File.open('mocks/feeds.json').read end

1
2
3
4
get '/api/feeds' do
  content_type 'application/json'
  File.open('mocks/feeds.json').read
end

这样,当大家和实际的服务开展归拢时,只须要连接到不行服务器就足以了。

小心,大家现在的骨干是mocks/feeds.json这么些文件。那一个文件今后的剧中人物正是叁个合同,起码对于前带给讲是如此的。紧接着,我们的接收须要渲染加星的机能,那就供给此外二个合同:寻觅当下客商加星过的保有规规矩矩,由此我们投入了一个新的契约:

XHTML

[ { "id": 3, "url": "", "title": "使用underscore.js创设前端选取", "publicDate": "2014年三月二日" } ]

1
2
3
4
5
6
7
8
[
    {
        "id": 3,
        "url": "http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/",
        "title": "使用underscore.js构建前端应用",
        "publicDate": "2015年1月20日"
    }
]

然后在sinatra中投入叁个新的酷炫:

XHTML

get '/api/fav-feeds/:id' do content_type 'application/json' File.open('mocks/fav-feeds.json').read end

1
2
3
4
get '/api/fav-feeds/:id' do
  content_type 'application/json'
  File.open('mocks/fav-feeds.json').read
end

透过那多少个伏乞,大家会拿走八个列表,然后依照那五个列表的插花来绘制出具有的星号的景况(有的是空心,有的是实心卡塔尔:

JavaScript

$.when(feeds, favorite).then(function(feeds, favorite) { var ids = _.pluck(favorite[0], 'id'); var extended = _.map(feeds[0], function(feed) { return _.extend(feed, {status: _.includes(ids, feed.id)}); }); var feedList = new Backbone.Collection(extended); var feedListView = new FeedListView(feedList); $('.container').append(feedListView.render()); });

1
2
3
4
5
6
7
8
9
10
11
$.when(feeds, favorite).then(function(feeds, favorite) {
    var ids = _.pluck(favorite[0], 'id');
    var extended = _.map(feeds[0], function(feed) {
        return _.extend(feed, {status: _.includes(ids, feed.id)});
    });
 
    var feedList = new Backbone.Collection(extended);
    var feedListView = new FeedListView(feedList);
 
    $('.container').append(feedListView.render());
});

剩余的多少个主题素材是当点击红心时,大家必要发诉求给后端,然后更新红心的气象:

JavaScript

toggleFavorite: function(event) { event.preventDefault(); var that = this; $.post('/api/feeds/' this.model.get('id')).done(function(){ var status = that.model.get('status'); that.model.set('status', !status); }); }

1
2
3
4
5
6
7
8
toggleFavorite: function(event) {
    event.preventDefault();
    var that = this;
    $.post('/api/feeds/' this.model.get('id')).done(function(){
        var status = that.model.get('status');
        that.model.set('status', !status);
    });
}

这里又多出来一个伸手,但是使用Sinatra大家还是得以十分轻便的帮忙它:

JavaScript

post '/api/feeds/:id' do end

1
2
post '/api/feeds/:id' do
end

能够见见,在未曾后端的状态下,大家全部都实行顺遂 — 后端以致还不曾起来做,大概正在由八个进程比大家慢的集体在支付,可是不在乎,他们不会影响咱们的。

不独有如此,当大家写完前端的代码之后,能够做叁个End2End的测验。由于使用了mock数据,免去了数据库和网络的耗费时间,那一个End2End的测量试验会运营的百般快,况兼它的确起到了端到端的效率。那些测量试验在终极的合一时,还是能够用来当UI测量试验来运转。所谓一举多得。

JavaScript

#encoding: utf-8 require 'spec_helper' describe 'Feeds List Page' do let(:list_page) {FeedListPage.new} before do list_page.load end it 'user can see a banner and some feeds' do expect(list_page).to have_banner expect(list_page).to have_feeds end it 'user can see 3 feeds in the list' do expect(list_page.all_feeds).to have_feed_items count: 3 end it 'feed has some detail information' do first = list_page.all_feeds.feed_items.first expect(first.title).to eql("Python中的 list comprehension 以及 generator") end end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#encoding: utf-8
require 'spec_helper'
 
describe 'Feeds List Page' do
  let(:list_page) {FeedListPage.new}
 
  before do
      list_page.load
  end
 
  it 'user can see a banner and some feeds' do
      expect(list_page).to have_banner
      expect(list_page).to have_feeds
  end
 
  it 'user can see 3 feeds in the list' do
      expect(list_page.all_feeds).to have_feed_items count: 3
  end
 
  it 'feed has some detail information' do
      first = list_page.all_feeds.feed_items.first
      expect(first.title).to eql("Python中的 list comprehension 以及 generator")
  end
end

图片 7

有关什么编写那样的测验,能够参照他事他说加以考察以前写的那篇文章。

  一点背景

  一个超人的Web应用的布局看起来是那样的:

图片 8

  前后端都分别有温馨的支付流程,营造筑工程具,测量试验集结等等。前后端仅仅通过接口来编制程序,这么些接口或者是JSON格式的RESTFul的接口,也可能是XML的,着重是后台只承受数据的提供和计量,而浑然不管理表现。而前面三个则负担得到数量,组织数量并呈现的做事。那样结构清晰,关切点抽离,前后端会变得相对独立并松耦合。

  上述的场合依然比较可观,我们其实在实际上条件中会有特别复杂的场景,比如异构的互连网,异构的操作系统等等:

图片 9

  在骨子里的场所中,后端大概还有恐怕会更头晕目眩,譬如用C语言做多少搜罗,然后通过Java整合到三个数据酒馆,然后该数据仓库又有风姿罗曼蒂克层Web 瑟维斯,最终若干个这么的Web Service又被叁个Ruby的聚合Service整合在联合重临给前端。在这里样一个错综相连的类别中,后台任性端点的曲折都大概窒碍前端的费用流程,由此大家会使用mock的艺术来消除这几个主题材料:

图片 10

  这个mock服务器能够运维二个总结的HTTP服务器,然后将一些静态的剧情serve出来,以供前端代码应用。那样的利润多多:

  1. 前后端支出相对独立
  2. 后端的进程不会潜移暗化前端开采
  3. 开头速度越来越快
  4. 内外端都足以运用本人熟知的技巧栈(让前面三个的学maven,让后端的用gulp都会特别不顺手卡塔尔

  不过当集成仍然是二个让人脑瓜疼的难点。大家一再在合龙的时候才意识,本来协商的数据结构变了:deliveryAddress字段本来是二个字符串,将来变为数组了(业务发生了变动,系统未来得以支撑四个快递地址卡塔尔国;price字段产生字符串,协商的时候是number;客户邮箱地址多了二个层级等等。这几个更换在劫难逃,并且发生,那会费用多量的调节和测验时间和合并时间,更别提改革以往的回归测量检验了。

  所以仅仅使用一个静态服务器,然后提供mock数量是远远不足的。大家要求的mock有道是还是可以一刀两断:

  1. 前端重视钦定格式的mock数据来扩充UI开垦
  2. 前者的开荒和测量检验都基于这个mock数据
  3. 后端发生内定格式的mock数据
  4. 后端要求测量检验来担保生成的mock数据就是前端要求的

  由此可见,大家供给签署一些公约,并将这么些公约作为能够被测验的中间格式。然后前后端都亟待有测验来使用那么些合同。大器晚成旦公约产生变化,则另一方的测验会退步,那样就能够使得双方共同商议,并收缩集成时的浪费。

  三个实际上的情形是:前端开掘已有个别有个别左券中,紧缺了三个address的字段,于是就在协议中增多了该字段。然后在UI中将这些字段正确的展现了(当然还设置了字体,字号,颜色等等卡塔 尔(英语:State of Qatar)。但是后台湾学子成该契约的劳动并不曾感知到那意气风发变化,当运转生成协议部分测量试验(后台卡塔尔国时,测量试验会战败了 — 因为它并未有生成那一个字段。于是后端程序猿就找前带给探讨,领悟工作逻辑之后,他会更正代码,并保管测量试验通过。那样,当集成的时候,就不会鬼使神差UI上少了一个字段,可是哪个人也不亮堂是前面一个难题,后端难题,仍旧数据库难题等。

  而且事实上的类型中,往往都以多个页面,几个API,八个版本,多少个集体还要拓张开荒,那样的合同会收缩非常多的调理时间,使得集成相对平缓。

  在施行中,公约能够定义为四个JSON文件,也许三个XML的payload。只需求保障前后端分享同多个合同会集来做测量试验,那么集成专门的学问就能从当中获益。一个最简易的格局是:提供一些静态的mock文件,而后边叁个有着发现在台的须求都被某种机制拦截,并转变到对该静态能源的乞请。

  1. moco,基于Java
  2. wiremock,基于Java
  3. sinatra,基于Ruby

  看到sinatra被列在那处,恐怕熟习Ruby的人会批驳:它只是三个后端全职能的的程序库啊。之所以列它在这里,是因为sinatra提供了意气风发套简洁优美的DSL,这个DSL可怜适合Web言语,作者找不到更完美的措施来驱动那个mock server一发易读,所以就应用了它。

后端开拓

本身在这里个示例中,后端采纳了spring-boot作为示范,你应当能够比较轻便将看似的笔触应用到Ruby或然其余语言上。

首先是诉求的进口,FeedsController会担任拆解深入分析倡议路径,查数据库,最终回来JSON格式的多少。

JavaScript

@Controller @RequestMapping("/api") public class FeedsController { @Autowired private FeedsService feedsService; @Autowired private UserService userService; public void setFeedsService(FeedsService feedsService) { this.feedsService = feedsService; } public void setUserService(UserService userService) { this.userService = userService; } @RequestMapping(value="/feeds", method = RequestMethod.GET) @ResponseBody public Iterable<Feed> allFeeds() { return feedsService.allFeeds(); } @RequestMapping(value="/fav-feeds/{userId}", method = RequestMethod.GET) @ResponseBody public Iterable<Feed> favFeeds(@PathVariable("userId") Long userId) { return userService.favoriteFeeds(userId); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Controller
@RequestMapping("/api")
public class FeedsController {
 
    @Autowired
    private FeedsService feedsService;
 
    @Autowired
    private UserService userService;
 
    public void setFeedsService(FeedsService feedsService) {
        this.feedsService = feedsService;
    }
 
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
 
    @RequestMapping(value="/feeds", method = RequestMethod.GET)
    @ResponseBody
    public Iterable<Feed> allFeeds() {
        return feedsService.allFeeds();
    }
 
    @RequestMapping(value="/fav-feeds/{userId}", method = RequestMethod.GET)
    @ResponseBody
    public Iterable<Feed> favFeeds(@PathVariable("userId") Long userId) {
        return userService.favoriteFeeds(userId);
    }
}

切实查询的内部景况大家就不做商讨了,感兴趣的能够在篇章结尾处找到代码库的链接。那么有了那一个Controller之后,我们怎么着测量检验它吧?只怕说,怎样让合同变得实在可用呢?

spring-test提供了十三分赏心悦指标DSL来编排测量试验,大家仅须求或多或少代码就足以将合同用起来,并实际的监督接口的改革:

Java

private MockMvc mockMvc; private FeedsService feedsService; private UserService userService; @Before public void setup() { feedsService = mock(FeedsService.class); userService = mock(UserService.class); FeedsController feedsController = new FeedsController(); feedsController.setFeedsService(feedsService); feedsController.setUserService(userService); mockMvc = standaloneSetup(feedsController).build(); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private MockMvc mockMvc;
private FeedsService feedsService;
private UserService userService;
 
@Before
public void setup() {
    feedsService = mock(FeedsService.class);
    userService = mock(UserService.class);
 
    FeedsController feedsController = new FeedsController();
    feedsController.setFeedsService(feedsService);
    feedsController.setUserService(userService);
 
    mockMvc = standaloneSetup(feedsController).build();
}

树立了mockmvc之后,我们就足以编写制定Controller的单元测量检验了:

JavaScript

<a href='; public void shouldResponseWithAllFeeds() throws Exception { when(feedsService.allFeeds()).thenReturn(Arrays.asList(prepareFeeds())); mockMvc.perform(get("/api/feeds")) .andExpect(status().isOk()) .andExpect(content().contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$", hasSize(3))) .andExpect(jsonPath("$[0].publishDate", is(notNullValue()))); }

1
2
3
4
5
6
7
8
9
10
<a href='http://www.jobbole.com/members/madao'>@Test</a>
public void shouldResponseWithAllFeeds() throws Exception {
    when(feedsService.allFeeds()).thenReturn(Arrays.asList(prepareFeeds()));
 
    mockMvc.perform(get("/api/feeds"))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json;charset=UTF-8"))
            .andExpect(jsonPath("$", hasSize(3)))
            .andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
}

当发送GET诉求到/api/feeds上之后,大家愿意再次来到状态是200,然后内容是application/json。然后大家预料再次来到的结果是一个长度为3的数组,然后数组中的第七个要素的publishDate字段不为空。

只顾此处的prepareFeeds方法,事实上它会去加载mocks/feeds.json文件 — 也正是前面二个用来测量试验的mock文件:

JavaScript

private Feed[] prepareFeeds() throws IOException { URL resource = getClass().getResource("/mocks/feeds.json"); ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(resource, Feed[].class); }

1
2
3
4
5
private Feed[] prepareFeeds() throws IOException {
    URL resource = getClass().getResource("/mocks/feeds.json");
    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(resource, Feed[].class);
}

与此相类似,当后端修正Feed定义(加多/删除/校勘字段卡塔尔,或然涂改了mock数据等,都会促成测验战败;而前面一个校勘mock之后,也会导致测验退步— 不要惧怕战败 — 这样的战败会推动贰回协商,并驱动出最后的service的合同。

相应的,测验/api/fav-feeds/{userId}的点子左近:

JavaScript

<a href='; public void shouldResponseWithUsersFavoriteFeeds() throws Exception { when(userService.favoriteFeeds(any(Long.class))) .thenReturn(Arrays.asList(prepareFavoriteFeeds())); mockMvc.perform(get("/api/fav-feeds/1")) .andExpect(status().isOk()) .andExpect(content().contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$", hasSize(1))) .andExpect(jsonPath("$[0].title", is("使用underscore.js营造前端接收"))) .andExpect(jsonPath("$[0].publishDate", is(notNullValue()))); }

1
2
3
4
5
6
7
8
9
10
11
12
<a href='http://www.jobbole.com/members/madao'>@Test</a>
public void shouldResponseWithUsersFavoriteFeeds() throws Exception {
    when(userService.favoriteFeeds(any(Long.class)))
        .thenReturn(Arrays.asList(prepareFavoriteFeeds()));
 
    mockMvc.perform(get("/api/fav-feeds/1"))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json;charset=UTF-8"))
            .andExpect(jsonPath("$", hasSize(1)))
            .andExpect(jsonPath("$[0].title", is("使用underscore.js构建前端应用")))
            .andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
}

  叁个例证

  大家以那么些应用为示范,来验证什么在左右端抽离之后,保险代码的材料,并减弱集成的本钱。这几个应用处景很简短:全部人都得以看看五个条约列表,每一种登录客商都足以接纳自个儿喜欢的条款,并为之加星。加星之后的条规会保留到客商自身的个人中心中。顾客分界面看起来是那样的:

图片 11

  可是为了潜心在我们的大旨上,作者去掉了诸如登录,个人基本之类的页面,若是你是三个已报到顾客,然后大家来看看哪些编写测量试验。

总结

上下端分离是风流洒脱件轻易的事体,并且组织大概在短时间能够见见不菲好处,然而假诺不认真管理集成的主题素材,抽离反而恐怕会带来更加长的三合有时间。通过面向协议的不二诀要来组织各自的测验,能够带动许多的裨益:更急速的End2End测量试验,更平整的集成,更安全的拜别开辟等等。

  前端开辟

  依据经常的做法,前后端抽离之后,大家超轻便mock部分数码来自个儿测试:

[
    {
        "id": 1,
        "url": "http://abruzzi.github.com/2015/03/list-comprehension-in-python/",
        "title": "Python中的 list comprehension 以及 generator",
        "publicDate": "2015年3月20日"
    },
    {
        "id": 2,
        "url": "http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/",
        "title": "使用inotify/fswatch构建自动监控脚本",
        "publicDate": "2015年2月1日"
    },
    {
        "id": 3,
        "url": "http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/",
        "title": "使用underscore.js构建前端应用",
        "publicDate": "2015年1月20日"
    }
]

  然后,四个可能的点子是透过诉求那么些json来测验前台:

$(function() {
  $.get('/mocks/feeds.json').then(function(feeds) {
      var feedList = new Backbone.Collection(extended);
      var feedListView = new FeedListView(feedList);
      $('.container').append(feedListView.render());
  });
});

  那样自然是能够干活的,可是此地发送乞求的url而不是最后的,当集成的时候大家又必要纠正为实在的url。多少个简约的做法是应用Sinatra来做壹回url的改变:

get '/api/feeds' do
  content_type 'application/json'
  File.open('mocks/feeds.json').read
end

  这样,当我们和事实上的劳务扩充集成时,只须求连接到异平常衣裳务器就足以了。

  注意,我们前天的主导是mocks/feeds.json以此文件。那么些文件今后的剧中人物正是三个协议,起码对于前带来讲是这么的。紧接着,大家的应用必要渲染加星的意义,那就要求其它二个公约:寻觅脚下顾客加星过的具备规行矩步,因而大家参加了二个新的公约:

[
    {
        "id": 3,
        "url": "http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/",
        "title": "使用underscore.js构建前端应用",
        "publicDate": "2015年1月20日"
    }
]

  然后在sinatra中步向多个新的投射:

get '/api/fav-feeds/:id' do
  content_type 'application/json'
  File.open('mocks/fav-feeds.json').read
end

  通过那多少个央浼,大家会收获七个列表,然后遵照那四个列表的插花来绘制出全体的星号的事态(有的是空心,有的是实心卡塔尔国:

$.when(feeds, favorite).then(function(feeds, favorite) {
    var ids = _.pluck(favorite[0], 'id');
    var extended = _.map(feeds[0], function(feed) {
        return _.extend(feed, {status: _.includes(ids, feed.id)});
    });
    var feedList = new Backbone.Collection(extended);
    var feedListView = new FeedListView(feedList);
    $('.container').append(feedListView.render());
});

  剩下的一个问题是当点击红心时,大家要求发要求给后端,然后更新红心的气象:

toggleFavorite: function(event) {
    event.preventDefault();
    var that = this;
    $.post('/api/feeds/' this.model.get('id')).done(function(){
        var status = that.model.get('status');
        that.model.set('status', !status);
    });
}

  这里又多出来叁个倡议,可是使用Sinatra我们还是得以非常轻便的辅助它:

post '/api/feeds/:id' do
end

  能够见见,在并未有后端的意况下,我们全体都开展顺利 — 后端以致还不曾开头做,只怕正在由二个进程比我们慢的集体在开发,但是不留意,他们不会潜移暗化大家的。

  不仅仅如此,当大家写完前端的代码之后,能够做二个End2End的测验。由于使用了mock数据,免去了数据库和网络的耗时,这些End2End的测量检验会运作的那一个快,何况它的确起到了端到端的效用。那些测量试验在终极的并轨时,还足以用来当UI测验来运营。所谓一举多得。

#encoding: utf-8
require 'spec_helper'
describe 'Feeds List Page' do
  let(:list_page) {FeedListPage.new}
  before do
      list_page.load
  end
  it 'user can see a banner and some feeds' do
      expect(list_page).to have_banner
      expect(list_page).to have_feeds
  end
  it 'user can see 3 feeds in the list' do
      expect(list_page.all_feeds).to have_feed_items count: 3
  end
  it 'feed has some detail information' do
      first = list_page.all_feeds.feed_items.first
      expect(first.title).to eql("Python中的 list comprehension 以及 generator")
  end
end

图片 12

  关于什么编写那样的测量检验,可以参照他事他说加以考察以前写的这篇小说。

代码

左右端的代码小编都放到了Gitbub上,感兴趣的能够clone下来自行钻研:

  1. bookmarks-frontend
  2. bookmarks-server

    1 赞 5 收藏 2 评论

图片 13

  后端开采

  作者在这里个示例中,后端选用了spring-boot用作示范,你应有可以相当轻松将看似的笔触应用到Ruby也许别的语言上。

  首先是需要的输入,FeedsController会肩负分析倡议路线,查数据库,最终回到JSON格式的多少。

@Controller
@RequestMapping("/api")
public class FeedsController {
    @Autowired
    private FeedsService feedsService;
    @Autowired
    private UserService userService;
    public void setFeedsService(FeedsService feedsService) {
        this.feedsService = feedsService;
    }
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    @RequestMapping(value="/feeds", method = RequestMethod.GET)
    @ResponseBody
    public Iterable<Feed> allFeeds() {
        return feedsService.allFeeds();
    }
    @RequestMapping(value="/fav-feeds/{userId}", method = RequestMethod.GET)
    @ResponseBody
    public Iterable<Feed> favFeeds(@PathVariable("userId") Long userId) {
        return userService.favoriteFeeds(userId);
    }
}

  具体查询的细节大家就不做研商了,感兴趣的能够在篇章结尾处找到代码库的链接。那么有了那一个Controller之后,大家怎么测验它吧?可能说,怎么样让公约变得实际可用呢?

sprint-test提供了特别美妙的DSL来编排测量检验,大家仅必要或多或少代码就足以将协议用起来,并实际的监督接口的改变:

private MockMvc mockMvc;
private FeedsService feedsService;
private UserService userService;
@Before
public void setup() {
    feedsService = mock(FeedsService.class);
    userService = mock(UserService.class);
    FeedsController feedsController = new FeedsController();
    feedsController.setFeedsService(feedsService);
    feedsController.setUserService(userService);
    mockMvc = standaloneSetup(feedsController).build();
}

  建构了mockmvc之后,大家就足以编写Controller的单元测量检验了:

@Test
public void shouldResponseWithAllFeeds() throws Exception {
    when(feedsService.allFeeds()).thenReturn(Arrays.asList(prepareFeeds()));
    mockMvc.perform(get("/api/feeds"))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json;charset=UTF-8"))
            .andExpect(jsonPath("$", hasSize(3)))
            .andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
}

  当发送GET请求到/api/feeds上今后,大家期望再次回到状态是200,然后内容是application/json。然后大家预料重临的结果是三个尺寸为3的数组,然后数组中的第二个成分的publishDate字段不为空。

  注意此处的prepareFeeds主意,事实上它会去加载mocks/feeds.json文本 — 也正是后边多少个用来测量检验的mock文件:

private Feed[] prepareFeeds() throws IOException {
    URL resource = getClass().getResource("/mocks/feeds.json");
    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(resource, Feed[].class);
}

  那样,当后端改革Feed概念(增加/删除/改善字段卡塔尔,可能涂改了mock数据等,都会产生测验失利;而前者改正mock之后,也会导致测量检验败北— 不要惧怕战败 — 那样的战败会推进贰回磋商,并驱动出最后的service的左券。

  对应的,测试/api/fav-feeds/{userId}的办法周边:

@Test
public void shouldResponseWithUsersFavoriteFeeds() throws Exception {
    when(userService.favoriteFeeds(any(Long.class)))
        .thenReturn(Arrays.asList(prepareFavoriteFeeds()));
    mockMvc.perform(get("/api/fav-feeds/1"))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json;charset=UTF-8"))
            .andExpect(jsonPath("$", hasSize(1)))
            .andExpect(jsonPath("$[0].title", is("使用underscore.js构建前端应用")))
            .andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
}

  总结

  前后端分离是大器晚成件轻便的事体,何况集体恐怕在长期能够看看多数益处,可是借使不认真管理集成的主题材料,分离反而恐怕会拉动越来越长的融会时间。通过面向合同的方法来公司各自的测量试验,能够推动多数的裨益:更便捷的End2End测验,更平整的集成,更安全的辞别开拓等等。

  代码

  前后端的代码小编都放到了Gitbub上,感兴趣的能够clone下来自行钻研:

  1. bookmarks-frontend
  2. bookmarks-server

 

 

本文由星彩网app下载发布于前端技术,转载请注明出处:轻量级的接口配置建模框架,前后端分离了

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。