>

前后端分离了

- 编辑:至尊游戏网站 -

前后端分离了

左右端抽离了,然后呢?

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

初藳出处: 邱俊涛的博客(@正面与反面反长)   

 

前言

前后端分离已然是产业界所共鸣的后生可畏种开垦/部署形式了。所谓的内外端抽离,并不是观念行个中的按机关分割,意气风发部分人纯做前端(HTML/CSS/JavaScript/Flex),另生龙活虎有些人纯做后端,因为这种办法是不工作的:举个例子相当多团队采取了后端的模版手艺(JSP, Free马克尔, ERB等等),前端的支付和调解须要叁个后台Web容器的支撑,进而不可能成功真正的分离(更不用提在布署的时候,由于动态内容和静态内容混在一块,当设计动态静态分流的时候,管理起来拾叁分辛苦)。关于前后端支付的另贰个商酌能够参照这里。

尽管通过API来解耦前端和后端开垦进度,前后端通过RESTFul的接口来通讯,前端的静态内容和后端的动态总括分别支付,分别布置,集成照例是叁个绕不开的标题— 前端/后端的施用都得以单独的运转,不过集成起来却不办事。我们供给开支一大波的生命力来调整,直到上线前照旧未有人有信念有所的接口都以专业的。

 前言

少数背景

一个头名的Web应用的布局看起来是那般的:

图片 1

上下端都各自有友好的开销流程,创设筑工程具,测验群集等等。前后端仅仅经过接口来编制程序,那一个接口大概是JSON格式的RESTFul的接口,也也许是XML的,入眼是后台只担负数据的提供和计量,而浑然不管理表现。而前者则承当得到数量,协会数据并显现的办事。那样结构清晰,关怀点抽离,前后端会变得相对独立并松耦合。

上述的场景依然相比不错,大家实际上在其实条件中会有极度复杂的景色,例如异构的网络,异构的操作系统等等:

图片 2

在实际上的场景中,后端恐怕还有或然会更目不暇接,比如用C语言做多少搜集,然后通过Java整合到二个数据堆栈,然后该数据旅馆又有一层Web Service,最终若干个如此的Web Service又被一个Ruby的聚合Service整合在协同重回给前端。在此么三个烦琐的种类中,后台放肆端点的破产都或许阻塞前端的付出流程,由此我们会动用mock的艺术来消除那些标题:

图片 3

以此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上校这几个字段精确的表现了(当然还设置了字体,字号,颜色等等)。但是后台生成该左券的劳务并从未感知到那风度翩翩调换,当运维生成左券部分测试(后台)时,测量检验会失败了 — 因为它并不曾变动这么些字段。于是后端程序员就找前端来合计,领会专门的学问逻辑之后,他会改善代码,并保管测量试验通过。那样,当集成的时候,就不会冒出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容器的支撑,进而不能够完成真正的分手(更毫不提在安顿的时候,由于动态内容和静态内容混在生龙活虎道,当设计动态静态分流的时候,管理起来十三分艰巨)。关于前后端支付的另贰个谈谈能够参照这里。

一个例子

咱们以这些利用为示范,来验证什么在前后端分离之后,保证代码的成色,并减弱集成的成本。那几个利用场景很简短:全数人都得以见见三个条文列表,每种登录客商都足以筛选本人喜欢的条文,并为之加星。加星之后的规规矩矩会保留到客商本人的村办基本中。客户分界面看起来是如此的:

图片 4

然而为了静心在大家的基本上,作者去掉了比方登录,个人基本之类的页面,假如你是三个已报到顾客,然后大家来探视怎么样编写测验。

  即便通过API来解耦前端和后端开辟进度,前后端通过RESTFul的接口来通讯,前端的静态内容和后端的动态总结分别支付,分别安顿,集成仍是二个绕不开的主题材料— 前端/后端的接收都能够独自的运维,可是集成起来却不坐班。我们须要开销大批量的生命力来调整,直到上线前依然未有人有信念有所的接口都以专业的。

前端开荒

听别人说日常的做法,前后端抽离之后,我们相当轻易mock一些多少来本身测量试验:

XHTML

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

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": "二〇一四年三月二十四日" } ]

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

图片 5

有关什么编写那样的测量试验,能够参照他事他说加以考查早先写的那篇小说。

  一点背景

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

图片 6

  前后端都分别有友好的支付流程,创设筑工程具,测验会集等等。前后端仅仅通过接口来编制程序,那么些接口恐怕是JSON格式的RESTFul的接口,也只怕是XML的,注重是后台只担当数据的提供和计量,而浑然不处理表现。而前面一个则担当获得数量,组织数量并突显的工作。那样结构清晰,关怀点分离,前后端会变得相对独立并松耦合。

  上述的光景照旧比较完美,大家其实在实质上条件中会有特别复杂的风貌,比方异构的互联网,异构的操作系统等等:

图片 7

  在事实上的光景中,后端大概还恐怕会更复杂,举个例子用C语言做多少采摘,然后经过Java整合到四个数据商旅,然后该数据宾馆又有意气风发层Web Service,最终若干个如此的Web Service又被四个Ruby的聚合Service整合在同步回去给前端。在此么三个犬牙相制的体系中,后台任性端点的诉讼失败都可能过不去前端的支付流程,因而大家会使用mock的点子来化解这些标题:

图片 8

  这个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上校以此字段精确的表现了(当然还安装了字体,字号,颜色等等)。不过后台湾学子成该公约的劳动并不曾感知到那风流罗曼蒂克转移,当运营生成公约部分测量检验(后台)时,测量检验会失败了 — 因为它并未调换这些字段。于是后端技术员就找前端来商讨,掌握事情逻辑之后,他会修正代码,并确定保障测量试验通过。那样,当集成的时候,就不会产出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())));
}

  贰个例证

  大家以这几个利用为示范,来注解什么在内外端剥离之后,保证代码的质量,并裁减集成的财力。这些利用场景很简短:全部人都足以见见多个规规矩矩列表,各个登录客户都能够筛选本人喜欢的规规矩矩,并为之加星。加星之后的不成方圆会保留到顾客自身的个人中心中。客商界面看起来是如此的:

图片 9

  可是为了潜心在大家的为主上,小编去掉了诸如登录,个人宗旨之类的页面,假令你是三个已登陆客户,然后大家来探视怎么样编写测验。

总结

内外端分离是生机勃勃件轻巧的专业,何况集体大概在长期能够见到数不完益处,不过要是不认真管理集成的难点,分离反而只怕会推动越来越长的购并时间。通过面向合同的办法来公司各自的测试,能够推动超级多的实惠:更火速的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

图片 10

  关于怎样编写那样的测验,能够参见早先写的那篇小说。

代码

前后端的代码作者都放到了Gitbub上,感兴趣的可以clone下来自行钻研:

  1. bookmarks-frontend
  2. bookmarks-server

    1 赞 5 收藏 2 评论

图片 11

  后端开荒

  小编在这里个示例中,后端选取了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

 

 

本文由软件综合发布,转载请注明来源:前后端分离了