【IT168 技术文档】
问题:太过于关注 SOA 中的会话和实体
服务的会话和实体 EJB,它对于直接连接 Java 应用程序是很重要的,例如基于 Web 客户端的 HttpServlets 或富(我们不喜欢说“胖”)客户端的 Swing 应用程序。但我们曾听说面向服务的体系结构都是与松耦合有关的。这不就意味着要尽可能地使用 SOAP 来提供使客户端和服务器应用程序能够独立运行的语言中立性和异步协议?换句话说,为什么您对 JMS 和消息驱动 Bean 讨论得不多呢?
EJB 倡导者关注的是应用程序的服务层,而对客户端介绍得很少,因为我对古谚语形式追随功能 (form follows function) 深信不疑。其原因在于,许多进行 SOA 的项目之所以失败,是因为它们没有首先建立定义服务的良好模型就开始着手实现细节。
这种倾向相当正常,因为我在 SOA 项目中接触到的大多数人都是架构师和编程人员,他们知道棘手的始终是在细节上,想要尽快解决它们。所以,一旦我们认同好服务的特征是粗粒度、无状态、可通过中介传递和适应的(请参阅 Is it ever best to use EJB components without facades in service oriented architectures?),很明显带有数据传输对象的会话 Bean 就会在实现中扮演重要角色。
但上期专栏介绍了使用实体 Bean 及其 Home 方法代替会话 Bean 的可能性,从而使人们对会话 Bean 是否真的是必需的产生了疑问。图 1 显示了同时使用的两种方法。
(图 1. 用有待使用的会话和实体 EJB 实现的服务)
图 1 显示了当使用传递过去的会话 Bean 时,纯实体方法(自 EJB 2 开始可用)如何才能有更少的组件和更短的路径长度。其中使用绿色框表示客户端,蓝色框表示各种接口和 Facade 类。橙色框是最常访问的实体 Bean。框和框之间使用双向箭头连接,箭头标有在组件之间通信所使用的协议;蓝色箭头表示相同 JVM 中的 Java 调用,红色箭头表示远程连接(在本例中使用 RMI/IIOP)。为了表示端到端的流向,对流程箭头进行编号,其中 A1-A10 表示从 Java 客户端经过会话 Bean 到实体和返回的流程,B1-B4 表示从客户端直接到实体 Bean 和返回的流程。
客户端用于检索服务接口的编程模型也很简单,虽然在图中没有表示出来。检索会话 Bean 接口需要在 JNDI 上下文中查找会话 Home 并用它创建一个会话;实体 Home 仅仅需要一次查找,其方法可以直接调用而不需要创建对 EJB Object 的引用。以下两段代码示例显示了它们之间的区别。
清单 1. 定位和调用远程会话 EJB 方法
Context initCtx = new InitialContext();
Object obj = initCtx.lookup("java:comp/env/ejb/OrderEntry");
OrderEntryHome home = (OrderEntryHome)PortableObjectRemote(obj,OrderEntryHome.class);
OrderEntry ref = home.create();
// Method must be invoked on
a session referenceCustomerData data = ref.getOpenOrderForCustomercID);
清单 2. 定位和调用等效远程实体 EJB Home 方法
Context initCtx = new InitialContext();
Object obj = initCtx.lookup("java:comp/env/Customer");
CustomerHome ref = (CustomerHome)
PortableObjectRemote(obj, CustomerHome.class);
// Note how the method is invoked directly
CustomerData data = ref.getOpenOrder(cID);
图 1 和相关的代码示例显示了 Java EE 编程模型的真正好处,不管您是否选择使用实体 Home 方法。编程模型逐渐改进并提供向后兼容。简而言之,您的非常好的实践能够得以发展而不必强制更改现有的应用程序。另外,JNDI 上下文提供了一个不容忽视的松耦合相关方面:实现独立性。
(图 2. 部署到本地 Web 应用程序和远程富客户端的服务)
(图 3. 用提供异步客户端通道的 MDB 部署的服务)
会话或实体 EJB 组件的远程接口提供了位置独立性。使用远程接口使得在相同的 JVM 中部署客户端应用程序和服务组件成为可能,其中它对系统(例如使用 HttpServlets 的 Web 应用程序)的响应时间、吞吐量和可维护性目标有意义。图 2 显示的正是这样的配置,其中企业质量应用服务器(如 IBM® WebSphere® Application Server)在联合部署了客户端和服务组件时“短路”远程接口以使用 Java 并按引用传递(不管服务实现为实体 Home 方法还是会话 Bean)。流程 A1-A6 显示了与服务组件联合部署的 HttpServlet 的使用。流程 B1-B4 显示了它如何被远程富客户端 Java EE 应用程序重用。
(图 2. 部署到本地 Web 应用程序和远程富客户端的服务)
但这听起来像是您已确定松耦合的最重要方面是语言中立性和异步操作。并且您认为需要异步操作会使得必须在服务器端使用消息驱动 Bean (MDB) 和 JMS。在实现 MDB 时,许多编程人员使用的方法是通过其远程接口调用表示服务的 EJB 组件,以使上述位置独立性最大化。然后,不管服务实现是远程还是本地部署,MDB 的目的都是作为简单的适配器,将 MQ 层连接到承载服务的 EJB 容器。JMS 可能由异步客户端应用程序(如果它是 Java)或 MDB 使用(通常用于应答队列)。图 3 显示了服务实现的另一个输入通道的这一配置。
(图 3. 用提供异步客户端通道的 MDB 部署的服务)
流程 C1-C2 与 D1-D6 分别显示,以阐释客户端和服务器流程的独立性。C2 和 D6 只是对 Writer 是否写入消息进行“确认”,并不意味着等待。清单 3 显示了 MDB 的典型结构,该结构应该有助于阐明它必须做的处理:
清单 3. 典型的消息驱动 Bean 实现
public class OrderSubmitMsgHandler implements MessageDrivenBean {
private transient MessageDrivenContext context = null;
// Interface supported by either the session Bean or entity Home
private transient OrderEntry ref;
public void ejbCreate() {
// Cache the home according to either snippet one or two
}
public void ejbRemove() {}
public void setMessageDrivenContext(MessageDrivenContext mdc) {
context = mdc;
}
// Message acknowledge and database update participate
public void onMessage(Message inMessage) {
// Parse the customer from the message
try {
ref.submit(customer);
}
catch (CustomerDoesNotExist e) {
// Send JMS message to reply queue from exception
}
catch (OrderNotOpen e) {
// Send JMS message to reply queue from exception
}
catch (OrderHasNoItemsException e) {
// Send JMS message to reply queue from exception
}
}
}