交易中臺系統設計與思考
阿新 • • 發佈:2020-04-05
### 前言
將近兩年的時間,我一直在某企業做中臺系統的研發,最近可能這段工作經歷可能要結束。本文也算是這段經歷的回顧與反思。
### 系統架構
![](https://img2020.cnblogs.com/blog/1071030/202004/1071030-20200404211117494-1034790843.png)
在這裡主要想說的是服務接入層,在我們目前的系統架構中並沒有服務接入層。但是在我日後的反思中,覺得服務接入層的存在還是很有必要的。
#### 服務接入層的作用
1. 防腐層作用。因為業務中臺要服務於企業內多條業務線,日常開發中應對不同的業務需求,我們常常在底層服務中的新增許多轉換、判讀邏輯,而引入了服務接入層我們可以把這些程式碼放到服務接入層。
2. 業務隔離。企業內每條業務線都有其業務的獨特場景,有的對於服務的呼叫比較平緩,有的業務可能在某一時間集中爆發。作為中颱我們不希望某一個業務的某個場景或者bug導致所有業務的不可用,所以可以在接入層針對業務來進行服務的限流(主要方案是Sentinal的熱點引數限流)。
3. 便於對外輸出能力的管理。在後續的開發中開發人員只需要對於接入層的介面進行介面文件的編寫以及文件的維護即可。其次整個中臺的開發人員只要瞭解接入層對外輸出了那些能力就可以對於中臺的整體能力有一認知,不需要面對每個底層服務繁多的介面。
### 交易中臺要點
在建設交易中臺,或者說是業務中臺的主要的設計其實有兩點:
1. 如何提供合理的接入流程提供給業務方。讓業務方完整的實現自己的業務同時能夠快速的接入
2. 中臺業務系統如何應對不同業務對於業務流程上的差異
#### 接入流程
主要的接入流程入下圖:
![](https://img2020.cnblogs.com/blog/1071030/202004/1071030-20200404211328178-1651475531.png)
![](https://img2020.cnblogs.com/blog/1071030/202004/1071030-20200404211336960-2145090764.png)
![](https://img2020.cnblogs.com/blog/1071030/202004/1071030-20200404211344678-1215337263.png)
使用者在前臺頁面購買商品。這時呼叫業務方介面,業務方呼叫中臺的介面提交購買的商品等資訊建立交易單,獲取交易單號並與本身的業務資料關聯,然後根據交易單號喚起通用的支付元件。
喚起支付元件到支付這段的邏輯業務方無需關心,支付元件直接與交易中臺互動。
支付成功後,交易中臺處理訂單邏輯,處理完成後回撥業務方,這時業務方根據回撥的交易單號處理本身的業務邏輯。
接入流程在設計與演進時其實也是遇到了一些問題:
起初在第一步業務方建立的是不可見狀態的訂單資料,這也造成了交易系統中存在大量的無效的訂單資料,後來優化成交易單,訂單號複用交易單號,這樣對於業務方其實是透明無影響的。
還有就是處理支付回撥並回調業務方這一步驟,起初是同步的呼叫,這也造成了訂單狀態的流轉反向的依賴業務方的系統,當業務方系統出現故障時,訂單狀態就無法正常的流轉。
#### 業務差異
下面以兩個業務為例,我只描述大概的流程,真實上的業務流程可能更復雜。
![](https://img2020.cnblogs.com/blog/1071030/202004/1071030-20200404211614299-535604241.png)
在消除業務差異的時候,首先要分析各個業務的流程,找出最複雜的業務當做樣本,分析哪些是通用邏輯,哪些是存在差異的邏輯。
例子中商城的流程可能是最複雜的,會員的流程是更簡單的。根據上圖我們也可以以持久化訂單資料為節點劃分為兩個流程上的差異,
持久化訂單前的前置處理,持久化訂單後的後置處理。
我們對於邏輯抽離形成職責單一的元件,並對前置處理以及後置處理根據業務建立不同的元件呼叫鏈。
下面是示例程式碼:
我們首先定義元件
```java
public interface Processor {
/**
* 執行內容 ,中斷丟擲{@link com.xxx.xxx.infrastructure.exception.ProcessorInterruptedException}
*
* @param context
* @param result
*/
void process(T context, R result);
/**
* 觸發呼叫下一個 Processor
*
* @param context
* @param result
*/
void fireNext(T context, R result);
/**
* 檢查是否執行該 Processor
*
* @param context
* @param result
* @return
*/
boolean check(T context, R result);
}
public abstract class AbstractProcessor implements Processor {
private AbstractProcessor next = null;
public AbstractProcessor getNext() {
return next;
}
@Override
public boolean check(T context, R result) {
return true;
}
public void setNext(AbstractProcessor next) {
this.next = next;
}
public void invoke(T context, R result) {
try {
process(context, result);
fireNext(context, result);
} catch (ProcessorInterruptedException ex) {
return;
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
throw ex;
}
}
@Override
public void fireNext(T context, R result) {
if (next != null) {
if (check(context, result)) {
next.invoke(context, result);
} else {
if (next.getNext() != null) {
next.getNext().invoke(context, result);
}
}
}
}
}
```
然後定義構建元件鏈的抽象類
```java
public abstract class AbstractProcessorBuilder {
@Autowired
protected AutowireCapableBeanFactory autowireCapableBeanFactory;
protected AbstractProcessor instance;
@PostConstruct
public void init() {
initProcessor();
}
public abstract void initProcessor();
public AbstractProcessor build() {
return instance;
}
public void addLast(AbstractProcessor processor) {
if (instance == null) {
instance = autowired(processor);
return;
}
AbstractProcessor next = instance;
while (next.getNext() != null) {
next = next.getNext();
}
next.setNext(autowired(processor));
}
protected AbstractProcessor autowired(AbstractProcessor processor) {
autowireCapableBeanFactory.autowireBean(processor);
return processor;
}
}
```
上面兩個業務的前置呼叫鏈為
```java
@Component
public class StoreSubmitPreProcessorBuilder extends AbstractProcessorBuilder> {
@Override
public void initProcessor() {
addLast(new CheckSubmitOrderNumProcessor());
addLast(new CheckItemBuyNumLimitProcessor());
addLast(new PaddingAddressProcessor());
addLast(new CheckDeliveryLimitProcessor());
addLast(new QuerySkuProcessor());
addLast(new CheckSkuProcessor());
addLast(new CalPromotionProcessor());
}
}
@Component
public class PrimeSubmitPreProcessorBuilder extends AbstractProcessorBuilder> {
@Override
public void initProcessor() {
addLast(new QuerySkuProcessor());
addLast(new CheckSkuProcessor());
addLast(new CalPromotionProcessor());
}
}
```
簡化後的提交訂單程式碼為:
```java
getSubmitPreProcessorBuilder(bizType).build().invoke(context, ret);
if (!ret.isOk()) {
return ret;
}
//.....
```
通過這種元件化的方式,我們就可以根據不同的業務獲取不同的ProcessorBuilder 來應對流程上的差異。
### 中臺的思考
>> 中臺提供的不只是介面與元件這麼簡單,例如交易中臺,還需要提供訂單的管理後臺,訂單資料的視覺化,通過不斷的強化中臺的能力,來讓前臺業務更專注於本身業務。
>> 中臺提供的能力要不斷的推廣,當單一業務提出的需求有意義,在實現後可以以其為成功案例,推廣到其他業務,讓其他業務享受到中臺建設的紅利,只有不斷的推廣,業務不斷的反饋,中臺的能力才會越強。
>> 中臺不僅是業務架構,也是一種組織架構,只有組織架構獨立才能保證不會對某些業務產生資源上的傾斜。
>> 中臺的搭建要根據企業的實際情況,明確景願,解決企業相應的問題。
Tips:文中業務示例也為參考京東業務示例,示例程式碼也是對實際程式碼