日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
熱點(diǎn)推薦:前后端分離了,然后呢?

前言

江津網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站開發(fā)等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營維護(hù)。創(chuàng)新互聯(lián)于2013年創(chuàng)立到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。

前后端分離已經(jīng)是業(yè)界所共識(shí)的一種開發(fā)/部署模式了。所謂的前后端分離,并不是傳統(tǒng)行業(yè)中的按部門劃分,一部分人純做前端(HTML/CSS /JavaScript/Flex),另一部分人純做后端,因?yàn)檫@種方式是不工作的:比如很多團(tuán)隊(duì)采取了后端的模板技術(shù)(JSP, FreeMarker, ERB等等),前端的開發(fā)和調(diào)試需要一個(gè)后臺(tái)Web容器的支持,從而無法做到真正的分離(更不用提在部署的時(shí)候,由于動(dòng)態(tài)內(nèi)容和靜態(tài)內(nèi)容混在一起,當(dāng)設(shè)計(jì) 動(dòng)態(tài)靜態(tài)分流的時(shí)候,處理起來非常麻煩)。關(guān)于前后端開發(fā)的另一個(gè)討論可以參考這里。

即使通過API來解耦前端和后端開發(fā)過程,前后端通過RESTFul的接口來通信,前端的靜態(tài)內(nèi)容和后端的動(dòng)態(tài)計(jì)算分別開發(fā),分別部署,集成仍然 是一個(gè)繞不開的問題 — 前端/后端的應(yīng)用都可以獨(dú)立的運(yùn)行,但是集成起來卻不工作。我們需要花費(fèi)大量的精力來調(diào)試,直到上線前仍然沒有人有信心所有的接口都是工作的。

一點(diǎn)背景

一個(gè)典型的Web應(yīng)用的布局看起來是這樣的:

前后端都各自有自己的開發(fā)流程,構(gòu)建工具,測試集合等等。前后端僅僅通過接口來編程,這個(gè)接口可能是JSON格式的RESTFul的接口,也可能 是XML的,重點(diǎn)是后臺(tái)只負(fù)責(zé)數(shù)據(jù)的提供和計(jì)算,而完全不處理展現(xiàn)。而前端則負(fù)責(zé)拿到數(shù)據(jù),組織數(shù)據(jù)并展現(xiàn)的工作。這樣結(jié)構(gòu)清晰,關(guān)注點(diǎn)分離,前后端會(huì)變 得相對獨(dú)立并松耦合。

上述的場景還是比較理想,我們事實(shí)上在實(shí)際環(huán)境中會(huì)有非常復(fù)雜的場景,比如異構(gòu)的網(wǎng)絡(luò),異構(gòu)的操作系統(tǒng)等等:

在實(shí)際的場景中,后端可能還會(huì)更復(fù)雜,比如用C語言做數(shù)據(jù)采集,然后通過Java整合到一個(gè)數(shù)據(jù)倉庫,然后該數(shù)據(jù)倉庫又有一層Web Service,***若干個(gè)這樣的Web Service又被一個(gè)Ruby的聚合Service整合在一起返回給前端。在這樣一個(gè)復(fù)雜的系統(tǒng)中,后臺(tái)任意端點(diǎn)的失敗都可能阻塞前端的開發(fā)流程,因此 我們會(huì)采用mock的方式來解決這個(gè)問題:

這個(gè)mock服務(wù)器可以啟動(dòng)一個(gè)簡單的HTTP服務(wù)器,然后將一些靜態(tài)的內(nèi)容serve出來,以供前端代碼使用。這樣的好處很多:

1.前后端開發(fā)相對獨(dú)立

2.后端的進(jìn)度不會(huì)影響前端開發(fā)

3.啟動(dòng)速度更快

4.前后端都可以使用自己熟悉的技術(shù)棧(讓前端的學(xué)maven,讓后端的用gulp都會(huì)很不順手)

但是當(dāng)集成依然是一個(gè)令人頭疼的難題。我們往往在集成的時(shí)候才發(fā)現(xiàn),本來協(xié)商的數(shù)據(jù)結(jié)構(gòu)變了:deliveryAddress字段本來是一個(gè)字符 串,現(xiàn)在變成數(shù)組了(業(yè)務(wù)發(fā)生了變更,系統(tǒng)現(xiàn)在可以支持多個(gè)快遞地址);price字段變成字符串,協(xié)商的時(shí)候是number;用戶郵箱地址多了一個(gè)層級(jí) 等等。這些變動(dòng)在所難免,而且時(shí)有發(fā)生,這會(huì)花費(fèi)大量的調(diào)試時(shí)間和集成時(shí)間,更別提修改之后的回歸測試了。

所以僅僅使用一個(gè)靜態(tài)服務(wù)器,然后提供mock數(shù)據(jù)是遠(yuǎn)遠(yuǎn)不夠的。我們需要的mock應(yīng)該還能做到:

1.前端依賴指定格式的mock數(shù)據(jù)來進(jìn)行UI開發(fā)

2.前端的開發(fā)和測試都基于這些mock數(shù)據(jù)

3.后端產(chǎn)生指定格式的mock數(shù)據(jù)

4.后端需要測試來確保生成的mock數(shù)據(jù)正是前端需要的

簡而言之,我們需要商定一些契約,并將這些契約作為可以被測試的中間格式。然后前后端都需要有測試來使用這些契約。一旦契約發(fā)生變化,則另一方的測試會(huì)失敗,這樣就會(huì)驅(qū)動(dòng)雙方協(xié)商,并降低集成時(shí)的浪費(fèi)。

一個(gè)實(shí)際的場景是:前端發(fā)現(xiàn)已有的某個(gè)契約中,缺少了一個(gè)address的字段,于是就在契約中添加了該字段。然后在UI上將這個(gè)字段正確的展現(xiàn) 了(當(dāng)然還設(shè)置了字體,字號(hào),顏色等等)。但是后臺(tái)生成該契約的服務(wù)并沒有感知到這一變化,當(dāng)運(yùn)行生成契約部分測試(后臺(tái))時(shí),測試會(huì)失敗了 — 因?yàn)樗]有生成這個(gè)字段。于是后端工程師就找前端來商量,了解業(yè)務(wù)邏輯之后,他會(huì)修改代碼,并保證測試通過。這樣,當(dāng)集成的時(shí)候,就不會(huì)出現(xiàn)UI上少了 一個(gè)字段,但是誰也不知道是前端問題,后端問題,還是數(shù)據(jù)庫問題等。

而且實(shí)際的項(xiàng)目中,往往都是多個(gè)頁面,多個(gè)API,多個(gè)版本,多個(gè)團(tuán)隊(duì)同時(shí)進(jìn)行開發(fā),這樣的契約會(huì)降低非常多的調(diào)試時(shí)間,使得集成相對平滑。

在實(shí)踐中,契約可以定義為一個(gè)JSON文件,或者一個(gè)XML的payload。只需要保證前后端共享同一個(gè)契約集合來做測試,那么集成工作就會(huì)從 中受益。一個(gè)最簡單的形式是:提供一些靜態(tài)的mock文件,而前端所有發(fā)往后臺(tái)的請求都被某種機(jī)制攔截,并轉(zhuǎn)換成對該靜態(tài)資源的請求。

1.moco,基于Java

2.wiremock,基于Java

3.sinatra,基于Ruby

看到sinatra被列在這里,可能熟悉Ruby的人會(huì)反對:它可是一個(gè)后端全功能的的程序庫啊。之所以列它在這里,是因?yàn)閟inatra提供了 一套簡潔優(yōu)美的DSL,這個(gè)DSL非常契合Web語言,我找不到更漂亮的方式來使得這個(gè)mock server更加易讀,所以就采用了它。

#p#

一個(gè)例子

我們以這個(gè)應(yīng)用為示例,來說明如何在前后端分離之后,保證代碼的質(zhì)量,并降低集成的成本。這個(gè)應(yīng)用場景很簡單:所有人都可以看到一個(gè)條目列表,每個(gè)登陸用戶都可以選擇自己喜歡的條目,并為之加星。加星之后的條目會(huì)保存到用戶自己的個(gè)人中心中。用戶界面看起來是這樣的:

不過為了專注在我們的中心上,我去掉了諸如登陸,個(gè)人中心之類的頁面,假設(shè)你是一個(gè)已登錄用戶,然后我們來看看如何編寫測試。

前端開發(fā)

根據(jù)通常的做法,前后端分離之后,我們很容易mock一些數(shù)據(jù)來自己測試:

Js代碼

       
      
    1. [
    2.     {
    3.         "id": 1,
    4.         "url": "http://abruzzi.github.com/2015/03/list-comprehension-in-python/",
    5.         "title": "Python中的 list comprehension 以及 generator",
    6.         "publicDate": "2015年3月20日"
    7.     },
    8.     {
    9.         "id": 2,
    10.         "url": "http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/",
    11.         "title": "使用inotify/fswatch構(gòu)建自動(dòng)監(jiān)控腳本",
    12.         "publicDate": "2015年2月1日"
    13.     },
    14.     {
    15.         "id": 3,
    16.         "url": "http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/",
    17.         "title": "使用underscore.js構(gòu)建前端應(yīng)用",
    18.         "publicDate": "2015年1月20日"
    19.     }
    20. ]

然后,一個(gè)可能的方式是通過請求這個(gè)json來測試前臺(tái):

Js代碼

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

這樣當(dāng)然是可以工作的,但是這里發(fā)送請求的url并不是最終的,當(dāng)集成的時(shí)候我們又需要修改為真實(shí)的url。一個(gè)簡單的做法是使用Sinatra來做一次url的轉(zhuǎn)換:

Js代碼

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

這樣,當(dāng)我們和實(shí)際的服務(wù)進(jìn)行集成時(shí),只需要連接到那個(gè)服務(wù)器就可以了。

注意,我們現(xiàn)在的核心是mocks/feeds.json這個(gè)文件。這個(gè)文件現(xiàn)在的角色就是一個(gè)契約,至少對于前端來說是這樣的。緊接著,我們的應(yīng)用需要渲染加星的功能,這就需要另外一個(gè)契約:找出當(dāng)前用戶加星過的所有條目,因此我們加入了一個(gè)新的契約:

Js代碼

       
      
    1. [
    2.     {
    3.         "id": 3,
    4.         "url": "http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/",
    5.         "title": "使用underscore.js構(gòu)建前端應(yīng)用",
    6.         "publicDate": "2015年1月20日"
    7.     }
    8. ]

然后在sinatra中加入一個(gè)新的映射:

Js代碼

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

通過這兩個(gè)請求,我們會(huì)得到兩個(gè)列表,然后根據(jù)這兩個(gè)列表的交集來繪制出所有的星號(hào)的狀態(tài)(有的是空心,有的是實(shí)心):

Js代碼

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

剩下的一個(gè)問題是當(dāng)點(diǎn)擊紅心時(shí),我們需要發(fā)請求給后端,然后更新紅心的狀態(tài):

Js代碼

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

這里又多出來一個(gè)請求,不過使用Sinatra我們還是可以很容易的支持它:

Js代碼

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

可以看到,在沒有后端的情況下,我們一切都進(jìn)展順利 — 后端甚至還沒有開始做,或者正在由一個(gè)進(jìn)度比我們慢的團(tuán)隊(duì)在開發(fā),不過無所謂,他們不會(huì)影響我們的。

不僅如此,當(dāng)我們寫完前端的代碼之后,可以做一個(gè)End2End的測試。由于使用了mock數(shù)據(jù),免去了數(shù)據(jù)庫和網(wǎng)絡(luò)的耗時(shí),這個(gè)End2End的測試會(huì)運(yùn)行的非常快,并且它確實(shí)起到了端到端的作用。這些測試在***的集成時(shí),還可以用來當(dāng)UI測試來運(yùn)行。所謂一舉多得。

Js代碼

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

關(guān)于如何編寫這樣的測試,可以參考之前寫的這篇文章。

#p#

后端開發(fā)

我在這個(gè)示例中,后端采用了spring-boot作為示例,你應(yīng)該可以很容易將類似的思路應(yīng)用到Ruby或者其他語言上。

首先是請求的入口,F(xiàn)eedsController會(huì)負(fù)責(zé)解析請求路徑,查數(shù)據(jù)庫,***返回JSON格式的數(shù)據(jù)。

Js代碼

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

具體查詢的細(xì)節(jié)我們就不做討論了,感興趣的可以在文章結(jié)尾處找到代碼庫的鏈接。那么有了這個(gè)Controller之后,我們?nèi)绾螠y試它呢?或者說,如何讓契約變得實(shí)際可用呢?

sprint-test提供了非常優(yōu)美的DSL來編寫測試,我們僅需要一點(diǎn)代碼就可以將契約用起來,并實(shí)際的監(jiān)督接口的修改:

Js代碼

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

建立了mockmvc之后,我們就可以編寫Controller的單元測試了:

Js代碼

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

當(dāng)發(fā)送GET請求到/api/feeds上之后,我們期望返回狀態(tài)是200,然后內(nèi)容是application/json。然后我們預(yù)期返回的結(jié)果是一個(gè)長度為3的數(shù)組,然后數(shù)組中的***個(gè)元素的publishDate字段不為空。

注意此處的prepareFeeds方法,事實(shí)上它會(huì)去加載mocks/feeds.json文件 — 也就是前端用來測試的mock文件:

Js代碼

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

這樣,當(dāng)后端修改Feed定義(添加/刪除/修改字段),或者修改了mock數(shù)據(jù)等,都會(huì)導(dǎo)致測試失敗;而前端修改mock之后,也會(huì)導(dǎo)致測試失敗 — 不要懼怕失敗 — 這樣的失敗會(huì)促進(jìn)一次協(xié)商,并驅(qū)動(dòng)出最終的service的契約。

對應(yīng)的,測試/api/fav-feeds/{userId}的方式類似:

Js代碼

       
      
    1. @Test
    2. public void shouldResponseWithUsersFavoriteFeeds() throws Exception {
    3.     when(userService.favoriteFeeds(any(Long.class)))
    4.         .thenReturn(Arrays.asList(prepareFavoriteFeeds()));
    5.     mockMvc.perform(get("/api/fav-feeds/1"))
    6.             .andExpect(status().isOk())
    7.             .andExpect(content().contentType("application/json;charset=UTF-8"))
    8.             .andExpect(jsonPath("$", hasSize(1)))
    9.             .andExpect(jsonPath("$[0].title", is("使用underscore.js構(gòu)建前端應(yīng)用")))
    10.             .andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
    11. }

總結(jié)

前后端分離是一件容易的事情,而且團(tuán)隊(duì)可能在短期可以看到很多好處,但是如果不認(rèn)真處理集成的問題,分離反而可能會(huì)帶來更長的集成時(shí)間。通過面向契約的方式來組織各自的測試,可以帶來很多的好處:更快速的End2End測試,更平滑的集成,更安全的分離開發(fā)等等。

代碼

前后端的代碼我都放到了Gitbub上,感興趣的可以clone下來自行研究:

1.bookmarks-frontend

2.bookmarks-server


分享題目:熱點(diǎn)推薦:前后端分離了,然后呢?
文章來源:http://www.dlmjj.cn/article/cdeohpi.html