以前在服務化設計模式實踐,裏面介紹了交易側系統服務變遷的模式,服務的變遷更好的支持了業務的發展,伴隨着業務的發展,對業務系統內部的要求也更好,須要具備更好的擴展性。隨着業務的不斷髮展,每一個服務內部的邏輯也變得愈來愈多,須要有更好的抽象來支持之後更多的業務類型。算法
重構的項目有訂單服務,預訂系統,退款系統;這三個系統都是與用戶交易行爲息息相關。設計模式
其中訂單系統參與重構的模塊爲訂單建立,訂單狀態流轉,訂單支付;性能優化
預訂系統的重構主要爲了支撐更多的預訂方式,如以前已經支持的庫存模式、商家接單模式和售中客服模式,伴隨着重構還須要支持商家系統直連模式,並且須要可以支持之後業務發展更多的預訂模式。架構
退款服務的複雜度主要來源於多種退款類型,如用戶退款,系統退款,商家退款和客服退款等多種類型,而每種類型又有各類不一樣的退款規則;退款服務須要支持多種業務,如已有的KTV預訂和將要擴展出的酒水點單。併發
在這裏咱們主要來說講預訂系統重構,由於這個系統的重構幾乎涵蓋了訂單服務和退款服務重構所使用到的技術異步
抽象預訂流程,並模板化分佈式
對可變化的部分支持配置化ide
在上線過程當中支持新老流程切換微服務
由圖中能夠看出,業務流程很是複雜,一個訂單的預訂過程會根據不一樣的狀況走不一樣的預訂渠道,若是一個預訂渠道由於某種緣由預訂失敗了,可能會繼續使用另一個預訂渠道繼續進行預訂,也就是會發生流轉。高併發
另外,在預訂成功和預訂失敗時,會須要作一些其餘操做,例如發送短信告知用戶結果等;
圖中還有一點沒有體現的是,在開始發起預訂時,須要校驗數據的正確性,校驗是否複覈預訂規則等等校驗。
根據這些條件咱們作了如下抽象:
首先訂單從預訂開始、預訂中到預訂成功/失敗定義爲預訂的主流程,其中每一個接單都是一個重要的業務節點,這種主流程定義爲一級業務。
對於不一樣的預訂模式(如庫存模式、商家接單模式、客服售中介入模式和商家系統直連等),抽象爲預訂渠道。預訂渠道以前的轉化定義爲渠道流轉。
預訂渠道會直接影響預訂結果
預訂中、預訂成功/失敗 時渠道須要個性化的操做,如商家接單渠道開始時須要通知商家等,這種流程會影響一級業務,但其業務具備個性化特徵,所以定義爲二級業務。
同時預訂中、預訂成功/失敗後 須要進行不影響業務流程的操做,如發送短信告知用戶預訂結果,記錄一些屬性等等。這部分業務定義爲三級業務。
一級業務是系統最重要的業務,業務流程標準化且會直接影響業務結果;二級業務是一級業務一個步驟,但由於預訂渠道的不一樣而有個性化操做;三級業務是根據業務結果來執行的操做,不會再影響系統的主流程。
3.1 核心業務流程
預訂中核心業務流程是最重要的部分,也就是圖中所標註的一級業務,每個步驟都是一個重要的業務節點,且每個節點都會有一些複雜的邏輯。
所以在重構時,將核心業務流程的實現定義爲一個模板引擎,在這個模板引擎中的每個節點均可以是一個接口,能夠任意的配置。在代碼上的表現就會是這樣的。
開始預訂:
public class KtvReserveService { public KtvReserveResultDTO reserve(KtvReserveContext reserveContext) throws ReserveException { //校驗 KtvValidateResult validateResult = this.ktvReserveValidateStack. validate(reserveContext); if (validateResult == null || !validateResult.isValid()) { return KtvReserveResultDTO.createFailedResult("validate invalid"); } //斷定預訂渠道 KtvReserveChannel reserveChannel = reserveChannelJudgeService. judgeChannelType(reserveContext); reserveDataService.store() reserveDataService.transferReserveChannelStatus(); //開始渠道預訂 ChannelResult channelResult = this.reserveChannelService. reserve(reserveContext); return KtvReserveResultDTO
.genResult(channelResult.isSuccess(), channelResult.getDesc(),
reserveFlow.getReserveId()); }} 渠道反饋預訂結果: public class KtvReplyReserveService { @Override public ReplyReserveResult reply(KtvReplyReserveInfo replyReserveInfo) throws ReplyReserveException { //校驗 KtvValidateResult validateResult = replyReserve ValidateStack.validate(replyReserveInfo); if (validateResult == null || !validateResult.isValid()) { logger.warn(String.format(" %s validate failed", param)); return ReplyReserveResult.createFailedResult("validate failed"); } //更新預訂狀態 reserveDataService.transferReserveChannelStatus(); ReplyReserveResult result; //斷定預訂結果 KtvReserveStatus toReserveStatus = this.reserveChannelJudgeService. judgeReserveResult(replyReserveInfo); boolean reserveFailed = toReserveStatus == null || toReserveStatus == KtvReserveStatus. ReserveFailed || toReserveStatus == KtvReserveStatus.Init; if (reserveFailed) { //預訂失敗處理 result = this.reserveFailed(replyReserveInfo); } else if (toReserveStatus == KtvReserveStatus.ReserveSuccess) { //預訂成功處理 result = this.reserveSuccess(replyReserveInfo); } else { // 須要轉移其餘渠道預訂 result = ktvReserveTransferService.transferChannel(ktvReserveContext); } // 渠道處理內部事務 this.replyReserveChannelService.reply(replyReserveInfo); return result; }}
3.2 校驗棧
在業務性很強的服務來講,在業務開始以前須要有複雜的校驗,若是在這個服務中支持多種業務類型,還須要根據不一樣的業務類型來選擇不一樣的校驗邏輯,所以在服務中將校驗棧獨立出來。
校驗棧的組裝採用責任鏈模式,這樣每一個校驗service經過組裝的方式便可以靈活支持多種校驗。可是對於業務主流程來講,把校驗service的組裝服務並不適合放在主業務流程裏,所以在重構的時候將校驗棧的組裝邏輯放在一個單獨的service中採用代理模式進行組裝。
public interface KtvReserveValidateService { /** * 校驗預訂信息 * @param reserveContext * @return */ KtvValidateResult validate(KtvReserveContext reserveContext);} public class KtvReserveValidateStack implements KtvReserveValidateService { private List validateServices; public void setValidateServices( List validateServices) { this.validateServices = validateServices; } @Override public KtvValidateResult validate(KtvReserveContext reserveContext) { if (CollectionUtils.isEmpty(validateServices)) return KtvValidateResult.validResut(); for (KtvReserveValidateService service : validateServices) { KtvValidateResult result = service.validate(reserveContext); if (result == null || !result.isValid()) return result; } return KtvValidateResult.validResut(); }}
3.3 業務分級
在前面講到,在重構中將代碼功能分紅了一級功能,二級功能和三級功能。
其中一級功能的每一個步驟都須要嚴格保證,若是發生問題就須要直接影響業務流程,例如在預訂業務中,預訂數據狀態的更新就是一級業務,若是更新失敗就須要終結業務;
二級業務也是重要的業務,可是不須要二級業務不能影響最終的業務結果,可是當二級業務出錯時也須要及時處理,如在更新訂單狀態爲購買成功時發生錯誤,須要及時告警,或者異步化保證數據一致性;
三級業務徹底不影響業務流程,不少都是異步化調用外部服務,如短信通知用戶、雙寫訂單上的預訂狀態(老業務)等。預訂服務中的三級業務都是根據預訂結果而觸發,所以在這裏使用觀察者模式實現便可。
業務服務對業務流程相對較多,並且每一步出現問題都有可能直接影響購買結果,這種與錢息息相關的業務,一單出錯就會有各方來追殺,並且也極大影響用戶和商家的體驗。對業務分級是將不用影響最終結果的業務剝離出去,將最核心的業務重點對待,不一樣級別用不一樣的處理方式。
3.4 數據模型統一
這裏的業務模型是業務流程的數據統一。例如在開始預訂的業務中使用ReserveContext做爲整個業務流程的數據協議,在分業務時也能採用相同的數據,即避免相同數據的重複讀取也便於立減和時間。
一個業務流程的處理,其實也是一種服務的處理過程,而數據模型就是其業務的協議,好協議才能產生好實踐。
3.5 機制同策略分離
機制同策略分離是Unix設計中的基本原則之一,是將將程序的引擎(程序核心域的核心算法和邏輯規格)從接口部分(接受用戶命令,展現結果等)分離;由於在一個系統中策略變化相對較多,例如預訂服務的三級業務中之後並不須要再同步訂單上的預訂狀態,若是策略的變化影響到機制會使得系統很不穩定,有需求修改時會致使系統大的修改,在功能上線須要QA驗證的範圍也會很大;致使策略變得死板,難以適應用戶需求的改變,任何策略的改變都極有可能動搖機制。
機制同策略分離的機制引用最普遍的是MVC模式。
在這裏預訂流程的基本模型就是咱們的引擎,在引擎中規定了幾個基本的業務節點,而每一個業務點的實現都有各自的接口規定,若是有需求的變動只須要更改各個業務節點自身的接口實現便可。至於如何接收支付結果發起預訂,以及何種狀況下反饋預訂接口都是與核心流程分離的。
重構最痛苦的部分是怎麼把項目上線。
在這幾回的重構中,主要實行了兩種重構:
項目內部邏輯重構,但沒有新建數據表,對外的接口沒有修改;
修改了對外的接口,新增了數據表。
第一種模式重構,在上線時比較容易,由於基本不用考慮到新老邏輯兼容的問題,第二種模式的重構在上線時須要考慮新老接口的兼容。在此次預訂服務重構過程當中,修改了對外接口新增了數據記錄,並且重構後的系統邏輯也與新的數據表耦合,所以在新老接口上須要作特別的兼容。此次預訂服務改造主要涉及到發起預訂和預訂反饋,所以在兼容上須要在新老邏輯的入口上都須要作數據轉換。另外在測試階段須要模擬上線的步驟,校驗上線每一個階段的新老接口兼容如何,功能是否正常。
分拆上線
通常重構的部分不宜過大,過大時須要考慮的兼容就更多,影響到的外界系統也會更多;通常重構最好的方法是分步重構,重構一部分以後驗證上線,小步快跑的方式上線。
在此我向你們推薦一個架構學習交流羣。交流學習羣號:478030634 裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構等這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良多
在這個重構的過程當中咱們主要有一下基本的原則:
機制同策略分離
協議統一化和簡單化
開閉原則
主要使用到一下設計模式:
1.代理模式
2.監聽者模式
3.責任鏈模式
4.裝飾者模式