《Java事务设计策略》服务器端代理拥有事务的设计模式

标签: java 设计 策略 | 发表时间:2012-04-09 08:00 | 作者:[email protected] (秩名)
出处:http://www.kuqin.com/jingyan/

当您在应用架构中用到命令模式(Command Pattern)或服务器端代理设计模式(Server Delegate Design pattern)时,本章描述的事务设计模式就比较适合了。在本模式中,服务器端代理组件,作为对服务器的远程接入点,拥有事务并负责对事务实施全面的管理。其他任何组件,包括客户端组件、领域服务组件、或是持久化组件都不负责管理事务,它们甚至不会察觉到它们正在使用到了事务。

命令模式是一种非常有用的设计模式,它解决了关于客户端事务管理以及EJB中的很多常见问题。这种设计模式背后最基本的原则是,客户端功能被包装在所谓”命令(command)“中,提交给服务器端以便执行。该命令可能包含了一个或多个对领域服务方法的调用。然而,这些领域服务方法的调用都是通过服务器端的被称为“命令实现(Command Implementation)”的对象去执行的,而不是在客户端执行。使用命令模式,使得用户可以从客户端对领域服务组件发起单一请求,并同时允许服务器端而不是客户端来管理事务处理过程。

服务器端代理模式的机制类似,唯一不同的是,它没有使用类似于命令模式的框架,而是简单地将客户侧的业务代理逻辑放置在服务器端的代理对象中去了。最终结果是一样的,事务处理过程被移到了服务器端,并且客户端对服务器的请求从多个减少到一个。

“服务器端代理拥有事务的设计模式”是“领域服务拥有事务模式”的特例。两者的主要区别在于,如果使用命令模式,会有“一个独立的对象”拥有事务,而不是由“一类组件”拥有事务。例如,如果您的应用包含40个不同的领域服务,在“领域服务拥有事务模式”下,可能由40个bean去管理事务。然而,在“服务器端代理拥有事务的设计模式”下,您只需要一个bean(命令执行者,Command Processor)去管理事务。

背景(Context)

下面的代码示例展示了在EJB环境下,为了完成一个业务请求,客户端对象必须形成多次对服务器调用的场景。

public class ClientModel
{
    public void placeFixedIncomeTrade(TradeData trade) throws Exception {
           InitialContext ctx = new InitialContext(); 
           UserTransaction txn = (UserTransaction) 
           ctx.lookup("java:comp/UserTransaction"); 
        try {
               txn.begin(); 
            ...
            Placement placement = placementService.placeTrade(trade);
            executionService.executeTrade(placement);
               txn.commit(); 
        } catch (TradeUpdateException e) {
               txn.rollback(); 
            log.fatal(e);
                throw e;
        }
    }
}

在这个实例中,客户端必须使用 编程式事务来确保一个独立的事务性工作单元中满足ACID特性。在此情形下,不仅仅是客户端要负责事务管理,而且因为多次对(远端)领域服务的调用,性能也受到影响。还有,在这个例子中,领域服务由无状态会话Bean的EJB实现,更大程度上使得架构复杂化。

为了简化此类情景,我们需要将无状态会话Bean实现的领域服务重构为POJO,从客户端移除事务逻辑,而后为所涉及的所有客户端请求构造一个单一的对服务器端的调用。我们可以使用命令模式或是服务器端代理设计模式去达到部分或全部的目标。当应用这两个模式中任何一个时,本来位于客户层的事务处理将被搬移到服务器层了。下面的代码示例展示了利用命令模式后,改动过的客户端代码:

public class ClientModel
{
     public void placeFixedIncomeTrade(TradeData trade) throws Exception {
         PlaceFITradeCommand command =  new PlaceFITradeCommand();
         command.setTrade(trade);
         CommandHandler.execute(command);
      }
}

可以看出,这是对之前代码做了非常大简化的版本。然而,有一个问题留下来了,“事务处理的逻辑在哪里呢”?在这个场景中,可以使用“服务器端代理者拥有事务”设计模式,甄别由哪个组件负责事务管理,以及对这些组件声明式事务该如何设定。

制约条件(Forces)

  • 使用命令模式和服务器端代理设计模式设计应用架构和客户端与服务器的通信。
  • 客户端总是与服务器端代理进行单一交互以完成业务请求。
  • 必须保证ACID特性,意味着需要进行事务处理以维护数据完整性。
  • 领域服务对象使用POJO实现,可以位于远程或本地。
  • 命令处理器或服务器端代理是从客户端到领域服务的单一访问点。

解决方案(Solution)

当使用命令模式或服务器端代理设计模式时,服务器端代理者拥有事务设计模式能够作为此类应用架构的整体事务设计策略。该模式使用将整个事务管理的责任放在服务器端代理组件上的责任模型。当使用命令模式时,服务器端代理组件实现为单一的命令处理器(Command Processor)组件,该组件定位有关的命令实现对象并执行命令。在EJB中,该组件通常被实现为无状态会话Bean。在Spring中,它可以被实现为Spring管理的bean(POJO)。当使用服务器端代理设计模式时,每个客户端请求的功能集合可以实现为彼此独立的服务器端代理(Spring中的POJO或是EJB中的SLSB)。

下图说明了在EJB和Spring框架两种情况下本模式的实现细节:

服务器端代理组件使用声明式事务,有关的更新方法被赋予Required的事务属性,而所有的读取方法被赋予Supports属性。服务器端代理组件在处理应用异常时,也会负责调用setRollbackOnly()方法。

这种模式适用的应用架构非常单一。在命令模式的用例下,服务器端代理被实现为命令处理器,简单地接收客户端发送来的命令对象,执行这些命令。在命令模式框架下自始至终使用接口(interface)的编程风格则保证了命令接收(Command Handler)组件、命令处理(Command Processor)组件、命令实现接口(Command Implementation Interface)、以及命令接口(Command Interface)本身都保持通用和应用无关性。服务器端代理建立的事务上下文被传播到它调用到的所有对象。

在服务器端代理设计模式中,服务器端代理组件是作为对应用架构中领域服务组件的客户端门面存在的。在这种设计模式中,客户端的逻辑实际上被搬到了服务器,并放在客户端代理组件中。

这些设计模式的主要缺点是,服务器端代理组件包含了客户端的逻辑,而不是纯的服务器逻辑。并且,本模式在很多情况下比较难于实现,因为客户端业务代理常常是与使用的web框架紧密绑定的。这种情况直接的例子是struts,在struts框架中Action类具备扮演客户端业务代理角色的能力(事实上很多时候它也是这么用的)。此外,也许将客户端逻辑搬走很难,因为其代码包含了对完全基于客户端的对象,例如HTTPSesssion、HTTPRequest,以及HTTPResponse的引用,这些对象要搬到服务器侧去是比较困难的。

然而,这两种设计模式最为明显的一个优势在于,包含处理请求业务逻辑主体的领域服务组件实现为POJO(简单Java对象),而不是EJB。因此领域服务组件从EJB框架中解耦合,使得它们易于测试。服务器端代理拥有事务的设计模式另一个独特之处在于,由于服务器端代理常常实现为一个无状态会话Bean的单例(singleton)组件,整个应用的事务逻辑位于单一的对象内。因此,从实现和维护的观点讲,它是最简单的事务设计模式。并且,该事务设计模式将事务管理的责任重担放在了领域服务组件的上面一层,将应用中服务器端的核心功能从事务管理之类的基础架构方面中解放了。通过使用本模式,领域服务组件可用POJO编写,由于它们不包含事务逻辑,在容器外的环境中测试就十分方便。

后果(Consequences)

  • 客户端(无论哪种类型)不包含任何事务逻辑,不管理事务处理的方方面面。
  • 由于服务器端代理组件开启和管理事务,更新方法在碰到应用异常时必须调用setRollbackOnly()方法。
  • 服务器端代理建立的事务被传播到基于POJO的领域服务对象,同时也传播到领域服务用到的持久化对象(无论使用哪一种持久化框架)。而且,这些组件不包含任何事务或回滚的逻辑。
  • 服务器代理对象使用声明式事务,对更新相关的方法应用Required的事务属性,对读取操作应用Supports属性。
  • 为了维护ACID特性,客户端对象绝不开启事务、提交事务,或将事务标记为回滚。
  • 持久化对象和领域服务不包含任何事务或回滚逻辑。
  • 如果使用EJB2.1的实体Bean,更新操作的事务属性必须设置为Mandatory,并不要使用任何回滚逻辑。对读取操作而言,如果使用容器管理持久化(Container-managed Persistence,CMP),需要为实体Bean设置Required的事务属性;如果使用Bean管理的持久化(Bean-managed Persistence,BMP),则需要设置事务属性为Supports。

实现(Implementation)

下面的代码分别展示了EJB和Spring下这种模式的实现。为举例方便的需要,我们假设使用命令模式。对EJB,我假设命令处理器用无状态会话Bean实现;对Spring,我假设命令处理器为Spring框架所管理。

EJB

在命令模式下,该事务设计模式只有一个组件包含事务代码(即事务处理器组件)。因此,由于客户端不包含事务代码,我们仅仅有必要展示服务器端代理(事务处理器)关于这个模式的代码实现。EJB中的领域服务组件,针对更新和读取操作的代码如下所示(事务逻辑用粗体表示):

@Stateless
public class CommandProcessorImpl implements CommandProcessor
{
       @TransactionAttribute( 
       TransactionAttributeType.SUPPORTS) 
    public BaseCommand executeRead(BaseCommand command)  throws Exception {
        CommandImpl implementationClass = getCommandImpl(command);
        return implementationClass.execute(command);
    }
       @TransactionAttribute(    TransactionAttributeType.REQUIRED) 
    public BaseCommand executeUpdate(BaseCommand command) throws Exception {
        try {
            CommandImpl implementationClass = getCommandImpl(command);
            return implementationClass.execute(command);
        } catch (Exception e) {
       sessionCtx.setRollbackOnly(); 
         throw e;
           }
    }
}

上面例子中的getCommandImpl()方法使用反射(reflection)来装载和实例化命令实现对象。而后它被执行,然后通过命令对象向客户端返回结果。注意,该实现与“领域服务拥有事务的设计模式”非常相似,因为我们对读取操作附加了Supports的事务属性,并且对更新相关操作附加了Required的事务属性,并辅以setRollbackOnly()方法。

Spring框架

在Spring框架下,这个模式完全通过Spring的XML配置文件实现。在EJB实现中需要调用setRollbackOnly()方法,而在Spring中,通过配置文件中的回滚规则指令就可以处理了。以下的配置代码展示了本模式如何设置服务器端代理组件(命令处理器)去处理更新和读取操作(事务逻辑加用粗体):

   
<!-- 定义服务器端代理命令处理器 --><bean id="commandProcessorTarget" class="com.commandframework.server.commandProcessorImpl"></bean> <bean id="commandProcessor" class="org.springframework.transaction.interceptor. TransactionProxyFactoryBean"> <property name="transactionManager" ref="txnMgr"/> <property name="target" ref="tradingServiceTarget"/> <property name="transactionAttributes"> <props> <prop key="executeUpdate"> PROPAGATION_REQUIRED,-Exception </prop> <prop key="executeRead">PROPAGATION_SUPPORTS </prop> </props> </property> </bean>

因为这个模式针对服务器端代理组件确定了声明式事务的使用,在领域服务组件中无论是更新还是读取的代码都不包含任何事务逻辑。如之前讲到的,setRollbackOnly()的逻辑被Spring自动处理了,我们可以从上面XML代码中-Exception那一段看出端倪。下面则演示了在Spring中实现该模式不需要任何事务逻辑的Java代码:

public class CommandProcessorImpl implements CommandProcessor
{
     public BaseCommand executeRead(BaseCommand command) throws Exception {
         CommandImpl implementationClass = getCommandImpl(command);
             return implementationClass.execute(command);
         }
     public BaseCommand executeUpdate(BaseCommand command) throws Exception {
         CommandImpl implementationClass = getCommandImpl(command);
            return implementationClass.execute(command);
      }
}

注意,在本例中,Spring在两个方法中的实现实质上做了同样的事情。不像EJB实现,异常处理并不必要,因为setRollbackOnly逻辑已经包含在XML的回滚策略设置中了。

关于作者

Mark Richards是IBM认证的高级IT架构师,他在IBM公司从事大型系统面向服务架构的设计和架构工作,使用J2EE与其他技术,主要为金融行业服务。作者早在1984年起就加入软件行业,从开发人员做起,直至设计师、架构师。他经常在著名论坛“No Fluff Just Stuff”演讲,他从波士顿大学获取了计算机科学硕士学位,持有SUN、IBM、BEA的多个Java与架构师认证。如有关于本书的评论或疑问,尽请 联系Mark

本文选自迷你书 《Java事务设计策略》 的第九章,译者翟静。

《Java事务设计策略》XA事务处理
Java编程提高性能时需注意的地方
Java几款性能分析工具的对比
设计模式学习笔记(十八)——Strategy策略模式

[ comments ]

相关 [java 设计 策略] 推荐:

《Java事务设计策略》服务器端代理拥有事务的设计模式

- - 酷勤网-挖经验 [expanded by feedex.net]
当您在应用架构中用到命令模式(Command Pattern)或服务器端代理设计模式(Server Delegate Design pattern)时,本章描述的事务设计模式就比较适合了. 在本模式中,服务器端代理组件,作为对服务器的远程接入点,拥有事务并负责对事务实施全面的管理. 其他任何组件,包括客户端组件、领域服务组件、或是持久化组件都不负责管理事务,它们甚至不会察觉到它们正在使用到了事务.

Java异常处理策略

- - 研发管理 - ITeye博客
任务与预先设定的规则不相符的情况都可以称之为异常. 但凡业务逻辑操作,都会划定一些边界或规则,但是往往事与愿违,总会有调皮鬼来挑战系统的健壮性. 用户并不都是知道潜规则的,比如用户的银行账户中只有100块钱,但是用户不查询就直接取200块. 码农有时候太过自信了,比如你在编写文件下载功能时忽略了文件有可能不存在这个分支流程.

JAVA优化代码策略(一)

- - Web前端 - ITeye博客
在Java程序中,性能问题的大部分原因并不在于Java语言,而是在于程序本身. 所以养成好的代码编写习惯非常重要. 比如:String 对象的使用中,出现字符串连接情况时应用StringBuffer 代替. 由于系统不仅要花时间生成对象,以后可能还需花时间对这些对象进行垃圾回收和处理. 因此,生成过多的对象将会给程序的性能带来很大的影响.

分布式系统设计策略

- - 互联网 - ITeye博客
摘自 《深入分布式缓存:从原理到实践》. 分布式系统本质是通过低廉的硬件攒在一起以获得更好地吞吐量、性能以及可用性等. 分布式系统有一些通用的设计策略,也是在分布式环境下普遍关心的几个问题:. 在分布式环境中,一般会有多个节点来分担任务的运行、计算或程序逻辑处理. 如上图所示,Client请求Server,Server转发请求到具体的Node获取请求结果.

Java API 设计清单 « 友好的API

- - 东西
在设计Java API的时候总是有很多不同的规范和考量. 与任何复杂的事物一样,这项工作往往就是在考验我们思考的缜密程度. 就像飞行员起飞前的检查清单,这张清单将帮助软件设计者在设计Java API的过程中回忆起那些明确的或者不明确的规范. 本文也可以看作为“ API设计指南”这篇文章的附录. 我们还准备了一些前后比对的例子来展示这个列表如何帮助你理清设计需求,找出错误,识别糟糕的设计实践以及如何寻找改进的时机.

java多线程设计wait/notify机制

- - CSDN博客推荐文章
  当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait() , 放弃对象锁..   之后在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A:.   # 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {…} 代码段内.

为什么分销策略比设计更重要?

- - 博客 - 伯乐在线
英文原文: medium.com,编译: 36氪. 乔布斯曾在《连线》杂志的采访中说:“「 设计」是一个有趣的词. 有人觉得,设计就是对外观的设计. 但进一步想,其实设计是产品的用法. Mac 的设计就不在于它的外观,尽管外观也是一部分. 最主要的设计还是在于对工作方式的设计. 要设计出好的产品,就必须熟悉它,切实弄清楚它到底是什么.

如何将战略性的内容策略融入进网页设计中

- - 互联网的那点事
译者注:对于网页设计人员,如何组织和规划内容可以说是一个十分关键而又令人头疼的问题. 本文站在管理者的角度,从内容规划、开发和维护三个阶段来告诉读者应该如何采取并实施正确的内容策略,同时提供了不少实用方法及内容规划工具,如 Basecamp、 Trello、 GatherContent.

9个杀手级策略,助你找到优秀设计师和程序员

- - TECH2IPO创见
本文作者:Steve Penfold 文章来源:. 尽管互联网行业蓬勃发展,优秀的设计师和程序员毕竟是少数,而同时,行业对于他们的需求却在不断增长,你正为你的公司苦苦寻找这类人才吗. 本文将告诉你如何鉴别他们,以及更加重要的,如何发现他们. CEO 只有三件工作:为公司制定整体目标和发展策略;雇佣最棒的人,并留下他们;保证公司银行账户有足够资产.

方法论:如何从0到1设计用户激活增长策略

- - 人人都是产品经理
用户激活是完成用户获取后的第一步,它也始终是营销人员的重点工作之一. 想要完成用户增长,营销人员需要明确新用户激活阶段的目标并找到激活时刻. 在做增长的工作中大家都非常关注拉新. 但若计算拉新漏斗每一步的转化率,常会得出一个非常糟糕的结论: 获取的95%以上的用户都流失了. 在这种情况下,做增长人的首要任务不是花费几十万甚至上百万的经费做拉新、扩展渠道,而是从根源解决问题,提高整体的漏斗转化率.