编排系统_客户编排策略

如果您遵循本系列教程,那么现在您将知道,无论使用哪种语言,环境,框架或平台,都需要一种有效且健壮的事务策略来确保高水平的数据一致性和数据完整性。 在本文中,我将描述“客户编排”事务策略,该策略在“ 模型和策略概述 ”中进行了简要介绍 。 顾名思义,当应用程序的客户端层必须对API层进行一次或多次调用以完成单个事务性工作单元时,将使用此策略。 我将在代码示例中使用EJB 3.0规范。 Spring Framework和Java Open Transaction Manager(JOTM)的概念相同。

有时,应用程序是用细粒度的API层编写的,要求客户端针对单个逻辑工作单元(LUW)多次调用API层。 这可能是由于复杂而多样的客户端请求无法与较粗粒度的API模型进行汇总而造成的,也可能是由于不良的应用程序设计所致。 无论出于何种原因,当来自客户端的多个API层方法调用的百分比超过合理的数量以重构为单个API层调用时,就该放弃更简单的API层策略并采用Client Orchestration交易策略了。

基本结构

在“ API层策略 ”中,我概述了构建事务策略的两个黄金法则:

  • 启动事务的方法被指定为事务所有者 。
  • 只有交易所有者才能回滚交易。

我再次提到这些规则,因为它们也适用于客户编排交易策略。 无论启动事务的方法位于何处,重要的是,事务所有者是管理事务并执行提交或回滚的唯一方法。

图1展示了大多数Java应用程序的典型逻辑应用程序层堆栈:

图1.体系结构层和事务逻辑
架构层和事务逻辑

图1中的体系结构实现了客户端编排事务策略。 包含事务逻辑的类以红色阴影显示。 注意,在此策略中,客户端层和API层均包含事务逻辑。 客户端层控制事务范围。 事务在这里开始,提交和回滚。 API层方法包含事务指令,这些指令指示事务管理器合并并使用由客户端层启动的事务范围。 业务层和持久层不包含事务逻辑,这意味着这些层不启动,提交或回滚事务,也不包含事务注释,例如EJB 3.0中的@TransactionAttribute注释。

不要挂在图1显示了四层这一事实上。 您的应用程序体系结构可以具有更多或更少的层。 您可以将表示层和域层组合在一个WAR文件中,或者您的域类可能在单独的EAR文件中。 您可能将业务逻辑放置在域类中为一层而不是两层。 与交易策略的工作方式或实施方式无关。

此事务策略非常适合具有复杂且细粒度的API层的应用程序。 这些应用程序(通常称为“ 聊天” )需要对API层进行多次调用才能实现单个LUW。 Client Orchestration事务策略不具有与API层事务策略相同的单个API层调用限制:您可以从客户端层对API层进行一次调用,也可以对每个LUW进行多次调用。 但是,从应用程序体系结构的角度来看,此事务策略比其他事务策略更具限制性,因为客户端层必须能够启动事务并将其传播到API层。 这意味着您不能使用Java消息服务(JMS)消息传递客户端,Web服务客户端或非Java客户端。 另外,客户端层和API层(如果有)之间的通信协议必须支持事务的传播(例如,Internet球间协议上的RMI [RMI-IIOP];请参阅参考资料 )。

我不鼓励使用细粒度的API层健谈应用程序体系结构; 相反,我是说, 如果您的应用程序比较健壮且无法重构,那么客户编排交易策略可能是适用的正确策略。

策略规则和特征

以下规则和特征适用于客户编排交易策略:

  • 仅应用程序体系结构的客户端层和API层中的方法应包含事务逻辑。 其他方法,类或组件均不应包含事务逻辑(包括事务注释,程序化事务逻辑和回滚逻辑)。
  • 客户端层方法是负责启动,提交和回滚事务的唯一方法。
  • 启动事务的客户端层方法被视为事务所有者 。
  • 在大多数情况下,客户端层中需要进行编程式事务处理,这意味着您必须以编程方式获取事务管理器并编写开始,提交和回滚逻辑的代码。 此规则的例外情况是,由Spring框架将控制事务范围的客户端层中的客户端业务委托作为Spring Bean进行管理。 在这种情况下,可以使用Spring提供的声明式事务模型。
  • 因为您不能以编程方式传递事务上下文,所以API层必须使用声明式事务模型,这意味着容器可以管理事务。 您只需要指定事务属性(无需回滚代码或回滚指令!)。
  • API层中的所有公共写方法(插入,更新和删除)都应标记为MANDATORY的事务属性,指示需要进行事务,但必须在调用该方法之前建立事务上下文。 与REQUIRED属性不同,如果一个不存在,则MANDATORY属性将不会启动事务,而是会引发异常,指示需要进行事务。
  • 无论抛出的异常类型如何,API层中的任何公共写入方法(插入,更新和删除)都不应包含回滚逻辑。
  • 默认情况下,API层中的所有公共读取方法都应标记为SUPPORTS的事务属性。 如果在该作用域的上下文中调用了read方法,则可以确保该方法包含在事务作用域中。 否则,它将在没有事务上下文的情况下运行,并假定它是逻辑工作单元(LUW)中唯一被调用的方法。 我在这里假设读取操作(作为API层的入口点)并不会在数据库上调用写入操作。
  • 来自客户端层的事务上下文将传播到API层方法以及在API层下调用的所有方法。
  • 如果客户端层正在对API层进行远程调用,则客户端层必须使用支持事务上下文(例如RMI-IIOP)传播的协议和事务管理器。

局限性

如前所述,此交易策略的最大限制之一是客户端层必须能够启动交易并将其传播到API层。 这意味着用于在客户端层与API层之间进行通信的协议以及客户端的类型在应用程序体系结构中起着重要的作用。 例如,您不能将Web服务客户端或JMS客户端与该策略一起使用,也不能依赖客户端层与API层之间的HTTP通信。 两层之间使用的协议必须能够支持事务传播。

API层事务策略不同,此策略的另一个局限性是您不能“欺骗”并将其逐步引入到应用程序体系结构中。 借助API层事务策略,如果您在重构过程中在客户端层启动事务,这不是世界末日。 使用API​​层事务处理策略执行此操作的影响在于,客户端层将无法对异常采取纠正措施,并且如果事务已在API层中已被回滚,则您将无法回滚。 马虎,但不具破坏性。

但是,使用Client Orchestration事务策略,因为API层使用MANDATORY事务属性,并且不包含事务回滚逻辑,所以客户机层方法必须启动事务。 将API层方法更改为REQUIRED并添加回滚逻辑会引入“ API层策略 ”中“限制和限制”部分中概述的相同问题。 此外,在API层方法中使用REQUIRED意味着可以由API层启动事务,从而违反了客户编排事务策略的主要原理。

交易策略实施

Client Orchestration交易策略的实现非常简单,但是由于它涉及架构的客户端层和API层,因此我将展示这两个层的方法的交易逻辑和策略实现。

回顾一下“ 策略规则和特征”部分,在大多数情况下,除非特别在Spring上下文中作为Spring管理的bean运行,否则客户端层需要使用编程事务 。 因为我将EJB3用于实现示例,所以将向您展示使用程序化事务的实现。 您可以参考“ 模型和策略概述 ”或Spring Framework文档,获取有关如何在Spring中使用程序化事务的示例(请参阅参考资料 )。

另外,如果您正在运行外部客户端,请确保事务管理器支持跨JVM的事务传播。 对于我的示例,我将JBoss 4.2.0与EJB 3.0结合使用,并将Java Persistence API(JPA)与Hibernate 3.2.3结合使用,并使用InnoDB引擎与MySQL 5.0.51b一起运行。 该环境(特别是JBoss)支持跨多个JVM的客户端事务传播(使用RMI-IIOP)。

我将从读取操作开始,因为这些操作最简单。 对于源自客户端层的数据库读取操作,您无需从事务的角度在客户端代码中做任何事情,因为数据库读取操作不需要事务(请参阅“ 了解事务 ”中的“ 永不说永不”边栏) 陷阱 ”)。 但是,与API层事务策略一样,您将需要将API层读取操作方法设置为SUPPORTS以确保如果在该作用域的上下文中调用该读取方法,则该方法将包含在事务作用域中。

清单1展示了调用读取操作的简单客户端层方法。 请注意, getTrade()读取方法中不需要事务逻辑:

清单1.读取操作—客户端层
package com.trading.client;

import javax.naming.InitialContext;
import com.trading.common.TradeData;
import com.trading.common.TradingService;

public class TradingClient {

   public static void main(String[] args) {
      new TradingClient().getTrade();
   }
	
   public void getTrade() {
      try {
         InitialContext ctx = new InitialContext();
         TradingService service = (TradingService)
            ctx.lookup("TradingServiceImpl/remote");
   
         TradeData trade = service.getTrade(11);
         System.out.println(trade);		
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

清单2显示了TradingServiceImpl EJB3无状态会话Bean中相应的getTrade() API层读取方法:

清单2.读取操作— API层
package com.trading.server;

import javax.ejb.Stateless;
import javax.ejb.Remote;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import com.trading.common.TradeData;
import com.trading.common.TradingService;

@Stateless
@Remote(TradingService.class)
public class TradingServiceImpl implements TradingService {
   @PersistenceContext EntityManager em;
   
   @TransactionAttribute(TransactionAttributeType.SUPPORTS)
   public TradeData getTrade(long tradeId) throws Exception {
      return em.find(TradeData.class, tradeId);
   }		
}

注意,清单2中使用了SUPPORTS事务属性,指示如果独立调用此方法,它将不会启动事务,但是如果在现有事务的上下文中调用它,它将使用现有的事务上下文。

对于数据库更新操作,作为事务所有者的客户端层负责获取事务管理器,启动事务,然后根据操作的结果提交事务或将其回滚。 通常,您需要在客户端层中使用程序化事务 。 在EJB3中,这是通过首先为应用程序服务器建立InitialContext ,然后在UserTransaction的Java命名和目录接口(JNDI)名称上执行查找来完成的。 对于JBoss,JNDI名称为UserTransaction 。 您可以参考我的交易书中的大多数常见应用程序服务器的JNDI名称列表(请参阅参考资料 ),或查阅所用应用程序服务器的文档。 一旦有了UserTransaction ,就可以对begin()方法进行编码以开始事务,对commit()方法进行提交,并且-如果发生异常,则可以使用rollback()方法对事务进行回滚。 清单3显示了客户端层方法的完整源代码,该方法向API层发出精心安排的更新请求,以插入股票交易并更新客户帐户:

清单3.更新操作—客户端层
package com.trading.client;

import javax.naming.InitialContext;
import javax.transaction.UserTransaction;
import com.trading.common.AcctData;
import com.trading.common.TradeData;
import com.trading.common.TradingService;

public class TradingClient {

   UserTransaction txn = null;
   
   public static void main(String[] args) {
      new TradingClient().placeTrade();
   }
	
   public void placeTrade() {
      try {
         InitialContext ctx = new InitialContext();
         TradingService service = (TradingService)
            ctx.lookup("TradingServiceImpl/remote");

         TradeData trade = new TradeData();
         trade.setAcctId(1234);
         trade.setAction("BUY");
         trade.setSymbol("AAPL");
         trade.setShares(100);
         trade.setPrice(103.45);
			
         txn = (UserTransaction)ctx.lookup("UserTransaction");
         txn.begin();
         service.insertTrade(trade);
         service.updateAcct(trade);
         txn.commit();
      } catch (Exception e) {
         try {
            txn.rollback();
         } catch (Exception e2) {
            e2.printStackTrace();
         } 
         System.out.println("ERROR: Trade Not Placed");
         e.printStackTrace();
      }
   }
}

由于客户端层中的更新方法始终是“客户编排”事务策略中的事务所有者,因此API层中的公共更新方法永远不应启动事务。 出于这个原因,他们必须使用MANDATORY事务属性,指示该方法需要事务,但该事务应已在其他地方(例如在客户端层)启动。 此外,根据第二条黄金法则,API层中的更新方法不应包含任何事务回滚逻辑。 清单4显示了一个EJB3无状态会话Bean的完整示例,该清单Bean为清单3中所示的相应客户端层代码实现了Client Orchestration事务策略:

清单4.更新操作— API层
package com.trading.server;

import javax.ejb.Remote;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import com.trading.common.AcctData;
import com.trading.common.TradeData;
import com.trading.common.TradingService;

@Stateless
@Remote(TradingService.class)
public class TradingServiceImpl implements TradingService {
   @PersistenceContext EntityManager em;
   
   @TransactionAttribute(TransactionAttributeType.MANDATORY)
   public TradeData insertTrade(TradeData trade) throws Exception { 
      trade.setStage("PLACED");		
      em.persist(trade);
      return trade;
   }

   @TransactionAttribute(TransactionAttributeType.MANDATORY)
   public void updateAcct(TradeData trade) throws Exception {
      AcctData acct = em.find(AcctData.class, trade.getAcctId());
      if (trade.getAction().equals("BUY")) {
         acct.setBalance(acct.getBalance() - (trade.getShares() * trade.getPrice()));
      } else {
         acct.setBalance(acct.getBalance() + (trade.getShares() * trade.getPrice()));
      }
   }
}

从清单4中可以看到,如果两个更新方法中的任何一个都抛出异常,则客户端层方法负责执行必要的事务回滚。 从清单4中还可以看到,采用这种策略,客户端层必须能够启动和传播事务。 否则,您将获得一个javax.ejb.EJBTransactionRequiredException指示需要一个事务来调用更新方法。

结论

当来自客户端层的大多数请求需要多次调用API层以完成单个LUW时,客户端编排事务策略很有用。 但是请务必小心-实施此策略会对应用程序体系结构施加限制,包括体系结构将支持哪种客户端以及客户端层与API层之间使用的通信协议。 这种事务处理策略的另一个缺点是,在客户端层使用程序化事务处理总是留有“程序员错误”的空间,更不用说客户端开发人员现在必须了解Java事务处理API(JTA)和相应的事务处理逻辑的事实。

不要试图在同一应用程序中混合使用客户端编排策略和API层策略来尝试解决应用程序体系结构中的每个排列问题。 它根本行不通,并且最终会导致数据库中的数据不一致以及设计过于复杂。 如果确实有不支持交易的客户,但是您发现“客户编排”交易策略似乎很合适,则可能需要进行一些重构。 解决这种“混杂”问题的方法之一是使用API​​层事务策略提供“替代API”外观层,并使用客户端编排策略调用API层。 但是,请记住,对此替代API的调用必须是单个调用(如API层事务策略中所指定)。 本质上,您将替代API视为API层的客户端。 在这里,您可以进行多个API层调用,因为事务将源自新的替代API。


翻译自: https://www.ibm.com/developerworks/java/library/j-ts4/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值