欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

面向业务接口的业务事件交付

程序员文章站 2022-07-03 20:26:23
...

      在轻量流程引擎接口设计中提到触发业务事件需要调用事件的deliver方法来触发并交付业务事件,流程引擎最终会调用业务服务组件的事件处理方法,例如对买家确认收货事件,会调用收货组件的确认收货方法。采用这种显式交付业务事件的方式,开发人员需要new一个对应的业务事件对象,然后调用该事件的deliver。这种方式不是很友好,在没有采用流程引擎之前,开发人员直接调用收货组件接口来进行确认收货。采用流程引擎后,开发人员希望仍希望采用这种面向业务组件接口的编程方式,但流程引擎是采用事件驱动的,流程引擎根据事件来调用对应的业务服务组件,也就是不会让开发人员来直接调用业务服务组件。 因此为了沿用先前的调用方式,需要有一种机制来将完成业务方法调用到业务事件的转换(业务方法->业务事件),通过这种转换来达到隐式交付业务事件的目的。为了完成这种转换,需要增加一个间接层,这里采用代理模式(Proxy设计模式)来实现间接层,完成方法调用到业务事件的转换,更优雅的方式是直接使用spring aop。这里我们自己用Java动态代理而不是直接用AOP。

       在转换时,需要建立方法调用参数和业务事件属性之间的对应关系(映射关系),在技术上在采用annotation来注解方法,建立方法参数和事件属性之间的对应关系。

        先简要介绍下Java动态代理。

       Java动态代理

     代理模式是一种常用的设计模式,其目的是为了控制对某个真实对象的访问。代理对象和真实对象一般实现了相同的接口。客户端在调用代理对象的方法时,代理对象负责将调用委托给真实对象,在委托的前后,代理可以进行一些控制或预处理工作。

      Java动态代理的核心是java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口,动态代理是实现AOP的一种方式。注意此Proxy不是代理模式中的代理类,而是生成代理对象的工厂。客户端在调用代理对象时,需要先调用Proxy类的newProxyInstance静态方法生成代理对象实例,在调用该方法时,需要传入真实对象实现的接口和InvocationHandler的实现类对象,然后newProxyInstance会返回一个实现了这些接口的代理对象。由于是在JVM虚拟机运行过程中生成代理类,不需要事先定义代理类的类文件,因此叫动态代理。

      InvocationHandler是调用处理器接口。无论客户端调用代理对象的哪个接口方法,最终均会调用到invoke方法上,这是动态代理机制内部实现的。在该接口实现类的invoke方法中集中统一处理在代理对象上的方法调用,实现对真实对象的委托访问。 

   InvocationHandler接口的invoke方法签名如下:

   /* proxy就是Proxy类的newProxyInstance工厂方法返回的代理对象实例,method是客户端调用该代理对象的方法表示,args就是方法的参数。

  */     

  public Object invoke(Object proxy, Method method,Object[] args);
 

   Proxy类的newProxyInstance方法签名如下:

   /*loader为类装载器,用于加载动态生成的代理类、 interfaces为接口数组、

      h为 InvocationHandler接口实现。

      返回创建的代理对象实例。

   */
   static Object newProxyInstance(ClassLoader loader, Class[] interfaces,
    InvocationHandler h);

 

   BusinessEventAttr annotation

   BusinessEventAttr为业务事件annotation注解,作用于业务组件接口方法(前文中的南向接口)上,建立方法参数和事件属性之间的对应关系。建立对应关系后,就可从业务组件接口方法参数中提取事件属性。    下面是用于买家确认收货业务组件接口的注解:

    interface BuyerConfirmReceptionService{
       @BusinessEventAttr(businessKey="$inputScope[0]",

                                          roleId="$inputScope[1].id",eventType="buyerConfirmReceptionEvent")
       ConfirmReceptionResult confirm(Integer tradeId,RoleInfo roleInfo);
    }

    注解的属性通常是常量或表达式。

    上面的注解属性businessKey=”$inputScope[0]”,表示businessKey属性值是confirm方法的第一个输入参数tradeId(交易ID,通常交易ID和订单ID相同)。businessKey代表业务ID,业务ID和流程实例ID一一对应,对应关系可由流程引擎维护,此处之所以不用流程实例ID是为了避免业务组件和流程引擎耦合。

     roleId="$inputScope[1].id"表示角色id是confirm方法的第二个参数对象roleInfo的id属性;eventType为事件类型。

   BusinessEventAttr其他属性,如流程名称、是否同步事件等就不列出了。

    采用注解的方式表达接口方法中的事件信息后,南向接口的定义更为*,其事件处理方法中的参数可以有多个,例如上面的confirm方法有两个参数。

     

    业务对象代理工厂类

     客户端使用业务服务代理工厂类来生成业务服务代理。为了方便客户端编程,该代理工厂类对java动态代理api进行了封装,简化客户端调用。该工厂类定义如下:

     public class BusinessServiceProxyFactory {
     @SuppressWarnings("unchecked")
     public static <T> T getProxy(Class<T> intf) {
         return (T) Proxy.newProxyInstance(BusinessServiceProxyFactory.class.getClassLoader(),new Class[]{intf},
                    new InvocationHandler() {
                        public Object invoke(Object proxy, Method method,
                                             Object[] args) throws Throwable {

                            //获得注解实例
                            BusinessEventAttr eventAttr = method.getAnnotation(BusinessEventAttr.class);
                            //完成业务方法调用到业务事件的转换,根据eventAttr和args调用populateEvent拼装                        //业务事件,完成方法调用到业务事件的转换。
                            BusinessEvent bzEvent = populateEvent(eventAttr,args);
                            //获取流程引擎
                            FlowEngine flowEngine =  FlowServicesLocator.getFlowEngine();
                           /*调用startFlow驱动流程,提交业务事件。流程引擎会根据bzEvent中的业务ID找到对应的流程实例ID,然后根据该流程实例所处的当前节点和事件类型,调用该节点上配置的南向接口上的事件

                              处理方法(即业务服务层的业务服务组件方法),调用完成后,进行流程流转,返回调用

                              结果。
                            */
                            return flowEngine.startFlow(bzEvent);
                        } // end invoke
                    }); //end new InvocationHandler()
        } 
   } 

        BusinessServiceProxyFactory类只有一个方法getProxy(Class<T> intf),传入业务服务组件接口,返回

   一个实现了该接口的动态代理实例,对返回采用了范型类型,这样客户端调用时不需要cast。getProxy其实只包含一行代码,就是调用Proxy.newProxyInstance方法,newProxyInstance方法中的 InvocationHandler的实例为匿名类。

       这个BusinessServiceProxyFactory也可封装为spring 中的factorybean,它返回业务服务实例(是一个代理)。

        在invoke方法中完成业务方法调用到业务事件的转换,然后启动流程(流程启动后,会调用事件处理器,最后进行流程流转)。

        在轻量流程引擎接口设计中提到的BusinessEvent需要将BusinessDataDto businessData属性改为    Object args[ ];

     

      客户端调用

    以某交易流程中的买家确认收货为例说明客户端的调用。买家签收物流送来的宝贝后,打开该订单web页面,点击页面中的"确认收货"按钮。系统将调用Action控制类的execute方法或doXXX方法,在execute该方法中调用买家确认收货服务的confirm方法,execute方法示例如下:

    //todo进行一些权限验证或数据组装工作

    ...

    //调用业务服务代理工厂的getProxy工厂方法得到买家确认收货服务实例,该实例是一个动态产生的代理   

    BuyerConfirmReceptionService    confirmService =  

                    BusinessServiceProxyFactory.getProxy(BuyerConfirmReceptionService.class);

 

     /*和未采用流程引擎之前一样,仍调用BuyerConfirmReceptionService业务接口的confirm方法,  confirmService不真正指向业务服务层的确认收货组件,它是一个代理,作为面具来伪装,让开发人员觉得是调用确认收货服务。对confirm方法的调用会转到上面的 InvocationHandler匿名类的invoke方法中,从而将confirm方法调用转换为确认收货的业务事件。

       在返回的result结果中包含流程实例的最新流转状态,也可提供一个接口来专门查询流程实例状态。

     */

    ConfirmReceptionResult  result = confirmService.confirm(tradeId,roleInfo);

    ...

    

    

     如上节业务对象代理工厂中所述,在InvocationHandler匿名类的invoke方法中调用flowEngine.startFlow时,流程引擎将会调用业务逻辑层的买家确认收货组件的confirm方法(流程定义文件中的事件处理器方法)。业务服务层的买家确认收货组件实现BuyerConfirmReceptionService接口,在该业务服务层组件的confirm方法中,开发人员只需要考虑如何处理买家确认收货逻辑,不需要关心流程流转,也不需要调用流程引擎api;在action类中也不需考虑流程流转和调用流程引擎api,也就是流程引擎api不会侵入其他层的代码中,实现了业务逻辑层和流程层的完美解藕和隔离。

    

    流程引擎和流程定义文件

     采用DSL来定义流程(包含节点定义、事件处理器、流转条件),流程DSL对流程api进行封装(创建流程实例api、设置修改流程变量api等)。

     流程定义文件除了包含节点定义、事件处理器、流转条件外,还可以在其中定义流程脚本(使用Java或groovy动态语言编写的代码片段):修改或设置流程变量的脚本、action脚本、流出操作、流入操作等。

     通过流程DSL和这些流程脚本,把流程相关的逻辑全部封装在流程层,保证了这些流程逻辑不会侵入到业务服务层,业务服务层也就不需要调用这些流程api了。例如在开始节点中处理下单事件的业务组件(生成订单组件)不需要调用流程api来创建流程实例,创建流程实例是由流程层负责调用流程api来创建的,生成订单的组件不关心流程实例的创建。

     流程引擎提供了对多种流程模式的支持,如and-join模式。