­

交易中台系統設計與思考

前言

將近兩年的時間,我一直在某企業做中台系統的研發,最近可能這段工作經歷可能要結束。本文也算是這段經歷的回顧與反思。

系統架構

在這裡主要想說的是服務接入層,在我們目前的系統架構中並沒有服務接入層。但是在我日後的反思中,覺得服務接入層的存在還是很有必要的。

服務接入層的作用

  1. 防腐層作用。因為業務中台要服務於企業內多條業務線,日常開發中應對不同的業務需求,我們常常在底層服務中的添加許多轉換、判讀邏輯,而引入了服務接入層我們可以把這些代碼放到服務接入層。
  2. 業務隔離。企業內每條業務線都有其業務的獨特場景,有的對於服務的調用比較平緩,有的業務可能在某一時間集中爆發。作為中台我們不希望某一個業務的某個場景或者bug導致所有業務的不可用,所以可以在接入層針對業務來進行服務的限流(主要方案是Sentinal的熱點參數限流)。
  3. 便於對外輸出能力的管理。在後續的開發中開發人員只需要對於接入層的接口進行接口文檔的編寫以及文檔的維護即可。其次整個中台的開發人員只要了解接入層對外輸出了那些能力就可以對於中台的整體能力有一認知,不需要面對每個底層服務繁多的接口。

交易中台要點

在建設交易中台,或者說是業務中台的主要的設計其實有兩點:

  1. 如何提供合理的接入流程提供給業務方。讓業務方完整的實現自己的業務同時能夠快速的接入
  2. 中台業務系統如何應對不同業務對於業務流程上的差異

接入流程

主要的接入流程入下圖:


用戶在前台頁面購買商品。這時調用業務方接口,業務方調用中台的接口提交購買的商品等信息創建交易單,獲取交易單號並與本身的業務數據關聯,然後根據交易單號喚起通用的支付組件。
喚起支付組件到支付這段的邏輯業務方無需關心,支付組件直接與交易中台交互。
支付成功後,交易中台處理訂單邏輯,處理完成後回調業務方,這時業務方根據回調的交易單號處理本身的業務邏輯。

接入流程在設計與演進時其實也是遇到了一些問題:
起初在第一步業務方創建的是不可見狀態的訂單數據,這也造成了交易系統中存在大量的無效的訂單數據,後來優化成交易單,訂單號復用交易單號,這樣對於業務方其實是透明無影響的。
還有就是處理支付回調並回調業務方這一步驟,起初是同步的調用,這也造成了訂單狀態的流轉反向的依賴業務方的系統,當業務方系統出現故障時,訂單狀態就無法正常的流轉。

業務差異

下面以兩個業務為例,我只描述大概的流程,真實上的業務流程可能更複雜。

在消除業務差異的時候,首先要分析各個業務的流程,找出最複雜的業務當做樣本,分析哪些是通用邏輯,哪些是存在差異的邏輯。
例子中商城的流程可能是最複雜的,會員的流程是更簡單的。根據上圖我們也可以以持久化訂單數據為節點劃分為兩個流程上的差異,
持久化訂單前的前置處理,持久化訂單後的後置處理。
我們對於邏輯抽離形成職責單一的組件,並對前置處理以及後置處理根據業務創建不同的組件調用鏈。
下面是示例代碼:
我們首先定義組件

public interface Processor<T, R> {      /**       * 執行內容 ,中斷拋出{@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<T, R> implements Processor<T, R> {      private AbstractProcessor<T, R> next = null;        public AbstractProcessor<T, R> getNext() {          return next;      }        @Override      public boolean check(T context, R result) {          return true;      }        public void setNext(AbstractProcessor<T, R> 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);                  }              }          }      }  }  

然後定義構建組件鏈的抽象類

public abstract class AbstractProcessorBuilder<T, R> {      @Autowired      protected AutowireCapableBeanFactory autowireCapableBeanFactory;      protected AbstractProcessor<T, R> instance;        @PostConstruct      public void init() {          initProcessor();      }        public abstract void initProcessor();        public AbstractProcessor<T, R> build() {          return instance;      }        public void addLast(AbstractProcessor<T, R> processor) {          if (instance == null) {              instance = autowired(processor);              return;          }          AbstractProcessor<T, R> next = instance;          while (next.getNext() != null) {              next = next.getNext();          }          next.setNext(autowired(processor));        }        protected AbstractProcessor<T, R> autowired(AbstractProcessor<T, R> processor) {          autowireCapableBeanFactory.autowireBean(processor);          return processor;      }  }  

上面兩個業務的前置調用鏈為

@Component  public class StoreSubmitPreProcessorBuilder extends AbstractProcessorBuilder<SubmitOrderContext, ResultDTO<SubmitOrderResult>> {      @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<SubmitOrderContext, ResultDTO<SubmitOrderResult>> {      @Override      public void initProcessor() {          addLast(new QuerySkuProcessor());          addLast(new CheckSkuProcessor());          addLast(new CalPromotionProcessor());      }  }  

簡化後的提交訂單代碼為:

        getSubmitPreProcessorBuilder(bizType).build().invoke(context, ret);           if (!ret.isOk()) {                  return ret;           }           //.....  

通過這種組件化的方式,我們就可以根據不同的業務獲取不同的ProcessorBuilder 來應對流程上的差異。

中台的思考

中台提供的不只是接口與組件這麼簡單,例如交易中台,還需要提供訂單的管理後台,訂單數據的可視化,通過不斷的強化中台的能力,來讓前台業務更專註於本身業務。

中台提供的能力要不斷的推廣,當單一業務提出的需求有意義,在實現後可以以其為成功案例,推廣到其他業務,讓其他業務享受到中台建設的紅利,只有不斷的推廣,業務不斷的反饋,中台的能力才會越強。

中台不僅是業務架構,也是一種組織架構,只有組織架構獨立才能保證不會對某些業務產生資源上的傾斜。

中台的搭建要根據企業的實際情況,明確景願,解決企業相應的問題。

Tips:文中業務示例也為參考京東業務示例,示例代碼也是對實際代碼的修改