Poplar: 從React Native到微服務(wù)钞护,落地一個(gè)全棧解決方案

poplar.png

Poplar是一個(gè)社交主題的內(nèi)容社區(qū),但自身并不做社區(qū)爆办,旨在提供可快速二次開(kāi)發(fā)的開(kāi)源基礎(chǔ)套件难咕。前端基于React Native與Redux構(gòu)建,后端由Spring Boot、Dubbo余佃、Zookeeper組成微服務(wù)對(duì)外提供一致的API訪問(wèn)暮刃。

https://github.com/lvwangbeta/Poplar

前端React Native & Redux

show.png

React Native雖然提供跨平臺(tái)解決方案,但并未在性能與開(kāi)發(fā)效率上做出過(guò)度妥協(xié)爆土,尤其是對(duì)于有JS與CSS基礎(chǔ)的開(kāi)發(fā)人員入手不會(huì)很難椭懊,不過(guò)JSX語(yǔ)法糖需要一定的適應(yīng)時(shí)間,至于DOM結(jié)構(gòu)與樣式和JS處理寫(xiě)在一起是否喜歡就見(jiàn)仁見(jiàn)智了雾消,可這也是一個(gè)強(qiáng)迫你去模塊化解耦的比較好的方式灾搏。由于React組件的數(shù)據(jù)流是單向的,因此會(huì)引入一個(gè)很麻煩的問(wèn)題立润,組件之間很難高效通信狂窑,尤其是兩個(gè)層級(jí)很深的兄弟節(jié)點(diǎn)之間通信變得異常復(fù)雜,對(duì)上游所有父節(jié)點(diǎn)造成傳遞污染桑腮,維護(hù)成本極高泉哈。為此Poplar引入了Redux架構(gòu),統(tǒng)一管理應(yīng)用狀態(tài)破讨。

模塊化

front-framework.png

APP由5個(gè)基礎(chǔ)頁(yè)面構(gòu)成丛晦,分別是Feed信息流主頁(yè)(MainPage)、探索發(fā)現(xiàn)頁(yè)面(ExplorePage)提陶、我的賬戶詳情頁(yè)(MinePage)烫沙、狀態(tài)創(chuàng)建于發(fā)送頁(yè)(NewFeed)、登錄注冊(cè)頁(yè)面(LoginRegPage)等隙笆。頁(yè)面又由基礎(chǔ)組件組成锌蓄,如Feed列表、Feed詳情撑柔、評(píng)論瘸爽、標(biāo)簽、相冊(cè)等等铅忿。如果與服務(wù)器交互剪决,則統(tǒng)一交由API層處理。

pages.png

頁(yè)面底部由TabNavigator包含5個(gè)TabNavigator.Item構(gòu)成檀训,分別對(duì)應(yīng)基礎(chǔ)頁(yè)面柑潦,如果用戶未登錄,則在點(diǎn)擊主頁(yè)或新增Tab時(shí)呼出登錄注冊(cè)頁(yè)面肢扯。

Redux

引入Redux并不是趕潮流妒茬,而且早在2014年就已經(jīng)提出了Flux的概念。使用Redux主要是不得不用了蔚晨,Poplar組件結(jié)構(gòu)并非特別復(fù)雜,但嵌套關(guān)系較多,而且需要同時(shí)支持登錄與非登錄情況的信息流訪問(wèn)铭腕,這就需要一個(gè)統(tǒng)一的狀態(tài)管理器來(lái)協(xié)調(diào)組件之間的通信和狀態(tài)更新银择,而Redux很好的解決了這個(gè)問(wèn)題。

這里不枯燥的講解Redux的架構(gòu)模型了累舷,而是以Poplar中的登錄狀態(tài)為例來(lái)簡(jiǎn)單說(shuō)下Redux在Poplar項(xiàng)目中是如何使用的浩考。

Poplar使用React-Redux庫(kù),一個(gè)將Redux架構(gòu)在React的實(shí)現(xiàn)被盈。

1. 場(chǎng)景描述

在未登錄情況下析孽,如果用戶點(diǎn)擊Feed流頁(yè)面會(huì)彈出登錄/注冊(cè)頁(yè)面,登錄或注冊(cè)成功之后頁(yè)面收回只怎,同時(shí)刷新出信息流內(nèi)容袜瞬。下圖中的App組件是登錄頁(yè)面和信息流主頁(yè)兄弟節(jié)點(diǎn)的共同父組件。

without_redux.png

這個(gè)需求看似簡(jiǎn)單身堡,但如果沒(méi)有Redux邓尤,在React中實(shí)現(xiàn)起來(lái)會(huì)很蹩腳而且會(huì)冗余很多無(wú)用代碼調(diào)用。

首先我們看下在沒(méi)有Redux的情況下是如何實(shí)現(xiàn)這一業(yè)務(wù)流程的贴谎?

在點(diǎn)擊Tabbar的第一個(gè)Item也就是信息流頁(yè)簽時(shí)汞扎,要做用戶是否登錄檢查,這個(gè)檢查可以通過(guò)查看應(yīng)用是否本地化存儲(chǔ)了token或其他驗(yàn)簽方式驗(yàn)證擅这,如果未登錄澈魄,需要主動(dòng)更新App組件的state狀態(tài),同時(shí)將這個(gè)狀態(tài)修改通過(guò)props的方式傳遞給LoginPage仲翎,LoginPage得知有新的props傳入后更新自己的state:{visible:true}來(lái)呼出自己痹扇,如果客戶輸入登錄信息并且登錄成功,則需要將LoginPage的state設(shè)置為{visible:false}來(lái)隱藏自己谭确,同時(shí)回調(diào)App傳給它的回調(diào)函數(shù)來(lái)告訴父附件用戶已經(jīng)登錄成功帘营,我們算一下這僅僅是兩個(gè)組件之間的通信就要消耗1個(gè)props變量1個(gè)props回調(diào)函數(shù)和2個(gè)state更新,到這里只是完成了LoginPage通知App組件目前應(yīng)用應(yīng)該處于已登錄狀態(tài)逐哈,但是還沒(méi)有刷新出用戶的Feed流芬迄,因?yàn)榇藭r(shí)MainPage還不知道用戶已登錄,需要App父組件來(lái)告知它已登錄請(qǐng)刷新昂秃,可怎樣通知呢禀梳?React是數(shù)據(jù)流單向的,要想讓下層組件更新只能傳遞變化的props屬性肠骆,這樣就又多了一個(gè)props屬性的開(kāi)銷算途,MainPage更新關(guān)聯(lián)的state同時(shí)刷新自己獲取Feed流,這才最終完成了一次登錄后的MainPage信息展示蚀腿。通過(guò)上面的分析可以看出Poplar在由未登錄到登錄的狀態(tài)轉(zhuǎn)變時(shí)冗余了很多但是又沒(méi)法避免的參數(shù)傳遞嘴瓤,因?yàn)樾值芄?jié)點(diǎn)LoginPage與MainPage之間無(wú)法簡(jiǎn)單的完成通信告知彼此的狀態(tài)扫外,就需要App父組件這個(gè)橋梁來(lái)先向上再向下的傳遞消息。

再來(lái)看下引入Redux之后是如何完成這一同樣的過(guò)程的:

還是在未登錄情況下點(diǎn)擊主頁(yè)廓脆,此時(shí)Poplar由于Redux的引入已經(jīng)為應(yīng)用初始了全局登錄狀態(tài){status: 'NOT_LOGGED_IN'}筛谚,當(dāng)用戶登錄成功之后會(huì)將該狀態(tài)更新為{status: 'LOGGED_IN'},同時(shí)LoginPage與此狀態(tài)進(jìn)行了綁定停忿,Redux會(huì)第一時(shí)間通知其更新組件自己的狀態(tài)為{visible:false}驾讲。與此同時(shí)App也綁定了這個(gè)由Redux管理的全局狀態(tài),因此也同樣可以獲得{status: 'LOGGED_IN'}的通知席赂,這樣就可以很簡(jiǎn)單的在客戶登錄之后隱藏LoginPage顯示MainPage吮铭,是不是很簡(jiǎn)單也很神奇,完全不用依賴參數(shù)的層層傳遞颅停,組件想要獲得哪個(gè)全局狀態(tài)就與其關(guān)聯(lián)就好谓晌,Redux會(huì)第一時(shí)間通知你。

2. 實(shí)現(xiàn)

以實(shí)際的代碼為例來(lái)講解下次場(chǎng)景的React-Redux實(shí)現(xiàn):

connect

在App組件中便监,通過(guò)connect方法將UI組件生成Redux容器組件扎谎,可以理解為架起了UI組件與Redux溝通的橋梁,將store于組件關(guān)聯(lián)在一起烧董。

import {showLoginPage, isLogin} from  './actions/loginAction';
import {showNewFeedPage} from './actions/NewFeedAction';

export default connect((state) => ({
  status: state.isLogin.status, //登錄狀態(tài)
  loginPageVisible: state.showLoginPage.loginPageVisible
}), (dispatch) => ({
  isLogin: () => dispatch(isLogin()),
  showLoginPage: () => dispatch(showLoginPage()),
  showNewFeedPage: () => dispatch(showNewFeedPage()),
}))(App)

connect方法的第一個(gè)參數(shù)是mapStateToProps函數(shù)毁靶,建立一個(gè)store中的數(shù)據(jù)到UI組件props對(duì)象的映射關(guān)系,只要store更新了就會(huì)調(diào)用mapStateToProps方法逊移,mapStateToProps返回一個(gè)對(duì)象预吆,是一個(gè)UI組件props與store數(shù)據(jù)的映射。上面代碼中胳泉,mapStateToProps接收state作為參數(shù)拐叉,返回一個(gè)UI組件登陸狀態(tài)與store中state的登陸狀態(tài)的映射關(guān)系以及一個(gè)登陸頁(yè)面是否顯示的映射關(guān)系。這樣App組件狀態(tài)就與Redux的store關(guān)聯(lián)上了扇商。

第二個(gè)參數(shù)mapDispatchToProps函數(shù)允許將action作為props綁定到組件上凤瘦,返回一個(gè)UI組件props與Redux action的映射關(guān)系,上面代碼中App組件的isLogin showLoginPage showNewFeedPageprops與Redux的action建立了映射關(guān)系案铺。調(diào)用isLogin實(shí)際調(diào)用的是Redux中的store.dispatch(isLogin) action蔬芥,dispatch完成對(duì)action到reducer的分發(fā)。

Provider

connect中的state是如何傳遞進(jìn)去的呢控汉?React-Redux 提供Provider組件笔诵,可以讓容器組件拿到state

import React, { Component } from 'react';
import { Provider } from 'react-redux';
import configureStore from './src/store/index';

const store = configureStore();

export default class Root extends Component {
  render() {
    return (
      <Provider store={store}>
        <Main />
      </Provider>
    )
  }
}

上面代碼中,Provider在根組件外面包了一層姑子,這樣一來(lái)乎婿,App的所有子組件就默認(rèn)都可以拿到state了。

Action & Reducer

組件與Redux全局狀態(tài)的關(guān)聯(lián)已經(jīng)搞定了街佑,可如何實(shí)現(xiàn)狀態(tài)的流轉(zhuǎn)呢谢翎?登錄狀態(tài)是如何擴(kuò)散到整個(gè)應(yīng)用的呢捍靠?

這里就需要Redux中的Action和Reducer了,Action負(fù)責(zé)接收UI組件的事件岳服,Reducer負(fù)責(zé)響應(yīng)Action剂公,返回新的store希俩,觸發(fā)與store關(guān)聯(lián)的UI組件更新吊宋。

export default connect((state) => ({
  loginPageVisible: state.showLoginPage.loginPageVisible,
}), (dispatch) => ({
  isLogin: () => dispatch(isLogin()),
  showLoginPage: (flag) => dispatch(showLoginPage(flag)),
  showRegPage: (flag) => dispatch(showRegPage(flag)),
}))(LoginPage)

this.props.showLoginPage(false);
this.props.isLogin();

在這個(gè)登錄場(chǎng)景中,如上代碼颜武,LoginPage將自己的props與store和action綁定璃搜,如果登錄成功,調(diào)用showLoginPage(false)action來(lái)隱藏自身鳞上,Reducer收到這個(gè)dispatch過(guò)來(lái)的action更新store狀態(tài):

//Action
export function showLoginPage(flag=true) {
  if(flag == true) {
    return {
      type: 'LOGIN_PAGE_VISIBLE'
    }
  } else {
    return {
      type: 'LOGIN_PAGE_INVISIBLE'
    }
  }
}

//Reducer
export function showLoginPage(state=pageState, action) {
  switch (action.type) {
    case 'LOGIN_PAGE_VISIBLE':
      return {
        ...state,
        loginPageVisible: true,
      }
      break;
    case 'LOGIN_PAGE_INVISIBLE':
      return {
        ...state,
        loginPageVisible: false,
      }
      break;
    default:
      return state;
  }
}

同時(shí)調(diào)用isLogin這個(gè)action更新應(yīng)用的全局狀態(tài)為已登錄:

//Action
export function isLogin() {
  return dispatch => {
      Secret.isLogin((result, token) => {
        if(result) {
          dispatch({
            type: 'LOGGED_IN',
          });
        } else {
          dispatch({
            type: 'NOT_LOGGED_IN',
          });
        }
      });
  }
}

//Reducer
export function isLogin(state=loginStatus, action) {
    switch (action.type) {
      case 'LOGGED_IN':
        return {
          ...state,
          status: 'LOGGED_IN',
        }
        break;
      case 'NOT_LOGGED_IN':
        return {
          ...state,
          status: 'NOT_LOGGED_IN',
        }
        break;
      default:
        return state;
    }
}

App組件由于已經(jīng)關(guān)聯(lián)了這個(gè)全局的登錄狀態(tài)这吻,在reducer更新了此狀態(tài)之后,App也會(huì)收到該更新篙议,進(jìn)而重新渲染自己唾糯,此時(shí)MainPage就會(huì)渲染出來(lái)了:

const {status} = this.props;
return (
  <TabNavigator>
    <TabNavigator.Item
      selected={this.state.selectedTab === 'mainTab'}
      renderIcon={() => <Image style={styles.icon} 
                         source={require('./imgs/icons/home.png')} />}
      renderSelectedIcon={() => <Image style={styles.icon} 
                          source={require('./imgs/icons/home_selected.png')} />}
      onPress={() => {
                      this.setState({ selectedTab: 'mainTab' });
                      if(status == 'NOT_LOGGED_IN') {
                        showLoginPage();
                      }
                  }
               }
    >
      //全局狀態(tài)已由NOT_LOGGED_IN變?yōu)長(zhǎng)OGGED_IN
      {status == 'NOT_LOGGED_IN'?<LoginPage {...this.props}/>:<MainPage {...this.props}/>}

后端微服務(wù)架構(gòu)

ms-architecture.png

項(xiàng)目構(gòu)建 & 開(kāi)發(fā)

1. 項(xiàng)目結(jié)構(gòu)

[圖片上傳中...(module.png-2df72a-1531835146967-0)]

poplar作為一個(gè)整體Maven項(xiàng)目,頂層不具備業(yè)務(wù)功能也不包含代碼鬼贱,對(duì)下層提供基礎(chǔ)的pom依賴導(dǎo)入
poplar-api有著兩重身份:API網(wǎng)關(guān)接收渠道層請(qǐng)求路由轉(zhuǎn)發(fā)移怯、作為微服務(wù)消費(fèi)者組織提供者服務(wù)調(diào)用完成服務(wù)串聯(lián)
poplar-user-service: 微服務(wù)提供者,提供注冊(cè)这难、登錄舟误、用戶管理等服務(wù)
poplar-feed-service: 微服務(wù)提供者,提供feed創(chuàng)建姻乓、生成信息流等服務(wù)
poplar-notice-service: 微服務(wù)提供者嵌溢, 提供通知消息服務(wù)

每個(gè)子項(xiàng)目以Module方式單獨(dú)創(chuàng)建

module.png

2. Maven聚合項(xiàng)目

Poplar由多個(gè)服務(wù)提供者、消費(fèi)者和公共組件構(gòu)成蹋岩,他們之間的依賴關(guān)系既有關(guān)聯(lián)關(guān)系又有父子從屬關(guān)系赖草,
為了簡(jiǎn)化配置也便于統(tǒng)一構(gòu)建,需要建立合理的依賴剪个。服務(wù)的提供者主要是Spring Boot項(xiàng)目秧骑,兼有數(shù)據(jù)庫(kù)訪問(wèn)等依賴;服務(wù)的消費(fèi)者同樣是是Spring Boot項(xiàng)目禁偎,但由于是API層腿堤,需要對(duì)外提供接口,所以需要支持Controller如暖;
服務(wù)消費(fèi)者笆檀、提供者通過(guò)Dubbo完成調(diào)用,這也需要共用的Dubbo組件盒至,所以我們可以發(fā)現(xiàn)消費(fèi)者酗洒、提供者共同依賴Spring Boot以及Dubbo士修,抽離出一個(gè)parent的pom即可,定義公共的父組件:

<groupId>com.lvwangbeta</groupId>
<artifactId>poplar</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>

<name>poplar</name>
<description>Poplar</description>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>    
    ...
</dependencies>

Poplar父組件除了引入公共的構(gòu)建包之外樱衷,還需要聲明其包含的子組件棋嘲,這樣做的原因是在Poplar頂層構(gòu)建的時(shí)候Maven可以在反應(yīng)堆計(jì)算出各模塊之間的依賴關(guān)系和構(gòu)建順序。我們引入服務(wù)提供者和消費(fèi)者:

 <modules>
    <module>poplar-common</module>
    <module>poplar-api</module>
    <module>poplar-feed-service</module>
    <module>poplar-user-service</module>
</modules>

子組件的pom結(jié)構(gòu)就變的簡(jiǎn)單許多了矩桂,指定parent即可沸移,pom源為父組件的相對(duì)路徑

<groupId>com.lvwangbeta</groupId>
<artifactId>poplar-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>

<name>poplar-api</name>
<description>poplar api</description>

<parent>
    <groupId>com.lvwangbeta</groupId>
    <artifactId>poplar</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>   

還有一個(gè)公共構(gòu)建包我們并沒(méi)有說(shuō),它主要包含了消費(fèi)者侄榴、提供者共用的接口雹锣、model、Utils方法等癞蚕,不需要依賴Spring也沒(méi)有數(shù)據(jù)庫(kù)訪問(wèn)的需求蕊爵,這是一個(gè)被其他項(xiàng)目引用的公共組件,我們把它聲明為一個(gè)package方式為jar的本地包即可桦山,不需要依賴parent:

<groupId>com.lvwangbeta</groupId>
<artifactId>poplar-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

在項(xiàng)目整體打包的時(shí)候攒射,Maven會(huì)計(jì)算出其他子項(xiàng)目依賴了這個(gè)本地jar包就會(huì)優(yōu)先將其打入本地Maven庫(kù)。
在Poplar項(xiàng)目根目錄執(zhí)行mvn clean install查看構(gòu)建順序恒水,可以看到各子項(xiàng)目并不是按照我們?cè)赑oplar-pom中定義的那樣順序執(zhí)行的会放,而是Maven反應(yīng)堆計(jì)算各模塊的先后依賴來(lái)執(zhí)行構(gòu)建,先構(gòu)建公共依賴common包然后構(gòu)建poplar寇窑,最后構(gòu)建各消費(fèi)者鸦概、提供者。

[INFO] Reactor Summary:
[INFO]
[INFO] poplar-common ...................................... SUCCESS [ 3.341 s]
[INFO] poplar ............................................. SUCCESS [ 3.034 s]
[INFO] poplar-api ......................................... SUCCESS [ 25.028 s]
[INFO] poplar-feed-service ................................ SUCCESS [ 6.451 s]
[INFO] poplar-user-service ................................ SUCCESS [ 8.056 s]
[INFO] ------------------------------------------------------------------

如果我們只修改了某幾個(gè)子項(xiàng)目甩骏,并不需要全量構(gòu)建窗市,只需要用Maven的-pl選項(xiàng)指定項(xiàng)目同時(shí)-am構(gòu)建其依賴的模塊即可,我們嘗試單獨(dú)構(gòu)建poplar-api這個(gè)項(xiàng)目饮笛,其依賴于poplar-commonpoplar:

mvn clean install -pl poplar-api -am  

執(zhí)行構(gòu)建發(fā)現(xiàn)Maven將poplar-api依賴的poplar-commonpoplar優(yōu)先構(gòu)建之后再構(gòu)建自己:

[INFO] Reactor Summary:
[INFO]
[INFO] poplar-common ...................................... SUCCESS [ 2.536 s]
[INFO] poplar ............................................. SUCCESS [ 1.756 s]
[INFO] poplar-api ......................................... SUCCESS [ 28.101 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

3. Dubbo & Zookeeper

上面所述的服務(wù)提供者和消費(fèi)者依托于Dubbo實(shí)現(xiàn)遠(yuǎn)程調(diào)用咨察,但還需要一個(gè)注冊(cè)中心,來(lái)完成服務(wù)提供者的注冊(cè)福青、通知服務(wù)消費(fèi)者的任務(wù)摄狱,Zookeeper就是一種注冊(cè)中心的實(shí)現(xiàn),poplar使用Zookeeper作為注冊(cè)中心无午。

3.1 Zookeeper安裝

下載解壓Zookeeper文件

$ cd zookeeper-3.4.6  
$ mkdir data  

創(chuàng)建配置文件

$ vim conf/zoo.cfg

tickTime = 2000
dataDir = /path/to/zookeeper/data
clientPort = 2181
initLimit = 5
syncLimit = 2

啟動(dòng)

$ bin/zkServer.sh start

停止

$ bin/zkServer.sh stop 

3.2 Dubbo admin

Dubbo管理控制臺(tái)安裝

git clone https://github.com/apache/incubator-dubbo-ops
cd incubator-dubbo-ops && mvn package  

然后就可以在target目錄下看到打包好的war包了媒役,將其解壓到tomcat webapps/ROOT目錄下(ROOT目錄內(nèi)容要提前清空),可以查看下解壓后的dubbo.properties文件宪迟,指定了注冊(cè)中心Zookeeper的IP和端口

dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest

啟動(dòng)tomcat

./bin/startup.sh    

訪問(wèn)

http://127.0.0.1:8080/   

這樣Dubbo就完成了對(duì)注冊(cè)中心的監(jiān)控設(shè)置

dubbo-admin.png

4. 開(kāi)發(fā)

微服務(wù)的提供者和消費(fèi)者開(kāi)發(fā)模式與以往的單體架構(gòu)應(yīng)用雖有不同酣衷,但邏輯關(guān)系大同小異,只是引入了注冊(cè)中心需要消費(fèi)者和提供者配合實(shí)現(xiàn)一次請(qǐng)求次泽,這就必然需要在兩者之間協(xié)商接口和模型穿仪,保證調(diào)用的可用席爽。

文檔以用戶注冊(cè)為例展示從渠道調(diào)用到服務(wù)提供者、消費(fèi)者和公共模塊發(fā)布的完整開(kāi)發(fā)流程啊片。

4.1 公共

poplar-common作為公共模塊定義了消費(fèi)者和提供者都依賴的接口和模型只锻, 微服務(wù)發(fā)布時(shí)才可以被正常訪問(wèn)到
定義用戶服務(wù)接口

public interface UserService {
    String register(String username, String email, String password);
}

4.2 服務(wù)提供者

UserServiceImpl實(shí)現(xiàn)了poplar-common中定義的UserService接口

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    @Qualifier("userDao")
    private UserDAO userDao;

    public String register(String username, String email, String password){
        if(email == null || email.length() <= 0)
            return Property.ERROR_EMAIL_EMPTY;

        if(!ValidateEmail(email))
            return Property.ERROR_EMAIL_FORMAT;
        ...
    }

可以看到這就是單純的Spring Boot Service寫(xiě)法,但是@Service注解一定要引入Dubbo包下的紫谷,才可以讓Dubbo掃描到該Service完成向Zookeeper注冊(cè):

dubbo.scan.basePackages = com.lvwangbeta.poplar.user.service

dubbo.application.id=poplar-user-service
dubbo.application.name=poplar-user-service

dubbo.registry.address=zookeeper://127.0.0.1:2181

dubbo.protocol.id=dubbo
dubbo.protocol.name=dubbo
dubbo.protocol.port=9001

4.3 服務(wù)消費(fèi)者

前面已經(jīng)說(shuō)過(guò)齐饮,poplar-api作為API網(wǎng)關(guān)的同時(shí)還是服務(wù)消費(fèi)者,組織提供者調(diào)用關(guān)系碴里,完成請(qǐng)求鏈路沈矿。

API層使用@Reference注解來(lái)向注冊(cè)中心請(qǐng)求服務(wù),通過(guò)定義在poplar-common模塊中的UserService接口實(shí)現(xiàn)與服務(wù)提供者RPC通信

@RestController
@RequestMapping("/user")
public class UserController {

    @Reference
    private UserService userService;

    @ResponseBody
    @RequestMapping("/register")
    public Message register(String username, String email, String password) {
        Message message = new Message();
        String errno = userService.register(username, email, password);
        message.setErrno(errno);
        return message;
    }
} 

application.properties配置

dubbo.scan.basePackages = com.lvwangbeta.poplar.api.controller

dubbo.application.id=poplar-api
dubbo.application.name=poplar-api

dubbo.registry.address=zookeeper://127.0.0.1:2181 

5.服務(wù)Docker化

如果以上步驟都已做完咬腋,一個(gè)完整的微服務(wù)架構(gòu)基本已搭建完成,可以開(kāi)始coding業(yè)務(wù)代碼了睡互,為什么還要再做Docker化改造根竿?首先隨著業(yè)務(wù)的復(fù)雜度增高,可能會(huì)引入新的微服務(wù)模塊就珠,在開(kāi)發(fā)新模塊的同時(shí)提供一個(gè)穩(wěn)定的外圍環(huán)境還是很有必要的寇壳,如果測(cè)試環(huán)境不理想,可以自己?jiǎn)?dòng)必要的docker容器妻怎,節(jié)省編譯時(shí)間壳炎;另外減少環(huán)境遷移帶來(lái)的程序運(yùn)行穩(wěn)定性問(wèn)題,便于測(cè)試逼侦、部署匿辩,為持續(xù)集成提供更便捷、高效的部署方式榛丢。

在poplar根目錄執(zhí)行 build.sh 可實(shí)現(xiàn)poplar包含的所有微服務(wù)模塊的Docker化和一鍵啟動(dòng):

cd poplar && ./build.sh

如果你有耐心铲球,可看下如下兩個(gè)小章節(jié),是如何實(shí)現(xiàn)的

5.1 構(gòu)建鏡像

Poplar采用了將各微服務(wù)與數(shù)據(jù)庫(kù)晰赞、注冊(cè)中心單獨(dú)Docker化的部署模式稼病,其中poplar-dubbo-admin是dubbo管理控制臺(tái),poplar-api poplar-tag-service poplar-action-service poplar-feed-service poplar-user-service是具體的服務(wù)化業(yè)務(wù)層模塊掖鱼,poplar-redis poplar-mysql提供緩存與持久化數(shù)據(jù)支持然走,poplar-zookeeper為Zookeeper注冊(cè)中心

poplar-dubbo-admin
poplar-api
poplar-tag-service
poplar-action-service
poplar-feed-service
poplar-user-service
poplar-redis
poplar-mysql
poplar-zookeeper

poplar-api poplar-tag-service poplar-action-service poplar-feed-service poplar-user-service業(yè)務(wù)層模塊可以在pom.xml中配置docker-maven-plugin插件構(gòu)建,在configuration中指定工作目錄戏挡、基礎(chǔ)鏡像等信息可省去Dockerfile:

<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>1.0.0</version>
    <configuration>
        <imageName>lvwangbeta/poplar</imageName>
        <baseImage>java</baseImage>
        <maintainer>lvwangbeta lvwangbeta@163.com</maintainer>
        <workdir>/poplardir</workdir>
        <cmd>["java", "-version"]</cmd>
        <entryPoint>["java", "-jar", "${project.build.finalName}.jar"]</entryPoint>
        <skipDockerBuild>false</skipDockerBuild>
        <resources>
            <resource>
                <targetPath>/poplardir</targetPath>
                <directory>${project.build.directory}</directory>
                <include>${project.build.finalName}.jar</include>
            </resource>
        </resources>
    </configuration>
</plugin>

如果想讓某個(gè)子項(xiàng)目不執(zhí)行docker構(gòu)建芍瑞,可設(shè)置子項(xiàng)目pom.xml的skipDockerBuildtrue,如poplar-common為公共依賴包增拥,不需要單獨(dú)打包成獨(dú)立鏡像:

<skipDockerBuild>true</skipDockerBuild>

在poplar項(xiàng)目根目錄執(zhí)行如下命令啄巧,完成整個(gè)項(xiàng)目的業(yè)務(wù)層構(gòu)建:

mvn package -Pdocker  -Dmaven.test.skip=true docker:build
[INFO] Building image lvwangbeta/poplar-user-service
Step 1/6 : FROM java
 ---> d23bdf5b1b1b
Step 2/6 : MAINTAINER lvwangbeta lvwangbeta@163.com
 ---> Running in b7af524b49fb
 ---> 58796b8e728d
Removing intermediate container b7af524b49fb
Step 3/6 : WORKDIR /poplardir
 ---> e7b04b310ab4
Removing intermediate container 2206d7c78f6b
Step 4/6 : ADD /poplardir/poplar-user-service-2.0.0.jar /poplardir/
 ---> 254f7eca9e94
Step 5/6 : ENTRYPOINT java -jar poplar-user-service-2.0.0.jar
 ---> Running in f933f1f8f3b6
 ---> ce512833c792
Removing intermediate container f933f1f8f3b6
Step 6/6 : CMD java -version
 ---> Running in 31f52e7e31dd
 ---> f6587d37eb4d
Removing intermediate container 31f52e7e31dd
ProgressMessage{id=null, status=null, stream=null, error=null, progress=null, progressDetail=null}
Successfully built f6587d37eb4d
Successfully tagged lvwangbeta/poplar-user-service:latest
[INFO] Built lvwangbeta/poplar-user-service
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------ 

5.2 啟動(dòng)運(yùn)行容器

由于poplar包含的容器過(guò)多寻歧,在此為其創(chuàng)建自定義網(wǎng)絡(luò)poplar-netwotk

docker network create --subnet=172.18.0.0/16 poplar-network

運(yùn)行以上構(gòu)建的鏡像的容器,同時(shí)為其分配同網(wǎng)段IP

啟動(dòng)Zookeeper注冊(cè)中心

docker run --name poplar-zookeeper --restart always -d  --net poplar-network --ip 172.18.0.6  zookeeper 

啟動(dòng)MySQL

docker run --net poplar-network --ip 172.18.0.8  --name poplar-mysql -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 -d  lvwangbeta/poplar-mysql

啟動(dòng)Redis

docker run --net poplar-network --ip 172.18.0.9 --name poplar-redis -p 6380:6379 -d redis

啟動(dòng)業(yè)務(wù)服務(wù)

docker run --net poplar-network --ip 172.18.0.2 --name=poplar-user-service -p 8082:8082 -t lvwangbeta/poplar-user-service

docker run --net poplar-network --ip 172.18.0.3 --name=poplar-feed-service -p 8083:8083 -t lvwangbeta/poplar-feed-service

docker run --net poplar-network --ip 172.18.0.4 --name=poplar-action-service -p 8084:8084 -t lvwangbeta/poplar-action-service

docker run --net poplar-network --ip 172.18.0.10 --name=poplar-api -p 8080:8080 -t lvwangbeta/poplar-api

dockerps.png

至此秩仆,poplar項(xiàng)目的后端已完整的構(gòu)建和啟動(dòng)码泛,對(duì)外提供服務(wù),客戶端(無(wú)論是Web還是App)看到只有一個(gè)統(tǒng)一的API澄耍。

項(xiàng)目完整源碼:https://github.com/lvwangbeta/Poplar

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末噪珊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子齐莲,更是在濱河造成了極大的恐慌痢站,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件选酗,死亡現(xiàn)場(chǎng)離奇詭異阵难,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)芒填,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)呜叫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人殿衰,你說(shuō)我怎么就攤上這事朱庆。” “怎么了闷祥?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵娱颊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我凯砍,道長(zhǎng)箱硕,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任果覆,我火速辦了婚禮颅痊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘局待。我一直安慰自己斑响,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布钳榨。 她就那樣靜靜地躺著舰罚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪薛耻。 梳的紋絲不亂的頭發(fā)上营罢,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼饲漾。 笑死蝙搔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的考传。 我是一名探鬼主播吃型,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼僚楞!你這毒婦竟也來(lái)了勤晚?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤泉褐,失蹤者是張志新(化名)和其女友劉穎赐写,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體膜赃,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挺邀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了财剖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悠夯。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖躺坟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乳蓄,我是刑警寧澤咪橙,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站虚倒,受9級(jí)特大地震影響美侦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜魂奥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一菠剩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧耻煤,春花似錦具壮、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至炮赦,卻和暖如春怜跑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吠勘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工性芬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留峡眶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓植锉,卻偏偏與公主長(zhǎng)得像辫樱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子汽煮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容