【翻译】J2EE设计时的决策

Junglesong 发表于 2007-03-29 15:22:18

按:本文出于兴趣而翻译,有错误和纰漏处请见谅.
出处:http://www.javaworld.com/javaworld/jw-01-2006/jw-0130-pojo.html
作者:Chris Richardson, JavaWorld.com, 01/30/06

如果盲目的使用Pojo和轻量级框架,我们也会重蹈当年使用EJB的覆辙.每项技术都有其长处和短处,重要的是知道如何选择最合适的解决方案.

本书是关于使用设计模式和轻量级框架来完成企业级应用的.为确保你有效使用它们,本书提出了包括五种关键问题的决策.通过有意识的提出这些设计议题和理解你决定的后果,你将很快提高应用的质量.

在本文中,你将能总揽五种设计决策.我简要描述了每种决策的细节和它们各自的优缺点.

业务逻辑和数据库存取决定

有两种不同的途径来设计Java企业应用.一种是使用典型的EJB2方式,我称之为重量级途径.它是用SeesionBean和消息驱动Bean来完成业务逻辑.你还可以使用DAO(Data Access Object)和实体Bean来完成业务逻辑.

另一种是使用POJO和轻量级框架,我称之为POJO途径.当使用POJO途径时,业务逻辑完全由POJO组成.这种途径使用O/R Mapping框架如Hibernate或JDO来存取数据库,使用Spring AOP来提供如事务处理和安全处理等企业服务.

EJB3对这两种途径兼而有之,因为它包含了POJO和一些轻量级概念.比如说实体Bean是POJO,可以在EJB容器外运行.然而,虽然SessionBean和消息驱动Bean也是POJO,它们依然有重量级的行为,因为它们仅能在EJB容器内执行.你可以看出,EJB3同时拥有重量级途径和POJO途径的特性.EJB3的实体Bean是属于轻量级途径的而SeesionBean和消息驱动Bean是属于重量级途径的.

在重量级途径和POJO途径中作出选择是你在开发前要作出的无数决策中的第一个.它是影响到包括业务逻辑组织的决策和数据库存取机制的决策.为了帮助你在两种途径中作出决定,请看图一显示的典型企业应用的架构(白色部分),然后再查看每种需要作出决策(灰色部分).
Desision-figure1.jpg

企业应用包括基于Web的表现层,业务层和持久层.基于Web的表现层处理Http请求并生成HTML.业务层被表现层调用,它用来完成应用的业务逻辑.持久层被业务层使用来访问外部数据源如数据库或其它应用.

表现层的设计不在本文讨论的范畴内,我们关注的是图表中其余部分.我们需要决定业务层的结构及其暴露给表现层的接口.我们需要决定持久层如何存取数据库,我们也需要决定怎样处理短事务和长事务中的并发问题.这些加起来有五种决策,它们是设计者或架构师必须作出的,开发者必须整体理解的.

这些决策决定了应用的逻辑层和持久层的设计的关键特性.当然,还有一些重要的决策你必须作出,如怎样处理事务,安全和缓存,及怎样将应用装配起来.但是回答了这五个问题其它问题也能得到回答.

决策一:组织业务逻辑
这些天社区很多注意力集中在特定技术的优缺点上.这些当然很重要,但更基本的是你的业务逻辑是如何组织的.不思考怎样进行组织也容易写出代码,比如很轻易的向一个SessionBean加入许多代码而不是去思考领域模型类是否能适应新的功能.然而理想情况下你应该有意识的去组织最适合你应用程序的业务逻辑.毕竟,我确信你经历过维护自己或别人错误组织的代码的挫折感和不愉快感.

你必须作出的关键性的决策是使用面向对象的途径还是面向过程的途径.这不是技术决策,但你在技术上的选择能潜在的限制业务逻辑的组织形式.使用EJB2会坚定的推动你趋向面向过程的设计,而POJO和轻量级框架能确保你选择最合适的途径来完成你特定的应用.让我们来检查这些选项.

使用面向过程的设计

当我还是一个强烈的面向对象途径的吹鼓手时,有些情况下是存在过度设计的.但面向对象的设计的设计在某些情况下是不可能实行的,比如你没有持久层框架来将对象模型映射到数据库中时.在这种形势下,最好的途径时写过程化的代码即使用Martin Fowler所称的事务脚本模式(请参考<企业应用架构模式>).相对于面向对象的设计,你简单的写出称之为事务脚本的方法,处理来自于表现层的各种请求.

这种途径的重要特征是这些类的行为与其存储的状态是分离的.在EJB2应用中,它意味着你的设计像图二中显示的那样.这种设计将行为集中在SessionBean或POJO中,它们实现事务脚本和处理缺乏行为的哑数据(注:类似于失血模型).因为行为集中在少量的巨型类中,这些代码难以理解和维护.
Desision-figure2.jpg

这种设计高度过程化和缺乏对面向对象的语言特性依赖性.这是使用C等非OOP语言的典型设计.但是,你不应该因使用过程式设计而感到羞愧当它确实合适时.(按:架构师应当选择最合适的方案而不是概念上最完美的方案)

使用面向对象的设计

过程式设计的简单性可能很诱人(按:书写代码的简单).不需要仔细考虑如何组织类你就可以写出代码.问题是业务逻辑变得复杂时,你将被噩梦般的代码维护工作所终结.因此,除非面对非常简单的应用,你应该抵制书写过程式代码的诱惑而代之以面向对象的设计.

在面向对象设计中,业务逻辑由对象模型组成,它是一些相关小类组成的网络.这些类符合问题领域的概念.从图三可见,在这样的设计中一些类只有状态或行为,更多类两者都有,这是优秀设计的特点.
Desision-figure3.jpg

面向对象的设计有很多好处,包括好的可维护性和可扩展性.你可以使EJB2的实体Bean完成一个简单的对象模型,但为了享受大部分好处你应该使用POJOS和轻量级持久层框架比如Hibernate和JDO.POJO能保证你开发一个富血模型,它能使用继承和回调.轻量级持久层框架能保证你轻易的将对象模型映射到数据库上.

对象模型的另一个名字是领域模型,Fowler 称接近面向对象的开发业务逻辑的模式为领域模型模式.

表驱动模式
我经常使用领域模型和事务脚本模式来开发应用,但我听说Java企业应用还有第三种途径,它被Fowler称之为表模型模式(按:应该是我们称的表驱动模式).这种模式多建立在事务脚本模式的基础上,因为每一个数据库表都定义了一个用于处理表的表模块类.但类似于事务教本模式,它还是把状态和行为分散在分离的类中,因为表模块类的实例表现了整个数据库而不是个别的行.结果,维护性还是一个问题.因此,使用表模块模式只有有限的好处.

决策二:封装业务逻辑

在前面的章节中,我概括了如何组织业务逻辑.你必须决定业务逻辑拥有的接口.业务逻辑接口包括被表现层调用的类型和方法.当设计接口时一个重要的考虑是有多少业务逻辑实现应该被封装并对表现层不可见.封装改善了可维护性因为通过隐藏业务逻辑实现细节可以阻止对它的改变影响到表现层.但你必须写出更多的代码来封装业务逻辑.

你必须从事其它的问题,如怎样处理事务,安全和远程调用,因为它们是业务接口代码的职责.业务层的接口要保证对它的每次调用执行都要有对数据库操作的完整性(按:不理解的话请思考提款机的处理).同样的,它也要检验调用者是否被授权去调用业务方法.业务层接口也要保证对远程用户的可靠性.

让我们来考虑下面的选择.

EJB Session FACADE
封装业务逻辑的经典的J2EE途径是基于EJB的session facade.EJB容器提供了事务管理,安全,分布式处理和远程处理.这个外观也通过封装业务逻辑改善了可维护性.它粗放的API也通过最小化表现层和业务层之间调用的次数改善了性能.缩减对业务层的调用也缩减了数据库事务的数量和增加了缓存对象的机会.它也减少了网络的来回传递如果表现层是远程调用业务逻辑层的话.图四显示了使用EJB Session FACADE设计的例子.
Desision-figure4.jpg

在这种设计中,表现层调用外观(Facade),EJB容器中途截止对外观的调用,验证调用者是否被授权,然后再开始一个交易.然后由外观调用下层实现了业务逻辑的对象.当外观返回时,EJB容器提交或回滚这个交易过程.

不幸的是,使用EJB Session 外观模式有明显的缺陷.比如,EJB Session Bean只能在EJB容器内执行,这会减慢开发和测试.另外,如果你使用EJB2,则需要开发和管理DTO对象(数据传输对象,用于向表现层返回数据),这是很单调乏味和花时间的.

POJO外观

对许多应用而言,较好的途径是使用POJO外观如用Spring框架管理事务,持久层框架连接和安全.Pojo外观封装业务层于EJB外观类似并且一般来说拥有同样的共有方法.它们最大的区别是用AOP取代了EJB容器.图五显示了使用POJO外观的一种设计.

表现层调用POJO外观,POJO外观来调用业务层.与EJB容器中断对EJB外观的调用一样,AOP中断器中断对POJO外观的调用,鉴别调用者,再开始,提交,回滚交易.

通过确保所有的业务逻辑能在应用服务器外被开发测试,POJO外观途径简化了开发,并且也提供了许多EJB Session Bean的好处如宣言式事务和安全.一个额外的好处是你可以写更少的代码.你可以避免写很多数据传输类因为POJO外观直接将领域对象返回到了表现层;你可以使用依赖注入将应用组件连接在一起而不是书写JNDI查询代码.

然而,也有不能使用POJO外观的理由.举例来说,POJO外观不能参与远程客户端初始化的分布式交易.

暴露领域模型模式

使用POJO外观的另一个缺陷是你必须书写额外的代码.此外,确保持久性的领域对象返回到表现层的代码特别容易导致错误.由表现层试图访问没有被业务层载入的对象而导致的运行期错误有增长的风险.如果你使用JDO,Hibernate或EJB3,通过暴露领域对象给表现层和让业务层返回持久性的领域对象给表现层,你就可以避免这个问题.因为表现层导引领域对象间的关系,持久层可以在需要的时候加载某些对象,这在技术上称为Lazy Loading.图六显示了表现层自由访问领域对象的一种设计.
Desision-figure6.jpg

在图六的设计中,表现层直接调用了领域对象而不是通过外观.Spring的AOP能继续提供事务管理及安全等服务.

这种途径的一个重要的好处是它消除了业务层需要知道加载和返回给表现层的数据的需求.然而,虽然听起来简单,你会发现这里也存在缺陷.它增加了表现层的复杂度,使其必须管理数据库连接.Transaction management can also be tricky in a Web application because transactions must be committed before the presentation tier sends any part of the response back to the browser.

决策三:访问数据库

不管你怎样组织和封装业务层,你最终还是要从数据库提取和放入数据.在经典的J2EE应用中,你有两种选择:一种是JDBC,它需要大量底层代码或实体Bean,这是难于使用且缺乏重要细节.相对而言,使用轻量级的框架你可以显著减少许多数据库访问代码而获得更新的强有力的访问数据库途径.让我们仔细看看它们的细节.

直接使用JDBC代码有什么错误?

Hibernate,JDO等O/R mapping框架和iBatis这样的SQL Mapping框架不是凭空产生的.相反,它们是因Java界重复的JDBC挫折而浮现的.为了新框架出现的动机,让我们回顾一下直接使JDBC的问题,之所以说对许多应用而言直接使用JDBC不是一个好选择有三个理由:
1.开发和维护SQL代码是难且花时间的,许多开发者发现书写大的复杂的SQL语句非常难.将变化反映在SQL语句中也很花时间.你需要仔细考虑维护的付出是否比得上它带来的好处.
2.SQL不轻便,因为你经常需要书写数据库特定的SQL语句.一个与多种数据库打交道的引用肯定需要多种SQL语句,这维护起来是个噩梦.即使你的应用程序仅使用一种数据库,SQL的缺乏轻便性对用于测试的使用更简单和更快的内存数据库如HSQLDB也是个障碍.
3.书写JDBC代码花时间而易于出错--你必须写出大量的模板代码来获得连接,创建和初始化语句,然后关闭它们.你还必须书写Java对象和SQL处理语句间的映射代码.这些单调且易于出错.

如果你的应用必须直接执行SQL语句.,前两个问题是不可避免的.有时你必须发挥SQL语句的全面能量以获得高性能.或者,由于许多业务上的原因,你的DBA也许要求对你执行的SQL语句有完全控制,这会阻止你使用持久化框架来生成SQL语句.经常发生的是公司在数据库方面投资巨大以致你的应用程序显得相对不那么重要.就像iBATIS in Action的作者指出的那样"数据库和SQL语句比应用程序代码要更长久,或者在源码基础上还有多个版本,在一些场合下,程序即使被采用不同语言重写了,而SQL语句和数据库还能保持不变." 但幸运的是,如果你坚持直接使用SQL语句,也有能直接容易使用JDBC的框架.当然它就是iBATIS.

使用iBATIS
所有我经历过的J2EE程序都是直接执行SQL语句的.早期应用排他性使用SQL语句,而后期使用了持久层框架的在少数组件中使用SQL.一开始,我使用一般的JDBC执行SQL语句,但后来我以写小框架开发处理乏味的地方而告终.我甚至简单的使用了Spring的JDBC类,它消除了许多模板代码.但无论是自创的还是Spring都有Java类和SQL语句的映射问题,这就是我偶遇iBatis而感到异常激动的原因.

除了让程序与连接和代码完全绝缘,iBatis还用XML描述文件将JavaBeans和SQL语句映射在一起.它使用JavaBean反射机制来将Bean属性和SQL执行语句持有部分映射起来,并从ResultSet中构造出Beans.它也包括了数据库自生成主键的支持,自动加载相关对象,缓存和懒惰加载.通过这个方法,iBATIS消除了许多执行SQL语句的苦差使并简化了代码.你把写大量底层JDBC代码的工作代之以写XML描述文件,并少量调用BATIS的API.

使用持久层框架

当然,iBATIS 不能解决上面说的开发和管理SQL语句的问题.为了避免它们你需要使用将领域对象映射导数据库的持久层框架.它提供创建,寻找和删除对象的API.当程序处理对象间的关系时自动从数据库加载对象,在事务结束时更新数据库.它使用XML文件指定的O/R Mapping映射来生成SQL语句.这些XML文档定义了类如何映射导表,字段如何映射到列,以及外键和连接表的关系.

EJB2对持久层框架有自己的有限形式:实体Bean.但它有太多不足,开发和测试它也很冗长乏味.结果,EJB2的实体Bean很少被使用.

最广泛的轻量级持久层框架是Sun的JDO和开源的HIbernate.它们都对POJO提供了透明的持久化.你可以开发和测试自己的业务逻辑而无需担心持久化和类与数据库映射的问题,此外,它们在应用服务器外也能工作,这很大程度简化了开发.相对于旧式的EJB2实体bean,使用Hibernate和JDO是如此愉快.

除了决定怎么访问数据库,你必须决定如何处理数据库并行.让我们看看为什么这也是一个很重要的选项.

决策四:在数据库处理中处理并发

几乎所有的企业应用都有多个用户和后台线程能同时更新数据库.两个数据库处理同时访问同一数据很常见,这可能潜在引起数据库不一致或导致程序奇异.许多应用必须处理对同一数据的多并发情况,这能影响业务层和持久层的设计.

应用当然必须处理并发情况不管它是使用轻量级框架还是EJB.但是,不像EJB2实体Bean要求你使用指定厂商的产品,JDO和Hibernate直接支持了大多数并发机制.并且,使用它们不是一点简单配置就是一些少量代码.(先翻译到这里)

Isolated database transactions

Sometimes you can simply rely on the database to handle concurrent access to shared data. Databases can be configured to execute database transactions that are, in database-speak, isolated from one another. Don't worry if you are not familiar with this concept; for now the key thing to remember is that if the application uses fully isolated transactions, then the net effect of executing two transactions simultaneously will be as if they were executed one after the other.

On the surface this sounds extremely simple, but the problem with these kinds of transactions is that they have what is sometimes an unacceptable reduction in performance because of how isolated transactions are implemented by the database. For this reason, many applications avoid them and instead use what is termed optimistic or pessimistic locking, which is described a bit later.
Optimistic locking

One way to handle concurrent updates is to use optimistic locking. Optimistic locking works by having the application check whether the data it is about to update has been changed by another transaction since it was read. One common way to implement optimistic locking is to add a version column to each table, which is incremented by the application each time it changes a row. Each UPDATE statement's WHERE clause checks that the version number has not changed since it was read. An application can determine whether the UPDATE statement succeeded by checking the row count returned by PreparedStatement.executeUpdate(). If the row has been updated or deleted by another transaction, the application can roll back the transaction and start over.

It is quite easy to implement an optimistic locking mechanism in an application that executes SQL statements directly. But it is even easier when using persistence frameworks such as JDO and Hibernate because they provide optimistic locking as a configuration option. Once it is enabled, the persistence framework automatically generates SQL UPDATE statements that perform the version check.

Optimistic locking derives its name from the fact it assumes that concurrent updates are rare and that, instead of preventing them, the application detects and recovers from them. An alternative approach is to use pessimistic locking, which assumes that concurrent updates will occur and must be prevented.
Pessimistic locking

An alternative to optimistic locking is pessimistic locking. A transaction acquires locks on the rows when it reads them, which prevent other transactions from accessing the rows. The details depend on the database, and unfortunately not all databases support pessimistic locking. If it is supported by the database, it is quite easy to implement a pessimistic locking mechanism in an application that executes SQL statements directly. However, as you would expect, using pessimistic locking in a JDO or Hibernate application is even easier. JDO provides pessimistic locking as a configuration option, and Hibernate provides a simple programmatic API for locking objects.

In addition to handling concurrency within a single database transaction, you must often handle concurrency across a sequence of database transactions.
Decision 5: Handling concurrency in long transactions

Isolated transactions, optimistic locking, and pessimistic locking only work within a single database transaction. However, many applications have use-cases that are long running and that consist of multiple database transactions that read and update shared data. For example, suppose a use-case describes how a user edits an order (the shared data). This is a relatively lengthy process, which might take as long as several minutes and consists of multiple database transactions. Because data is read in one database transaction and modified in another, the application must handle concurrent access to shared data differently. It must use the Optimistic Offline Lock pattern or the Pessimistic Offline Lock pattern, two more patterns described by Fowler in Patterns of Enterprise Application Architecture.
Optimistic Offline Lock pattern

One option is to extend the optimistic locking mechanism described earlier and check in the final database transaction of the editing process that the data has not changed since it was first read. You can, for example, do this by using a version number column in the shared data's table. At the start of the editing process, the application stores the version number in the session state. Then, when the user saves their changes, the application makes sure that the saved version number matches the version number in the database.

Because the Optimistic Offline Lock pattern only detects changes when the user tries to save their changes, it only works well when starting over is not a burden on the user. When implementing such use-cases where the user would be extremely annoyed by having to discard several minutes' work, a much better option is to use the Pessimistic Offline Lock.
Pessimistic Offline Lock pattern

The Pessimistic Offline Lock pattern handles concurrent updates across a sequence of database transactions by locking the shared data at the start of the editing process, which prevents other users from editing it. It is similar to the pessimistic locking mechanism described earlier except that the locks are implemented by the application rather than the database. Because only one user at a time is able to edit the shared data, they are guaranteed to be able to save their changes.
Author Bio
Chris Richardson is a developer, architect, and mentor with more than 20 years of experience. He runs a consulting company that helps development teams become more productive and successful by adopting POJOs and lightweight frameworks. Richardson has been a technical leader at a variety of companies, including Insignia Solutions and BEA Systems. He holds a BA and MA in computer science from the University of Cambridge in England. He lives in Oakland, California, with his wife and three children.

关键词(Tag): j2ee设计决策


收藏: QQ书签 del.icio.us 订阅: Google 抓虾

最新评论

发表评论

* 昵称

已经注册过? 请登录

新用户请先注册 以便能显示头像及追踪评论回复

Email
网址
* 评论
表情
 
 

分类小组论坛
杂谈, 娱乐、八卦, 文学、艺术, 体育, 旅游、同城, 象牙塔, 情感, 时尚、生活, 星座, 科技

请注意遵守中华人民共和国法律法规, 如威胁到本站生存, 将依法向有关部门报告, 同时本站的相关记录可能成为对您不利的证据.

相关法律法规
全国人大常委会关于维护互联网安全的决定
中华人民共和国计算机信息系统安全保护条例
中华人民共和国计算机信息网络国际联网管理暂行规定
计算机信息网络国际联网安全保护管理办法
计算机信息系统国际联网保密管理规定