交易中台系统设计与思考

前言

将近两年的时间,我一直在某企业做中台系统的研发,最近可能这段工作经历可能要结束。本文也算是这段经历的回顾与反思。

系统架构

在这里主要想说的是服务接入层,在我们目前的系统架构中并没有服务接入层。但是在我日后的反思中,觉得服务接入层的存在还是很有必要的。

服务接入层的作用

  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:文中业务示例也为参考京东业务示例,示例代码也是对实际代码的修改