写点什么

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

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

评论

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

开源数据库TDSQL PG版再升级:分区表性能提升超10倍

腾讯云数据库

tdsql 国产数据库

选择“难而正确”的道路,国内VPN距离突破瓶颈还有多远?

科技热闻

隐喻回顾会

Bruce Talk

敏捷 Agile 回顾会 Coach/Facilitate

大数据开发之Hadoop高频面试题

@零度

大数据 hadoop

企业聊天APP-有什么作用,可以带来哪些便利?WorkPlus即时通讯

WorkPlus

前端开发之VUE基础面试题分享

@零度

Vue 前端开发

建议收藏 | SpringBoot 元数据配置原来可以这么玩!

码农架构

spring springboot SpringBoot 2 java 编程 1月月更

Tomcat系统架构分析-Service

编程江湖

tomcat

Linux之du命令

入门小站

Linux

【Redis集群原理专题】分析一下相关的Redis服务分片技术和Hash Tag

洛神灬殇

redis redis cluster redis架构 1月月更

TDengine在蓝深远望电机物联网监测预警与预测性维护平台中的应用

TDengine

数据库 大数据 tdengine 物联网

理清逻辑,确保云原生时代应用开发的全生命周期安全

华为云开发者联盟

网络安全 安全 应用开发 安全防守

在线正则表达式可视化测试工具

入门小站

工具

增效降本开源节流,2022年技术趋势前瞻(异步编程/容器技术)

刘悦的技术博客

容器 性能 异步IO 异步削峰 成本优化

ReactNative进阶(十五):应用 react-native-tab-navigator 实现底部导航栏

No Silver Bullet

​React Native 1月月更 底部导航

TDSQL 2021:致未来的年终总结

腾讯云数据库

tdsql 国产数据库

怎么访问到别人的电脑?

你?

云原生+国产化,腾讯云数据库不做选择题

腾讯云数据库

tdsql 国产数据库

青藤成功举办“ATT&CK应用发展论坛”,并发布《ATT&CK框架实践指南》

青藤云安全

Mybatis如何执行批量操作

编程江湖

mybatis

实现分区表性能提升超10倍,解密TDSQL PG版开源升级特性

腾讯云数据库

tdsql 国产数据库

使用CRM系统改善客户关系的方法

低代码小观

企业管理 CRM ERP CRM系统 企业管理工具

高成长、高潜力,火线安全入选2021中国新锐技术先锋企业20强!

火线安全

使用Amazon CDK部署基于Amazon Fargate的高可用、易扩展的Airflow集群

亚马逊云科技 (Amazon Web Services)

计算

技术解析 | 即构移动端超分辨率技术

ZEGO即构

计算机视觉 音视频 视频超分

趋势:2022 年 AI 五大预测

WorkPlus

拍乐云首发音视频「分组讨论」开放能力,开启线上群聊互动新玩法

拍乐云Pano

音视频 RTC 视频会议 泛娱乐 分组讨论

公开啦!「2021中国技术品牌影响力企业 」OceanBase 成功上榜

OceanBase 数据库

数据库 开源 开发者 OceanBase 开源 技术品牌

腾讯云TDSQL在PostgreSQL领域的‘‘再次突破’’

腾讯云数据库

tdsql 国产数据库

无服务器应用DevOps最新实践(内附完整演讲+视频)

亚马逊云科技 (Amazon Web Services)

计算

针对jQuery的优化方法有哪些

编程江湖

jquery

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