写点什么

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

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

评论

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

成本翻倍,部署复杂?那是你用错了kubernetes!

鼎道智联

Kubernetes CI/CD

百度王海峰披露飞桨生态最新成果 开发者数量已达800万

飞桨PaddlePaddle

人工智能 百度飞桨 WAVE SUMMIT

文心一言最新重磅发布!

飞桨PaddlePaddle

人工智能 百度飞桨 文心大模型 WAVE SUMMIT

搭载KaihongOS的工业平板、机器人、无人机等产品通过3.2版本兼容性测评,持续繁荣OpenHarmony生态

OpenHarmony开发者

OpenHarmony

苹果mac版 Photoshop 2023 v25.0beta「ps」

胖墩儿不胖y

ps 2023 PS 2023破解 ps ai beta

极光笔记 | 如何为您的业务开发和训练一个AI-BOT

极光JIGUANG

人工智能 AI技术 AI工具

21. 面向对象及特性

茶桁

Python 面向对象

智能仓储管理系统(自动化仓库管理解决方案)

万界星空科技

MES系统 仓储执行系统 WMS仓库管理

Tampermonkey for Mac(油猴Safari浏览器插件) 4.17.6162 中文版

mac

油猴 苹果mac Windows软件 Tampermonkey插件

Parallels Desktop 18 中文激活-Pd 18密钥

mac大玩家j

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

福大大架构师每日一题

福大大架构师每日一题

Beyond Compare 4 for Mac(文件对比工具) 4.4.6(27483)中文版

mac

Beyond Compare 4 苹果mac Windows软件下载 文件比较对比工具

带你快速上手HetuEngine

华为云开发者联盟

大数据 后端 华为云 华为云开发者联盟 企业号 8 月 PK 榜

挖掘优质短视频超百万条,火山引擎DataLeap助力电商平台生态治理

字节跳动数据平台

大数据 数据中台 数据治理 数据安全 企业号 8 月 PK 榜

Java应用堆外内存泄露问题排查 | 京东云技术团队

京东科技开发者

Java 内存泄露 堆外内存 企业号 8 月 PK 榜

聊聊自动化测试的分层实践

老张

自动化测试

攀枝花是哪个省的?当地有等级保护测评机构吗?

行云管家

等保 等级保护 等保测评 攀枝花

IPQ4019-IPQ4029-IPQ5018-IPQ6010-support 802.11KVR-Fast Roaming-The Future of Seamless Connectivity

wifi6-yiyi

wifi5

Seamless Roaming with IPQ6010 and IPQ6018: Elevating Industrial-Grade WiFi6 Solutions

wallyslilly

IPQ6010 ipq6018 IPQ6000

vivo 容器集群监控系统优化之道

vivo互联网技术

可观测性 Prometheus 云原生监控 Victoriametrics

LVS专访阿里云席明贤,从视频云2.0到“数能生智”的超长畅谈

阿里云CloudImagine

云计算 阿里云 视频云

从 1 杯咖啡到 1 首歌的时间,炎凰数据如何实现 Pipeline 执行提速 6 倍?

极狐GitLab

DevOps gitlab cicd pipeline 炎凰数据

嵌入式开发场景下的代码管理方案(上)

极狐GitLab

git svn gitlab 嵌入式 源代码管理

7个小技巧让你运行C4D不卡!

Finovy Cloud

学习 #技术干货# 建模 技巧分享 4CD

京东门详一码多端探索与实践 | 京东云技术团队

京东科技开发者

小程序 taro 企业号 8 月 PK 榜 一码多端

使用NineData实现数据量亿级别MySQL大表迁移

NineData

数据库 NineData MySQL大表迁移 迁移方案 迁移复制

糟了糟了,总部被SD画完都Q了,这篇深入浅出贴助你早日实现Stable Diffusion自由 | 京东云技术团队

京东科技开发者

AI绘画 Stable Diffusion 企业号 8 月 PK 榜

腾讯云原生数据库TDSQL-C Serverless架构全新升级,助力业务存储成本降低80%

Geek_2d6073

医疗机构过等保选择哪款堡垒机好?为什么?

行云管家

网络安全 等保 等级保护 IT运维 医疗机构

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