另外,我们必须强调,使用 EJB 和 Web 服务进行分布式的、基于组件的开发使得测试单个组件变得非常必要。如果没有“GUI”需要测试,您就必须进行低级(lower-level)测试。最好以这种方式开始测试,省得当您将分布式的组件或 Web 服务作为您的应用程序的一部分时,您不得不花费心思重新进行测试。
总之,通过使用自动的单元测试,能够很快地发现系统的缺陷,并且也易于发现这些缺陷,使得测试工作变得更加系统化,因此整体的质量也得以提高。
4. 按照规范来进行开发,而不是按照应用服务器来进行开发。
要将规范熟记于心,如果要背离规范,需经过慎密的考虑后才可以这样做。这是因为当您背离规则的时候,您所做的事情往往并不是您应该做的事情。
当您要背离 Java EE 允许您做的事情的时候,这很容易让使您遭受不幸。我们发现有一些开发人员钻研一些 Java EE 允许之外的东西,他们认为这样做可以“稍微”改善 Java EE 的性能,而他们最终只会发现这样做会引起严重的性能问题,或者在以后的移植(从一个厂商到另一个厂商,或者是更常见的从一个版本到另一个版本)中会出现问题。实际上,这种移植问题是如此严重,以致 [Beaton] 将此原则称为移植工作的基本最佳实践。
现在有好几个地方如果不直接使用 Java EE 提供的方法肯定会产生问题。一个常见的例子就是开发人员通过使用 JAAS 模块来替代 Java EE 安全性,而不是使用内置的遵循规范的应用服务器机制来进行验证和授权。要注意不要脱离 Java EE 规范提供的验证机制。如果脱离了此规范,这将是系统存在安全漏洞以及厂商兼容性问题的主要原因。类似地,要使用 Servlet 和 EJB 规范提供的授权机制,并且如果您要偏离这些规范的话,要确保使用规范定义的 API(例如 getCallerPrincipal())作为实现的基础。通过这种方式,您将能够利用厂商提供的强安全性基础设施,其中,业务要求需要支持复杂的授权规则。(有关授权的更详细内容,请参见 [Ilechko]。)
其他常见的问题包括使用不遵循 Java EE 规范的持久性机制(这使得事务管理变得困难)、在Java EE程序中使用不适当的 J2SE 方法(例如线程或 singleton),以及使用您自己的方法解决程序到程序(program-to-program)的通信,而不是使用 Java EE 内在支持的机制(例如 JCA、JMS 或 Web 服务)。当您将一个遵循 Java EE 的服务器移植到其他的服务器上,或者移植到相同服务器的新版本上,上述的设计选择将会造成无数的问题。使用 Java EE 之外的元素,通常会导致一些细微的可移植性问题。唯一要背离规范的情况是,当一个问题在规范的范围内无法解决的时候。例如,安排执行定时的业务逻辑在 EJB2.1 出现之前是一个问题。在类似这样的情况下,我们建议当有厂商提供的解决方案时就使用厂商提供的解决方案(例如 WebSphere Application Server Enterprise 中的 Scheduler 工具),而在没有厂商提供的解决方案时就使用第三方提供的工具。当然,现在的 EJB 规范提供了基于时间的函数,所以我们鼓励使用这些标准接口。如果使用厂商提供的解决方案,应用程序的维护以及将其移植到新的规范版本将是厂商的问题,而不是您的问题。
最后,要注意不要太早地采用新技术。太过于热衷采用还没有集成到 Java EE 规范的其他部分或者还没有集成到厂商的产品中的技术常会带来灾难性的后果。支持是关键的——如果您的厂商不直接支持某种特定的技术,那么您在采用此技术时 就应该非常谨慎。有些人(尤其是开发人员)过分关注于简化开发过程,忽略了依赖大量本组织之外开发的代码的长期后果,而供应商并不支持这些代码。我们发现,许多项目团队沉迷于新技术(例如最新的开放源代码框架),并很快地依赖于它,却没有考虑它对业务带来的实际代价。坦白地说,对于使用您的供应商所提供 的产品之外的任何技术的决策,都应该由企业组织结构中的各个部门、业务团队和法律团队(或您的环境中的等同机构)仔细地进行评审,这与正常的产品购买决策完全相同。毕竟,我们中的大多数人是在解决业务问题,而不是推进技术的发展。
5. 从一开始就计划使用 Java EE 安全性。
启用 WebSphere 安全性。这使您的 EJB 和 URL 至少可以让所有授权用户访问。不要问为什么——照着做就是了。
在与我们合作的客户中,一开始就打算启用 WebSphere Java EE 安全性的顾客是非常少的,这一点一直让我们感到吃惊。据我们估计大约只有 50% 的顾客一开始就打算使用此特性。例如,我们曾与一些大型的金融机构(银行、代理等等)合作过,他们也没有打算启用安全性。幸运的是,这种问题在部署之前的检查时就得以解决。
不使用 Java EE 安全性是件危险的事情。假设您的应用程序需要安全性(几乎所有的应用程序都需要),敢打赌您的开发人员能够构建出自己的安全性基础设施,其比您从 Java EE 厂商那里买来的更好。这可不是个好的游戏。为分布式的应用程序提供安全性是异常困难的。例如,您需要使用网络安全加密令牌控制对 EJB 的访问。以我们的经验看来,大多数自己构建的安全性基础设施是不安全的,并且有重大的缺陷,这使产品系统极其脆弱。(有关更详细的信息,请参考 [Barcia] 的第 18 章。)
一些不使用 Java EE 安全性的理由包括:担心性能的下降,相信其他的安全性(例如 IBM Tivoli® Access Manager 和 Netegrity SiteMinder)可以取代 Java EE 安全性,或者是不知道 WebSphere Application Server 安全特性及功能。不要陷入这些陷阱之中。尤其是,尽管像 Tivoli Access Manager 这样的产品能够提供优秀的安全特性,但是仅仅其自身不可能保护整个 Java EE 应用程序。这些产品必须与 Java EE 应用服务器联合起来才可能全面地保护您的系统。
其他一种常见的不使用 Java EE 安全性的原因是,基于角色的模型没有提供足够的粒度访问控制以满足复杂的业务规则。尽管事实是这样的,但这也不应该成为不使用 Java EE 安全性的理由。相反地,应该将 Java EE 验证及 Java EE 角色与特定的扩展规则结合起来。如果复杂的业务规则需要做出安全性决策,那就编写相应的代码,其安全性决策要基于可以直接使用的以及可靠的 Java EE 验证信息(用户 ID 和角色)。(有关授权的更详细的信息,请参见 [Ilechko]。)
6. 创建您所知道的。
反复的开发工作将使您能够逐渐地掌握所有的 Java EE 模块。要从创建小而简单的模块开始而不是从一开始就马上涉及到所有的模块。
我们必须承认 Java EE 是庞大的体系。如果一个开发团队只是开始使用 Java EE,这将很难一下子就能掌握它。在 Java EE 中有太多的概念和 API 需要掌握。在这种情况下,成功掌握 Java EE 的关键是从简单的步骤开始做起。
这种方法可以通过在您的应用程序中创建小而简单的模块来得到最好的实现。如果一个开发团队通过创建一个简单的域模型以及后端的持久性机制(也许使用 的是 JDBC),并且对其进行了完整的测试,这会增强他们的自信心,于是他们会使用该域模型去掌握使用 Servlet 和 JSP 的前端开发。如果一个开发团队发现有必要使用 EJB,他们也会类似地开始在容器管理的持久性 EJB 组件之上使用简单的会话 Facade,或者使用基于 JDBC 的数据访问对象(JDBC-based Data Access Objects,DAO),而不是跳过这些去使用更加复杂的构造(例如消息驱动的 Bean 和 JMS)。
这种方法并不是什么新方法,但是很少有开发团队以这种方式来培养他们的技能。相反地,多数开发团队由于尝试马上就构建所有的模块,同时涉及 MVC 中的视图层、模型层和控制器层,这样做的结果是他们往往会陷入进度的压力之中。他们应该考虑一些敏捷(Agile)开发方法,例如极限编程(XP),这种 开发方法采用一种增量学习及开发方法。在 XP 中有一种称为 ModelFirst [Wiki] 的过程,这个过程涉及到首先构建域模型作为一种机制来组织和实现用户场景。基本说来,您要构建域模型作为您要实现的用户场景的首要部分,然后在域模型之上构建一个用户界面(UI)作为用户场景实现的结果。这种方法非常适合让一个开发团队一次只学到一种技术,而不是让他们同时面对很多种情况(或者让他们读很多书),这会令他们崩溃的。
还有,对每个应用程序层重复的开发可能会包含一些适当的模式及最佳实践。如果您从应用程序的底层开始应用一些模式(如数据访问对象和会话 Facade),您就不应该在您的JSP和其他视图对象中使用域逻辑。
最后,当您开发一些简单的模块时,在开始的初期就可以对您的应用程序进行性能测试。如果直到应用程序开发的后期才进行性能测试的话,这往往会出现灾难性的后果,正如 [Joines] 所述。
7. 当使用 EJB 组件时,始终使用会话 Facade。
在体系结构合适的情况下,使用本地 EJB。
使用容器管理的事务(CMT)提供了两个关键的优势(如果没有容器支持这几乎是不可能的):可组合的工作单元和健壮的事务行为。
如果您的应用程序代码显式地使用了开始和结束事务(也许使用 javax.jts.UserTransaction 或者甚至是本地资源事务),而将来的要求需要组合模块(也许会是代码重构的一部分),这种情况下往往需要改变事务代码。例如,如果模块 A 开始了一个数据库事务,更新数据库,随后提交事务,并且有模块 B 做出同样的处理,请考虑一下当您在模块 C 中尝试使用上述两个模块,会出现什么情况呢?现在,模块 C 正在执行一个逻辑动作,而这个动作实际上将调用两个独立的事务。如果模块 B 在执行中失败了,而模块 A 的事务仍然能被提交。这是我们所不希望出现的行为。如果,相反地,模块 A 和模块 B 都使用 CMT 的话,模块 C 也可以开始一个 CMT(通常通过配置描述符),并且在模块 A 和模块 B 中的事务将是同一个事务的隐含部分,这样就不再需要重写复杂的代码了。
如果您的应用程序在同一个操作中需要访问多种资源,您就要使用两阶段提交事务。例如,如果从 JMS 队列中删除一个消息,并且随后更新基于这条消息的纪录,这时,要保证这两个操作都会执行或都不会执行就变得尤为重要。如果一条消息已经从队列中被删除,而系统没有更新与此消息相关的数据库中的记录,那么这种系统是不一致的。一些严重的客户及商业纠纷源自不一致的状态。
我们时常看到一些客户应用程序试图实现他们自己的解决方案。也许会通过应用程序的代码在数据库更新失败的时候“撤销”对队列的操作。我们不提倡这样 做。这种实现要比您最初的想象复杂得多,并且还有许多其他的情况(想象一下如果应用程序在执行此操作的过程中突然崩溃的情况)。作为替代的方式,应该使用两阶段提交事务。如果您使用 CMT,并且在单一的 CMT 中访问两阶段提交的资源(例如 JMS 和大多数数据库),WebSphere 将会处理所有的复杂工作。它将确保整个事务被执行或者都不被执行,包括系统崩溃、数据库崩溃或其他的情况。其实现在事务日志中保存着事务状态。当应用程序访问多种资源的时候,我们怎么强调使用 CMT 事务的必要性都不为过。如果您所访问的资源不支持两阶段提交,那么您当然就没有别的选择了,只能使用一种比较复杂的方法,但是您应该尽量避免这种情况。
10. 将 JSP 作为表示层技术的首选。
只有在需要多种表示输出类型,并且输出类型被单一的控制器及后端支持时才使用 XML/XSLT。