3. 架构和设计
我们定义架构将从解决头4个需求开始:
·定义把流程装配为一个活动集合的机制
·定义各个活动
·定义共享数据的占位符
·定义在流程范围内的这些活动协调执行的机制
活动是一个无状态工作者,它应该接收一个包含一些数据(上下文)的token。活动应该通过读写来操作这一共享的数据token,同时执行由这个活动所定义的业务逻辑。共享的数据token定义了一个流程的执行上下文。
为了坚持前面我们制定的轻量级原则,没有理由不把我们的活动定义为实现了POJI(Plan Old Java Interfaces)的POJO(Plain Old Java Objects)。
这里是Activity接口的定义,它只有一个process(Object obj)方法,其输入参数代表了一个共享数据(上下文)的占位符。
public void process (Object data);
}
一个共享数据的占位符可以是结构化或非结构化(比如Map)对象。这完全取决于你。为简单起见,通常我们的Activity接口把它定义为java.lang.Object,但是在真实环境中,它或许被表达为某种结构化对象类型,流程的所有参与者都知道这一结构。
流程
因为流程是活动的集合,我们需要定出装配及执行这种集合的机制。
有许多方式可以达成这一目的。其中之一是把所有活动插入到某种类型的有序集合中,按其预先定义好的顺序迭代它调用每个活动。这种方法的可配置性和可扩展性明显很差,因为流程控制和执行的所有方面(aspects)都将被硬编码。
我们还可以用某种非传统的方式来考虑流程:假定流程是一个行为较少的抽象,并没有具体实现。可是,从一个活动过滤器(Activity Filters)链中过滤这种抽象,可以定义这个流程的性质、状态和行为。
假定我们有一个叫做GenericProcess的类,其定义了process(..)方法:
public void process(Object obj){
System.out.println("executing process");
}
}
如果我们直接传递输入对象来调用process(..)方法,不会发生什么事情,因为process(..)方法没有做什么事情,上下文的状态将保持非修改状态。但是如果我们试图在调用process(..)之前找到一种方法来引入活动,并让该活动修改这个输入对象,那么process(..)方法仍将保持不变,但是因为输入对象是由活动预先处理过的,流程上下文的整体结果将会改变。
这种把拦截过滤器(Intercepting Filter)应用到目标资源的技术在拦截过滤模式中有很好的文档记录,在今天的企业级应用中被广泛使用。典型的例子是Servlet过滤器(Filters)。
拦截过滤器模式用一个过滤器封装了已有应用资源,这个过滤器拦截了请求的接收及响应的传递。一个拦截过滤器可以前置处理(pre-process)或重定向(redirect)应用请求,而且可以后置处理(post-process)或替换(replace)应用响应的内容。拦截过滤器还可以改变顺序,无需改变源代码就可以把一个分离的、可声明部署的服务链加入到现有资源——http://java.sun.com/blueprints/patterns/InterceptingFilter.html。
过去,这一架构通常用来解决非功能关注点(concerns),比如安全、事务等等……但是你可以清楚地看到,通过拦截过滤器(代表各个活动)链来装配类流程结构,同样的方法也可以很容易被应用来解决应用的功能特性。
下图显示了对于一个流程(Process)的调用如何被过滤器链拦截,其中每个过滤器都与流程的某个活动(Activity)相关联,这样,实际的目标流程组件没有什么事情可干,使之成为一个空的、可重用的目标对象。改变该过滤器链你就得到了自己一个不同的流程。
唯一留下来要做的事情就是看看有没有一个框架,可以帮助我们以优雅的方式装配类似这样的东西。
基于代理的(Proxy-based)Spring AOP看起来是理想的选择,因为它给我们提供了简单的结构和最重要的东西——执行机制。它将使我们能够定义一个有序拦截过滤器集合,来代表一个给定流程的各个活动。输入的流程请求将会由这些过滤器所代理,用活动过滤器(Activity Filters)中的行为实现来装饰流程。
这样留给我们的只有下面唯一的需求:
·定义转移规则和执行决策机制,根据由活动注册的事实执行转移规则