【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

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

  • 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:004831

评论

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

Moho Pro 14 for Mac(2D动画制作软件)附注册机v14.1激活版

Rose

Moho Pro 14下载 Moho Pro 14中文版 Moho Pro 14破解 二维动画制作

BetterMouse for Mac(鼠标增强软件)

展初云

Mac BetterMouse Mac鼠标辅助软件

​🔒 一文带你了解多文件混淆加密

天翼云以科技硬实力亮相第八届IDC中国数字化转型年度盛典

Geek_2d6073

macbook屏幕亮度调整工具:Lunar Pro for Mac破解版 附Lunar Pro注册机

Rose

Mac屏幕亮度调整工具 Lunar Pro破解版 Lunar Pro注册机 Lunar Pro下载

Mac云电视:CloudTV mac破解版 能够接通全球400多个电视频道

Rose

Mac破解软件 CloudTV for Mac Mac云电视播放 CloudTV破解版

使用SpringDataJPA(Hibernate)动态更新数据的正确姿势

redcoder54

Java hibernate Spring JPA

MacOS思维导图软件XMind for mac v24.01中文版下载

影影绰绰一往直前

字节2面真题,你能答对几道?

王磊

Java 面试

【代码可视化实践】代码变更影响分析 | 京东云技术团队

京东科技开发者

代码可视化 企业号11月PK榜 代码变更

Microsoft Remote Desktop for Mac激活版下载

影影绰绰一往直前

macbook翻译软件就用Translatium for Mac

Rose

翻译软件 Translatium中文版 Translatium下载 Translatium Mac Mac翻译工具

GitKraken for Mac(Git客户端) v9.10中文版

展初云

git Mac软件 GitKraken

万能蓝光播放器:Apeaksoft Blu-ray Player for Mac

Rose

Mac蓝光播放软件 Apeaksoft Blu-ray Player

分布式基础概念-选举算法

派大星

分布式 Java 面试题

PDF编辑阅读转换器:PDF Expert for Mac中文激活版下载

影影绰绰一往直前

IBM SPSS Statistics for Mac:强大统计分析工具,助力数据洞察与决策

晴雯哥

IBM SPSS Statistics Mac v27.0.1.0中文版 附 激活补丁

彩云

IBM SpsS Statistics 统计分析工具

IBM SPSS Statistics 27 Mac(spss统计分析软件)

展初云

Mac mac数据分析统计软件 IBM SPSS Statistics 27

Comsol Multiphysics for Mac(建模仿真软件) v6.2中文版

展初云

Mac COMSOL Multiphysics 物理场仿真软件

Wireshark的捕获过滤器

小魏写代码

虚拟定位软件AnyGo for Mac中文激活版下载

影影绰绰一往直前

自然环境渲染工具Terragen 4 for mac破解版 附Terragen永久证书

Rose

Terragen 4破解 自然景观生成工具 Terragen 4下载 Terragen许可证书

手游APP被攻击了我们如何用游戏盾防御

Geek_f19a80

服务器

文心一言 VS 讯飞星火 VS chatgpt (142)-- 算法导论12.1 2题

福大大架构师每日一题

福大大架构师每日一题

平台工程时代的 Kubernetes 揭秘:2023年生产状况报告深度剖析

SEAL安全

Kubernetes k8s 平台工程 企业号11月PK榜

小程序又给智能电视带来新的一春?

Geek_2305a8

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