今天這篇文章所涉及的相關觀點做法,按照我們公司某位架構師的說法就是,架構這東西沒有對錯之分,集思廣益,不一定我的做法就是對的,不一定我的做法就是最優的,小編和大家一起探討探討。
一、場景
首先我們模擬一個SDK交易鏈路的場景,對交易不是很懂的同學可以先移步至微信支付瞭解下大概業務,其實小編也不是特別精通交易,剛實習,但這不影響本文哈!一筆交易的生命週期大致可以分為這麼幾個階段,即預下單---支付---交易通知
,什麼意思呢?拿微信掃碼點餐付款來說,首先微信客戶端掃碼之後會先生成一筆訂單,當預下單完成之後,客戶端會觸發SDK發起支付請求,然後使用者輸入密碼完成支付,支付之後會傳送訂單狀態通知使用者,我們最後才能知道我到底付款成功沒有。
下面是微信掃碼支付業務時序圖:
既然業務步驟是明確的,我們首先考慮程式碼方面應該怎麼去架設會好一點,首先猜想能否定義三個生命週期方法,如下:
// 前置業務---預下單
beforeService()
// 核心業務處理----支付
doService()
// 後置業務處理----通知
afterService()
複製程式碼
但是,實際業務每個步驟往往非常複雜,也就說比如我們有可能在預下單的時候會加入優惠資訊,判斷當前使用者這筆訂單是否符合優惠,如果優惠後的金額為0,還有可能不需要任何的支付處理,直接就傳送通知給到使用者。還有一點我們業務通常還要區別是刷卡預下單還是掃碼預下單,掃碼預下單還要區分是主掃還是被掃,因為這幾種區分是實際和線下場景相匹配的,為了降低耦合,這時我們會分別提供一個介面來處理各種請求。
既然是每種場景對應一個介面,我們又可以想到,通常介面都是需要進行安全性校驗的,如MD5、RSA及SHA1各種加密,保證交易的安全性。為了和核心業務區分開來,我們通常會在前置業務中甚至在filter端就把這些校驗就完成掉,避免如果校驗出錯進到業務層去。既然每個介面都有諸如前置校驗----核心處理----後置處理
的邏輯,那我們可以把上面的設想再細分一下,即:
// 前置業務---引數校驗,加密等操作
beforeService()
// 核心業務處理----預下單、支付等核心業務
doService()
// 後置業務處理----儲存訂單、儲存流水、傳送通知等後置業務
afterService()
複製程式碼
那麼程式碼層應該如何去設計呢?可否這樣,像下面的UML圖:
下面對各個元件進行講解:
-
SDKService
最頂層的抽象,一般業務類不需要實現它,需要需要擴充套件該介面,直接繼承即可。
-
SDKNoCardService
無卡交易相關能力,擴充套件SDKService
-
SDKRomoteService
呼叫遠端方法交易相關能力,擴充套件SDKService,如遠端呼叫微信下單介面
-
AbstractSDKService
抽象類,實現SDKService,同時定義上面的三個宣告週期方法,相當於一個模板,具體實現由子類去重寫即可
-
SDKNoCardServceImpl
無卡交易業務實現類,繼承抽象類,因為需要獲取卡號,所以實現SDKNoCardService介面
-
SDKCardTradeServiceImpl
刷卡交易業務實現類,繼承抽象類,因為需要呼叫遠端服務,所以實現SDKRomoteService介面
這樣做的好處在於,如果我需要引入一個新的需求,比如說小額免籤免密,那麼我只需要建立一個新類繼承抽象類AbstractSDKService
即可,如果小額免籤免密需要一個新的能力,那麼只需要新增一個介面擴充套件SDKService即可。下面給出相應的測試程式碼:
SDKService.java
package com.example.interfaces;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
public interface SDKService<T,R> {
/**
* 業務處理
* @param request
* 請求
*
* @return
*/
public R service(HttpServletRequest request);
/**
* 業務處理
* @param body
* 客戶端請求訊息體物件
*
* @param request
* 請求
*
* @return
*/
public R service(T body, HttpServletRequest request);
/**
* 業務處理
* @param body
* 客戶端請求訊息體物件
*
* @param headers
* 客戶端請求頭資訊物件
*
* @return
*/
public R service(T body, Map<String, String> headers);
}
複製程式碼
SDKRemoteService.java
package com.example.interfaces;
public interface SDKRemoteService<T, R> extends SDKService<T, R>{
/**
* 呼叫遠端交易,比如說呼叫微信支付
* @return
*/
public R call();
}
複製程式碼
SDKNoCardService.java
package com.example.interfaces;
public interface SDKNoCardService<T, R> extends SDKService<T, R> {
/**
* 獲取卡號
* @return
*/
public R getCardNo();
}
複製程式碼
AbstractSDKService.java
package com.example.interfaces;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
/**
* 傳入引數T,返回結果R
*
* @author huangjiawei
*
* @param <T>
* @param <R>
*/
public abstract class AbstractSDKService<T, R> implements SDKService<T, R> {
// 前置處理,比如可以對引數進行校驗,由子類選擇性重寫
protected void beforeService() {
};
/**
* 核心業務方法,由不同業務子類選擇性去重寫
*
* @return
*/
protected abstract R doService();
// 後置處理,比如記錄業務結果,由子類選擇性重寫
protected void afterService() {
};
/**
* 接受請求
*/
@Override
public R service(HttpServletRequest request) {
// 模擬請求體
System.err.println("service with only request!");
return this.service(null, request);
}
/**
* 處理請求體
*/
@Override
public R service(T body, HttpServletRequest request) {
// 模擬請求體
Map<String, String> headers = new HashMap<>();
System.err.println("service with body and request!");
return this.service(body, headers);
}
/**
* 處理請求頭和實際業務邏輯
*/
@Override
public R service(T body, Map<String, String> headers) {
System.err.println("service with bofy and headers!");
R response = null;
// 記錄日誌
//......
// 前置處理
beforeService();
// 核心處理
doService();
// 後置處理
afterService();
// ......
return response;
}
}
複製程式碼
SDKCardTradeServiceImpl.java
package com.example.interfaces;
public class SDKCardTradeServiceImpl<T,R> extends AbstractSDKService<T,R> implements SDKRemoteService<T,R> {
// 在這裡可以選擇性重寫父類的before()方法
@Override
protected void beforeService() {
super.beforeService();
System.err.println("SDKCardTradeServiceImpl before!");
}
@Override
protected R doService() {
// 處理刷卡特定業務邏輯
System.err.println("SDKCardTradeServiceImpl doService!");
return null;
}
/**
* 我同時擁有呼叫遠端方法的能力,所以我實現了SDKRemoteService介面
*/
@Override
public R call() {
return null;
}
// 在這裡可以選擇性重寫父類的after()方法
@Override
protected void afterService() {
super.afterService();
System.err.println("SDKCardTradeServiceImpl afterService!");
}
}
複製程式碼
SDKNoCardServceImpl.java
package com.example.interfaces;
/**
* 無卡交易核心業務處理
* @author huangjiawei
*
*/
public class SDKNoCardServceImpl<T,R> extends AbstractSDKService<T,R> implements SDKNoCardService<T,R> {
// 在這裡可以選擇性重寫父類的before()方法
@Override
protected R doService() {
return null;
}
/**
* 我同時擁有獲取卡號的能力,所以我實現了SDKNoCardService介面
*/
@Override
public R getCardNo() {
return null;
}
// 在這裡可以選擇性重寫父類的after()方法
@Override
protected void afterService() {
super.afterService();
System.err.println("SDKNoCardServceImpl 後置處理");
}
}
複製程式碼
Main.java
package com.example.interfaces;
public class Mian {
public static void main(String[] args) {
SDKCardTradeServiceImpl<String,String> o = new SDKCardTradeServiceImpl<>();
o.service(null);
System.err.println("======================================");
SDKNoCardServceImpl<String, String> p = new SDKNoCardServceImpl<>();
p.service(null);
}
}
複製程式碼
程式輸出:
service with only request!
service with body and request!
service with bofy and headers!
SDKCardTradeServiceImpl before!
SDKCardTradeServiceImpl doService!
SDKCardTradeServiceImpl afterService!
======================================
service with only request!
service with body and request!
service with bofy and headers!
SDKNoCardServceImpl 後置處理
複製程式碼
注意我們實際呼叫的時候只需要呼叫頂層的service()
即可,面向介面程式設計。
二、總結
認真看,其實這個已經定義好了一個大模板了,會非常實用的。小編認為,一般的設計思路是先採用一個頂層介面進行高層抽象,然後建立其他介面擴充套件頂層介面,在頂層介面下建立一層抽象層,提供預設的實現,定義模板方法給不同的業務類重寫。當然,處理本文的做法,一般處理web請求我們也可以通過AOP切面的方式對前置處理和後置處理進行攔截,即利用環繞通知即可,但是,採用aop的缺點可能會獲取不到request請求的引數(沒測過,大佬們說會,我也不清楚為什麼,等你來告訴小編!),本文的方法利用了java物件導向的特性,擴充套件性會更好點,不過略抽象!
本文講解的比較抽象,很多地方可能沒有講的特別清楚,同學們可以評論,我們一起探討探討。