隨著公司業務的不斷變化,幾年前的 A 專案和底層 DB_A 資料庫華麗轉身為核心業務服務和核心資料庫。
想從 DB_A 資料庫取得資料的web 服務越來越多,專案之間的關係逐漸演變成下面這樣:
很容易看出來按上圖這樣的發展趨勢會存在很多問題(項目關係為個人抽像出來的簡化版,實際情況比這要復雜的多)。
a. 當 webappA 運作過程中出現異常無法訪問,webappB/ webappC .... 還能正常取得 DB_A 資料嗎?
b. 各種各樣的提供給webappB/webappC ... 取得DB_A 資料的服務都集中在webappA 中,webappA 的體積會無限水平擴張,誰都不喜歡贅肉對吧?
c. webappA 專案在運作過程中除了要正常提供自己的服務給使用者以外,還要兼顧其他專案取得資料的請求,勢必會造成效能瓶頸。
其中的有些問題已經在專案上線推進中出現過,隔三差五停機維護變成響亮的巴掌扇到專案組的臉上確實也不好受。
題外話:以目前網路的發展速度和各公司業務擴展,能準確預測專案兩年以內發展方向/並提前做好擴展的架構師,能力已經非常不錯。
專案組有人提出繞過專案webappA ,其餘的webappB/webappC ...直接連接DB_A 進行交互,但很快就被否決了(每個專案資料庫存取層你都要重新定義和編寫)。
能否將其中的資料庫存取層獨立出來,做為服務容許授權的項目進行存取?如下:
核心想法是為無限增多的N個 wabapp 提供特定資料庫的存取。這樣既避免了專案之間的耦合,也提高的資料存取層的重複使用率。
想法已經有了,那就開幹吧,BB 解決不了問題。 大概花了兩天時間搭建,填了無數坑,終於出落的和我預想中的一樣貼切。
原專案因商用無法開源,demo 經我重新組織已開源到:。
需要這方面實踐的同學,clone 到本地跑起來一切也明朗了。
需要DB_A 資料項目依賴dap -service-api 存取dao-service-impl 服務即可。
dao-service-api 為提供給外層的接口,最終的呈現方式為 jar, maven 專案直接依賴即可。
# 如果有舊非maven 項目,使用 maven-jar-plugin/maven-assembly-plugin 將所依賴的jar 都組裝進去加入到專案lib 裡面。
dao-service-impl 由cxf + spring + druid + jpa(hibernate impl) 開源類別庫建構而成的純後端組件服務。
做為服務介面的實作層,最終呈現方式為war,可進行叢集或分散部署,給其他項目提供服務。
目錄結構一目了然,上手開發速度很快,其中自己實現了簡易的程式碼產生(GenCodeServlet),dao 層 + webService 層 介面和實作都可以自動產生。
webSevice 實現層注入 dao 層接口,針對單表封裝增刪改查5個方法,大體上不用寫多餘的方法,避免編寫百分之 90 的 SQL 。
@WebService @SOAPBinding(style = SOAPBinding.Style.RPC)public interface UserWs {/** * 通过 ID 过去单个 User 实体对象 * cxf 传输返回对象不可为null,Dao 层获取为null时 * 实例化返回空对象,判空时使用对象主键进行判断即可 * * @param id 主键ID */UserPO getUser(String id);/** * 通过类似的 PO 获取多个 User 实体对象 * * @param userPO 对照的实体对象 */List<UserPO> listUser(UserPO userPO);/** * 通过类似的 PO 获取多个 User 实体对象 * * @param userPO 对照的实体对象 * @param orderby 排序字段 * @param asc 是否升序 */List<UserPO> listUserOrdrBy(UserPO userPO, String orderby, Boolean asc);/** * 新增 User 实体对象 * * @param userPO 要新增的对象 */UserPO addUser(UserPO userPO);/** * 更新 User 实体对象 * * @param userPO 要更新的对象 */UserPO updateUser(UserPO userPO); }
開發方式簡單粗暴,使用工具反向產生 hibernate 資料庫 po ,存取 GenCodeServlet 產生 dao/ws 層介面與實作。
新增設定檔選項,發佈 cxf webService 服務即可,估計5分鐘的時間都不要。
發佈的單表服務在呼叫方裡面理解為資料庫存取層,在你專案規定的地方注入,進行耦合處理業務邏輯。
這個模組存在的意義,相當於一個怎樣整合 cxf 發佈的服務的 demo。
a.呼叫方項目中已整合了spring (依賴dao-service-api)
<jaxws:client id="UserWs" serviceClass="com.rambo.dsd.sys.ws.inter.UserWs" address="${cxf.server.url}/UserWs"><jaxws:outInterceptors><ref bean="wss4JOutInterceptor"/></jaxws:outInterceptors></jaxws:client>
特定的使用方式(在spring 注入的前提下)
Map<String, Object> map = new HashMap<>(); UserWs userWs = (UserWs) SpringContextUtil.getBean("UserWs"); UserPO user = userWs.getUser("031e7a36972e11e6acede16e8241c0fe"); map.put("1.获取单个用户:", user); user.setPhone("18975468245"); UserPO userPO1 = userWs.updateUser(user); map.put("2.更新单个用户:", userPO1); UserPO userPO2 = new UserPO(); userPO2.setName("rambo"); userPO2.setPasswd(SecurityUtil.encryptMD5("123456")); userPO2.setSex("男"); userPO2.setYxbz("Y"); UserPO userPO3 = userWs.addUser(userPO2); map.put("3.新增单个用户:", userPO3); UserPO userPO4 = new UserPO(); userPO4.setSex("男"); List<UserPO> userPOList = userWs.listUser(userPO4); map.put("4.获取所有的男用户:", userPOList); UserPO userPO5 = new UserPO(); userPO5.setSex("男"); List<UserPO> userPOList1 = userWs.listUserOrdrBy(userPO5, "sorts", true); map.put("5.获取所有的男用户并按照 sorts 字段排序:", userPOList1);return map;
# b.呼叫方專案中未整合spring (依賴dao-service-api)
使用工具或指令產生cxf 服務用戶端,引入工廠模式在使用的地方取得服務實例,進行耦合即可。
UserWsImplService userWsImplService = new UserWsImplService(new URL(cxfServerUrl + "/UserWs?wsdl")); UserWs userWs = userWsImplService.getUserWsImplPort(); addWSS4JOutInterceptor(userWs); UserPO user = userWs.getUser("031e7a36972e11e6acede16e8241c0fe"); map.put("1.获取单个用户:", user); user.setPhone("18975468245"); UserPO userPO1 = userWs.updateUser(user); map.put("2.更新单个用户:", userPO1); UserPO userPO2 = new UserPO(); userPO2.setUuid(StringUtil.getUUID()); userPO2.setName("rambo"); userPO2.setPasswd(SecurityUtil.encryptMD5("123456")); userPO2.setSex("男"); userPO2.setYxbz("Y"); UserPO userPO3 = userWs.addUser(userPO2); map.put("3.新增单个用户:", userPO3); UserPO userPO4 = new UserPO(); userPO4.setSex("男"); UserPOArray userPOArray1 = userWs.listUser(userPO4); map.put("4.获取所有的男用户:", userPOArray1); UserPO userPO5 = new UserPO(); userPO5.setSex("男"); UserPOArray userPOArray2 = userWs.listUserOrdrBy(userPO5, "sorts", true); map.put("5.获取所有的男用户并按照 sorts 字段排序:", userPOArray2.getItem());
cxf 採用soap 通訊協議,畢竟是對外發佈出去的服務,安全性還是很重要。
安全認證引入 cxf ws-security wss4j 攔截器實現,soap 封包新增認證資訊。
a.服務端設定
<!--服务端安全认证回调函数--><bean id="serverAuthCallback" class="com.rambo.dsd.base.handler.CXFServerAuthHandler"/><!--安全日志认证拦截器--><bean id="wss4JInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"><constructor-arg><map><entry key="action" value="UsernameToken"/><entry key="passwordType" value="PasswordDigest"/><entry key="passwordCallbackRef" value-ref="serverAuthCallback"/></map></constructor-arg></bean>
## 服務端實作 javax.security.auth .callback.CallbackHandler 的安全回呼函數:
public class CXFServerAuthHandler implements CallbackHandler { protected final static Logger log = LoggerFactory.getLogger(CXFServerAuthHandler.class); private static final Map<String, String> userMap = new HashMap<String, String>(); static { userMap.put("webappA", "webappA2017"); userMap.put("webappB", "webappB2017"); } public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { WSPasswordCallback pc = (WSPasswordCallback) callback; String clientUsername = pc.getIdentifier(); String serverPassword = userMap.get(clientUsername); log.info(" client:{} is starting webservice...", clientUsername); int usage = pc.getUsage(); if (usage == WSPasswordCallback.USERNAME_TOKEN) { pc.setPassword(serverPassword); } else if (usage == WSPasswordCallback.SIGNATURE) { pc.setPassword(serverPassword); } } } }
b.整合Spring 的客戶端設定
<!--客户端安全认证回调函数--><bean id="wsClientAuthHandler" class="com.rambo.dsc.handler.WsClientAuthHandler"/><!--安全认证对外拦截器--><bean id="wss4JOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"><constructor-arg><map><entry key="action" value="UsernameToken"/><entry key="user" value="webappA"/><entry key="passwordType" value="PasswordDigest"/><entry key="passwordCallbackRef" value-ref="wsClientAuthHandler"/></map></constructor-arg></bean>
注入的webService 服務設定攔截器:
<jaxws:outInterceptors><ref bean="wss4JOutInterceptor"/></jaxws:outInterceptors>## 客戶端實作 javax.security.auth.callback.CallbackHandler的安全回呼函數:
public class WsClientAuthHandler implements CallbackHandler { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { WSPasswordCallback pc = (WSPasswordCallback) callback; pc.setPassword("webappA2017"); } } }
c.未整合Spring 的客戶端進行編碼
private void addWSS4JOutInterceptor(Object wsClass) { Endpoint cxfEndpoint = ClientProxy.getClient(wsClass).getEndpoint(); Map outProps = new HashMap(); outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN); outProps.put(WSHandlerConstants.USER,"webappA"); outProps.put(WSHandlerConstants.MUST_UNDERSTAND, "0"); outProps.put(WSHandlerConstants.PASSWORD_TYPE, "PasswordDigest"); outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, WsClientAuthHandler.class.getName()); cxfEndpoint.getOutInterceptors().add(new WSS4JOutInterceptor(outProps)); }專案中服務端安全認證使用的是 UsernameToken,cxf 支援認證方式/密碼類型還有很多,當然你也可以自訂安全認證方式。
4.結論
網路公司服務架構是血液,是習慣,每家公司都有自己的套路和架構,細節有不同,但是核心理念是通的。
這次實踐有點微服務的感覺,但還遠遠不夠,如服務的註冊/路由/容錯/緩存.....很多很多,項目已開源到上面,有興趣一起完善它吧。
#
以上是打造獨立資料庫存取的中間服務的詳細內容。更多資訊請關注PHP中文網其他相關文章!