一起探討下web請求流程的程式碼結構設計(簡單以交易為栗子)

擁抱心中的夢想發表於2019-02-27

今天這篇文章所涉及的相關觀點做法,按照我們公司某位架構師的說法就是,架構這東西沒有對錯之分,集思廣益,不一定我的做法就是對的,不一定我的做法就是最優的,小編和大家一起探討探討。

一、場景

首先我們模擬一個SDK交易鏈路的場景,對交易不是很懂的同學可以先移步至微信支付瞭解下大概業務,其實小編也不是特別精通交易,剛實習,但這不影響本文哈!一筆交易的生命週期大致可以分為這麼幾個階段,即預下單---支付---交易通知,什麼意思呢?拿微信掃碼點餐付款來說,首先微信客戶端掃碼之後會先生成一筆訂單,當預下單完成之後,客戶端會觸發SDK發起支付請求,然後使用者輸入密碼完成支付,支付之後會傳送訂單狀態通知使用者,我們最後才能知道我到底付款成功沒有。

下面是微信掃碼支付業務時序圖:

時序圖

既然業務步驟是明確的,我們首先考慮程式碼方面應該怎麼去架設會好一點,首先猜想能否定義三個生命週期方法,如下:

// 前置業務---預下單
beforeService()
// 核心業務處理----支付
doService()
// 後置業務處理----通知
afterService()
複製程式碼

但是,實際業務每個步驟往往非常複雜,也就說比如我們有可能在預下單的時候會加入優惠資訊,判斷當前使用者這筆訂單是否符合優惠,如果優惠後的金額為0,還有可能不需要任何的支付處理,直接就傳送通知給到使用者。還有一點我們業務通常還要區別是刷卡預下單還是掃碼預下單,掃碼預下單還要區分是主掃還是被掃,因為這幾種區分是實際和線下場景相匹配的,為了降低耦合,這時我們會分別提供一個介面來處理各種請求。

既然是每種場景對應一個介面,我們又可以想到,通常介面都是需要進行安全性校驗的,如MD5、RSA及SHA1各種加密,保證交易的安全性。為了和核心業務區分開來,我們通常會在前置業務中甚至在filter端就把這些校驗就完成掉,避免如果校驗出錯進到業務層去。既然每個介面都有諸如前置校驗----核心處理----後置處理的邏輯,那我們可以把上面的設想再細分一下,即:

// 前置業務---引數校驗,加密等操作
beforeService()
// 核心業務處理----預下單、支付等核心業務
doService()
// 後置業務處理----儲存訂單、儲存流水、傳送通知等後置業務
afterService()
複製程式碼

那麼程式碼層應該如何去設計呢?可否這樣,像下面的UML圖:

一起探討下web請求流程的程式碼結構設計(簡單以交易為栗子)

下面對各個元件進行講解:

  • 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物件導向的特性,擴充套件性會更好點,不過略抽象!
本文講解的比較抽象,很多地方可能沒有講的特別清楚,同學們可以評論,我們一起探討探討。

相關文章