写点什么

服务器端代理拥有事务的设计模式

  • 2012-04-09
  • 本文字数:5473 字

    阅读完需:约 18 分钟

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

当您在应用架构中用到命令模式(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 {
<strong>InitialContext ctx = new InitialContext(); </strong>
<strong>UserTransaction txn = (UserTransaction) </strong>
<strong>ctx.lookup("java:comp/UserTransaction"); </strong>
try {
<strong>txn.begin(); </strong>
...
Placement placement = placementService.placeTrade(trade);
executionService.executeTrade(placement);
<strong>txn.commit(); </strong>
} catch (TradeUpdateException e) {
<strong>txn.rollback(); </strong>
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
{
<strong>@TransactionAttribute( </strong>
<strong>TransactionAttributeType.SUPPORTS) </strong>
public BaseCommand executeRead(BaseCommand command) throws Exception {
CommandImpl implementationClass = getCommandImpl(command);
return implementationClass.execute(command);
}
<strong>@TransactionAttribute( </strong><strong>TransactionAttributeType.REQUIRED) </strong>
public BaseCommand executeUpdate(BaseCommand command) throws Exception {
try {
CommandImpl implementationClass = getCommandImpl(command);
return implementationClass.execute(command);
} catch (Exception e) {
<strong>sessionCtx.setRollbackOnly(); </strong>
throw e;
}
}
}

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

Spring 框架

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

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


感谢张龙对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2012-04-09 00:004949

评论

发布
暂无评论
发现更多内容

希望体验更好的开发流程

escray

学习 极客时间 朱赟的技术管理课 7月日更

新思科技解读金融服务业的应用安全误区与现实

InfoQ_434670063458

新思科技 金融服务安全

HarmonyOS开发者创新大赛获奖作品分享——《分镜头App》

科技汇

浪潮云洲赋能智造 拉升制造业“微笑曲线”

云计算

Kafka为何弃用zookeeper(翻译)

石头哥谈架构

kafka kafka架构 分布式消息/流中间件

爱奇艺搜索排序算法实践(内附福利)

爱奇艺技术产品团队

排序算法 nlp 搜索

智慧农业陷转型困局,区块链如何“对症下药”?

CECBC

你以为你懂redis?等看完某宝付费的资源你就知道了

Java架构师迁哥

2021可信云大会顺利召开,北鲲云践行云计算六大发展趋势

北鲲云

解读区块链技术对量子攻击的脆弱性以及量子安全区块链的解决方案

CECBC

ONES 当选深圳信创联盟副理事长单位,助力国产软件工业发展

万事ONES

信创 ONES

打造“云边一体化”,时序时空数据库TSDB技术原理深度揭秘

数据库 大数据 时序数据库 tsdb 数据智能

这次不编故事了,阿里Spring Cloud Alibabab笔记,自己领吧

Java架构师迁哥

【得物技术】服务发布时网络“抖动”

得物技术

网络 服务 响应时间 部署 发布

Fil收益怎么看?Fil一天收益如何?

区块链 IPFS fil收益 filecoin生态

第九周作业-朴朴超市用户路径&转化漏斗

小夏

产品经理训练营 邱岳

完备的娱乐行业知识图谱库如何建成?爱奇艺知识图谱落地实践

爱奇艺技术产品团队

nlp 搜索 知识图谱

微服务架构设计模式-进程间通信

以吻封笺

微服务 设计模式

下一个颠覆的领域:区块链如何影响审计行业?(中)

CECBC

如何识别并解决复杂的dcache问题

安第斯智能云

后端

新工具上线!sdkmgr命令行助力流水线构建

科技汇

手撕环形队列系列二:无锁实现高并发

实力程序员

程序员 数据结构 并发 无锁 环形队列

🏆「作者推荐!」【Java 技术之旅】彻底你明白什么是JIT编译器(Just In Time编译器)

洛神灬殇

Java 编译器 JIT compiler 即时编译器

行云管家荣获CFS第十届财经峰会2021科技创新引领奖!

行云管家

行云管家 财经峰会

阿里巴巴Java岗面试题库更新(第8版)

Java架构师迁哥

品牌轮:用MOT引导的品牌体验模型

石云升

用户体验 关键时刻 7月日更 体验设计

8月日更挑战正式开启,新人大奖等你来领!

InfoQ写作社区官方

8月日更 热门活动

主流分布式文件系统选型,写得太好了!

编程菌

Java 编程 程序员 计算机 技术宅

ISC网络安全大会关于“新型网络犯罪打击与治理”的分析

郑州埃文科技

网络安全 isc

Apache ShardingSphere:由开源驱动的分布式数据库中间件生态

亚马逊云科技 (Amazon Web Services)

人工智能 开源数据库

第三周作业-知识星球利益相关者排序

小夏

产品经理训练营 邱岳

服务器端代理拥有事务的设计模式_Java_Mark Richards_InfoQ精选文章