EJB 倡导者现在将回到您提到过的语言中立性问题。看起来您已经将语言中立性的要求与异步处理的要求紧密联系起来了。没有理由不能将这些考虑分开;解析 SOAP 消息和用它调用会话 Bean 的能力应该与该消息的处理是异步(通过 MQ 或者传递 JMS 等效消息的另一个协议)还是同步(例如通过 HTTP,甚至 IIOP)无关。事实上,Java EE 应用程序上的一些早期“发明”的 Web 服务使用 HttpServlet 来解析通过 HTTP 传递的 XML 消息。这种方法最终发展为 SOAP/HTTP。图 4 显示可以在 EJB 组件所实现的服务之上提供的另一个路径。
(图 4. 将语言中立性和异步性的考虑分开)
Web Service Servlet 和 Message Driven Bean 可以共享对来自从消息串或 HttpServletRequest 提取的流的数据传输对象进行解析的代码。类似地,响应或应答可以共享从数据传输对象(它可以是 Exception 的一个实例)生成流的代码。
希望这有助于您理解 Java EE 组件的布置,所有这些都将提供某种对面向服务的体系结构很基本的松耦合形式。
我以前不认为像 JNDI 这样的服务和远程接口会提供松耦合。我还可以了解如何将 SOAP 和 MQ 的概念“紧耦合”(按照您提到的方法),以及应该如何尽可能将它们分开。所以将解析和生成 SOAP 消息看作由 Web 服务 Servlet 和 MDB 重用的服务本身是很有意义的。
SOA 看似非常简单:每个服务都公开一个 SOAP/MQ 接口。现在看起来有好多选择要考虑,并且既然将 SOAP 消息的解析和生成看作服务,那么为什么不想建立一个独立的会话 Bean 来封装它们以便如图中所显示的那样进行重用呢?
开发一个业务流程模型,显示重要域对象的生命周期中的主要里程碑:
我们使用状态转换关系图来描述这一模型,其中状态代表里程碑,转换代表促使变成该状态的事件。转换可以看作应用程序所提供的服务(有关示例请参见图 5)。
(图 5. 显示订单生命周期的状态转换关系图示例)
我们使用 UML "Actor" 符号来扩展状态转换关系图,以显示当处于该状态时对象的所有者。状态的所有者负责发起转换,从而驱动应用程序的安全模型(也请参见图 5)。
对于业务流程中的每个状态,我们也对每个需要支持转换动作的域对象的属性和它们之间的关系进行建模。我们通常使用的符号是 UML 类关系图(有关示例,请参见图 6)。为每个状态建立独立的关系图可以让我们随时对变化的对象“形状”进行建模。这些关系图驱动持久性数据。
(图 6. 显示 open 订单“形状”的类关系图示例)
开发一个用户界面流程模型,显示在典型“会话”期间来自业务流程的给定参与者如何与系统相交互。与业务流程模型一样,我们也使用状态转换关系图,其中状态代表屏幕和对话框,而转换代表实际的 UI 事件,如选择菜单项和按下按钮。根据业务流程转换指定与转换相关的动作。
还是和业务流程模型一样,我们构建一个类关系图来显示必须在每个状态中可见的数据以支持各种选择。这意味着该数据必须可以从用户输入派生,并以自顶向下的方式驱动服务上的读取操作。
与业务流程模型不同,我们不需要用 Actor 符号扩展状态关系图,因为关系图本身可以视为单个用户角色的“生命中的一天 (day in the life)”。
这种综合方法可以确保:
·将正确的操作分组到一个服务(与业务流程生命周期中的一个状态相关联的所有转换和 read 方法)。
·每个服务的用途很好理解(根据相关业务对象来指定动作)。
·调用服务需要的前置条件和会产生的后置条件是相通的(当前状态和可选的监护、以及下一状态都是通过转换指定的)。
·标识了负责调用服务的用户角色(参与者与每个状态相关联)。
不管是否实现,方法签名都不会提供此信息,但此信息对于好的 SOA 来说是很关键的。否则,编程人员将陷入另一种倾向:一旦有怀疑,就再次构建。因为 SOA 的开发成本比较高(要为图 4 所示的完全松耦合提供各种中介器和适配器),所以这种反对重用的倾向可能会导致获得好处最少。
对您关于简单性的答复中提出的问题的回答是,此信息一点都没有强制您以某种方式公开接口,EJB 倡导者知道简单只存在于旁观者眼中。如果您想要使服务开发人员不必进行选择,只需为每个服务提供图 5 中所示的所有“蓝色”组件即可:
远程服务接口,用于提供同步 Java EE 客户端访问服务操作的位置独立性。
与每个操作相关联的 MDB,通过遵循 JMS 的 MQ 实现来提供异步非 Java 客户端访问。您可以选择编写此 MDB 或不同的 MDB 以期待 JMS 客户端的 Java 消息(从而避免 HTTP 解析开销)。
与每个操作相关联的 Web 服务 Servlet,用于提供同步 SOAP over HTTP 客户端访问。
因为有人会担心这种方法将生成大量未使用的组件,所以另一种简单的方法是,应用 EJB 倡导者喜欢的内容来调用面向客户端的体系结构 (COA);以对客户端最自然的方式给它们提供使用服务正好需要的内容。
这种 COA 方法需要考虑业务流程和 UI 模型的细节,以便为每种方法挑选最可能的候选者。例如:
业务流程生命周期中的状态之间的转换很可能是异步服务的候选者,因为它们将是相关域对象的“所有者”变体。例如,在 open 订单应用程序(我们在上述示例中调用此 OrderEntry)中,submit 方法可以简单地将订单的状态更改为“submitted”,并发送一条 JMS 消息来将其复制到已提交订单应用程序中(我们将这称为一次 OrderFulfillment)。
状态间的转换通常应该是同步的,因为所有者没有改变。举个例子来说明为什么这些操作不应该是异步的,设想一下,如果您进入一位书商的 Web 应用程序,还必须轮询或等待发布-订阅事件来显示目录或将物品添加到购物车中!对于想要通过异步通道使用伪同步方式的读者,请查阅 Bobby Woolf 关于设计消息传递系统的书籍。
只有对于没有相关的非 Java 客户端和服务的集成场景,才提供 SOAP over HTTP 或 MQ。
使用 COA 方法,可以“准时 (just-in-time)”开发组件,这就是 EJB 倡导者喜欢推荐它的原因。
对您的问题(为什么不将所有的东西都看作是服务,甚至是与中介器和适配器相关联的转换)的回答的最后一点是:简单的回答超出了“过犹不及”这一事实。在开发 SOA 或 COA Java EE 应用程序时,最好将服务视为业务流程模型上的操作。服务与应用程序的功能需求有关。中介和相关联的转换与非功能需求有关,例如可靠性、可用性、有效性、可维护性和可移植性。如果您将中介或相关联的转换视作第一个类服务,则最终将使应用程序的真实用途变得模糊。
我知道这其中有很多方面的内容需要理解,所以当您应用这些方法时,遇到相关细节问题不要嫌麻烦,尽管与我联系。