session facade
一个EJB客户端为了完成一个用例需要执行一个商业逻辑。
EJB客户端怎样才能在一个事务(transaction)和一个大批
(bulk)网络调用中执行一个用例的商业逻辑呢?
为了执行一个典型的用例的商业逻辑,多个服务器端对象
(如session或entity bean)通常需要被存取和可能的修
改。问题是session和entity bean的多个细粒度(fine-
grained)调用增加了多次网络调用(而且可能是多个事务
的)的网络开销(overhead),并且导致可维护性差的代码,
因为数据存取和工作流/商业逻辑在客户端之间分散了。
考虑一个在线银行情景,一个servlet接受请求来从一
个账户向另一个账户传输存款,作为一个Web客户端。
在这个情景下(像图1.1所示),一个servlet必须检查
来保证用户是授权了的,从一个银行账户entity bean
取出存款,并且把它存入另外一个银行账户entity bean。
当执行一个entity bean的home和remote interface,
这个方法在重负荷时将无法伸缩(scale),因为整个
情景(scenario)需要至少6个网络调用:3个寻找合适
的entity bean,还有三个实际的传送存款。还有,因为
entity bean是事务性的生物,每个对实体的调用需要
一个分离的服务器端的事务,需要通过应用服务器的
数据存储和维护来和远程实体同步。
更糟的是这个方法无法保证客户的钱的安全性。如果在
存钱时出错了,客户的钱将已经被取出,并且他的钱将
会丢失。用户授权检查,取钱,和存钱完全分离的运行,
并且如果存钱失败,取钱将不会回滚(roll back),结果
是不连续的状态。这里的问题是:当entity bean的方法
被直接调用,每个方法调用是一个分离的工作单元,并且
是一个分离的事务。
一个解决方案是在我们的entity bean中加入额外的逻辑,
来在一个单独的客户调用中完成许多操作。这个解决方案
引入了维护问题,因为我们的entity bean层将不能被应
用到许多不同的方式上。如果我们在每次需要性能增强
时增加应用逻辑到我们的entity bean上时,我们的entity
bean将很快变得臃肿和难于理解,维护和复用。我们有
效的将我们的应用逻辑(动词)和我们的持久化逻辑(名词)
融合起来,但这是差的应用设计。
另一个方法是为我们的客户端划分一个聚合,通过JTA的
大的事务。这将每个entity bean的方法调用工作在一个
相同的事务之下,在一个"全部或没有"(all-or-nothing)
的风格下。如果存钱失败,那么取钱将会被回滚(roll
back)并且用户的钱将会变得安全。然而,这个改进的
解决方案也有很多缺点:
1.高的网络开销。我们将有6个网络调用,将会降低性能。
(除非我们使用local interface)。
2.差的并发性。如果客户端离服务器很远(如一个applet
或application和远程的EJB系统交互,甚至可能通过
internet或一个防火墙),这个事务将会持续一个很长的
时间。这将导致超额锁定(excess locking),增加冲突
和死锁的可能性,并且降低了其他客户端存取同一个
entity bean实例的并发性。
3.高耦合。我们的客户端直接写一个entity bean的
API,这将使客户端和entity bean紧密的耦合在一起。
如果entity bean层需要在将来变化,我们也必须改变
客户端。
4.差的可复用性。执行“传送存款”的用例的商业逻辑
直接被嵌入到客户端。那么它将被有效的套在了那个
客户端中。其他类型的客户端(Java application,
applet,servlet,等等)不能重用这个商业逻辑。这个
把表示逻辑和商业逻辑混在一起是一个差的为任何
重要的发布的应用设计。
5.差的可维护性。
JTA的使用导致完成事务的中间件逻辑和应用逻辑交织在
一起。通过宣称(declarative)事务分离这两个会更干净,
所以我们能拧(tweak)和调(tune)我们的中间件,而且
不会影响我们的商业规则。
6.差的开发角色的分离。一个通用的在大型项目的实践
把表示层逻辑程序员的开发任务(如serlet和jsp开发者)
和商业逻辑/中间件程序员(EJB开发者)分离开来。如果
商业逻辑和客户端/表示层一起编码,一个清楚的角色
区分是不可能的。商业逻辑和表示逻辑程序员将踩着
各自的脚尖,如果同样在表示层编程。
我们的讨论的结论(takeaway point)是我们需要一个
服务器端的抽象作为entity bean的中介和缓冲。
session bean正是为这个设计的。
因此:
用一个叫做session facade的session bean层来
封装entity bean层。客户端应该只能存取session
bean,不能存取entity bean。
session facade模式把传统的facade模式的好处
应用到EJB,通过完全对客户端隐藏服务器上的
对象模型,通过用session bean层作为客户端
的单独存取点。图1.2描绘了架构通过这个方法
能被改进。session facade模式还通过在一个
网络调用的增强的用例的执行和提供一个干净
的层来封装完成这个用例的商业和工作流逻辑
带来好处。session facade通常作为无状态
session bean来实现。(虽然这个模式可以用
有状态session bean来实现。)
为了描绘这个paradigm怎样工作和这个paradigm
带来的好处,让我们用我们先前的例子。我们用
来的传输存款的用例的商业逻辑现在将在一个
session bean中替代,这个session bean有一个
叫做TransferFunds(userpk,accountpk,accountpk
,amount)的方法。那个Bank Teller session bean
因此完成大批(bulk)对Users和Bank账户的操作,
如图1.3所示。
因为Bank Teller session bean和User和Account
entity bean共存,它应该被hard-coded来和entity
bean通过local interface通信,因此把需要执行
那个用例的网络开销减小到一个调用(从客户端
到Bank Teller的调用)。同样,所有对entity bean
层的更新应该在Bank Teller所发起的事务中完成,
在它的deployment discriptor中定义,几乎总是,
用TX_REQUIRED的设置。这样有效的用一个事物
包装了整个用例,确保所有对entity bean的更新都在
Bank Teller的transferfunds方法所发起的事务中
完成。
session facade模式是今天使用中的最基础的
EJB模式(这就是为什么它是这本书的第一个
模式)。它不但提供性能好处,也建议了为
EJB 系统分配(systems-partitioning)你的
J2EE应用的标准架构,客户端和服务器被一个
session bean层所分离,这个session bean
的方法映射到(并且包括商业逻辑)所有的应用
中的用例。
进一步用Bank Teller例子,在bank应用中明显
有比传输存款更多的用例。使用session facade
模式,session bean将被创建来分组用例,使
相似的函数在一个bean中。因此我们可以增加其
它辅助(ancillary)银行操作到Bank Teller(如
withdrawFunds,depositFunds,getBalance())。
另外在banking应用中,为了不同目的的用例
也可以分组到一个session bean。例如,每个
银行有一个存款部门(loans department)。
需要去建模存款部门的操作的用例和Bank Teller
的用例没有关系。;所以,它们将分组成一个
LoanService session bean。相似的,一个
banking应用将也需要一个session bean来封装
和投资(investment)相关的用例。使用session
facade模式,这个baking应用的架构铺设(
architectural layout)将会如图1.4。
session facade模式工作得如此之好,以至于
常常它很容易被滥用。很通常能发现session
facade被误用的项目:
1.创建一个session bean巨类(God-class)。
常常开发者把系统中所有的用例都放在一个
session bean中。这导致臃肿的session
bean并降低开发生产力,因为所有的开发者
都需要用这一个类。session bean应该被分成
相关的用例的组群(house groupings)。
2.把领域(domain)逻辑放在session bean中。
一个设计得好的OO领域模型应该包括所有的
你的应用中的商业/用例逻辑(Fowler,2001)。
大多数session facade方法应该只是代理(
delegate)合适的entity bean,除非用例
包含需要跨不同bean而且不是直接有联系的
工作流(workflow)逻辑。
3.跨facade的商业逻辑的重复。随着项目
的增长,常常session bean方法包含重复
代码,如对checkCreditHistory的执行
逻辑,可能是任何数量的用例的工作流
的一部分。这个解决方案是增加一个服务层
(作为一个session bean或普通java类来实现),
来封装这个可重用,用例独立(use-case-inde
pendent)的商业逻辑。这个服务层是对客户端
隐藏的。当项目的体积变大时,用常规重构
(refactoring) sessions(重复逻辑被发现
和抽出(extracted))是很有用的。
下面是session facade模式的好处:
1.低网络开销。当session bean层增加一个
层来调用,客户端现在能只在一个网络调用
中传输存款,而不是6次网络调用。在服务器
上,session bean和entity bean通过local
interface来调用,从而不导致任何网络开销。
即使entity bean只被用做remote interface,
大多数应用服务器将优化共存(collocated)的
EJB之间的通信。
2.干净和严格的商业逻辑和表示层之间的分离。
通过使用session facade,需要去执行商业逻辑
的逻辑完全被封装在session bean的方法之后。
EJB客户端无需关心表示层的事情,并且为了
一个单位的工作完成无需执行多于一个方法。
这严格的将商业逻辑从表示逻辑分离。
3.事务集成。我们的session bean封装了所有的
逻辑来完成银行传输在一个事务中。session
bean因此作为一个事物facade,对服务器端
定位事务,并且保持它们短小。事务也在
session bean方法层次被改进(demarcated)了,
通过deployment descriptor可配置。
4.低耦合。客户端和entity bean之间的请求
被session bean缓冲了。如果entity bean层
需要在将来变化,我们能避免改变客户端,
因为有session bean的间隔(indirection)。
5.好的可重用性。我们的bank teller逻辑
是被封装到一个模块化的session bean中,
能被任何类型的客户端(JSP,servlet,
application,或applet)存取。封装到session
bean的应用逻辑意味着我们的entity bean
能只包含数据和数据存取逻辑,让它们可
以跨session bean在同一个或者甚至不同应用
中可复用。
6.好的可维护性。一个人应该宣称性(declara
tively)的在Bank Teller session bean的
Depolyment Discriptor中定义事务,而不是
通过JTA编程式(programmatically)的方法。
这让我们有一个干净的对中间件和应用逻辑
的分离,增加可维护性并且减少出错的可能性。
7.一个干净的动词-名词分离。session bean
层建模了特定应用用例,我们应用中的动词,
而entity bean建模了商业对象,或“名词”,
在我们的应用中。这个架构让我们很容易
从我们的需求文档映射到一个真实的EJB架构。
session facade模式是EJB开发中的订书钉(staple)。
它加强了高效率和可重用性的设计,和清楚的分离了
表示逻辑(客户端),商业逻辑(session facade)和
数据逻辑(entity bean,等等)。session facade描述了
一个有用的实现任何类型用例的架构;然而,如果
在自然中用例是异步的,Message facade模式提供了
一个更有伸缩性的方法。
相关模式:
Message Facade
Data Transfer Object
session facade(Alur,et al.,2001)
session facade(MartinFowler.com)