事务脚本(Transaction Script)

根据过程组织业务逻辑,每个过程处理来自表现层的一个单一请求。

 

大多数应用程序可以想象成一系列事务。一个事务可以查看以一定方式组织的信息。另一个会修改数据库。每一个客户端系统和服务器系统之间的交互都包含着一定的逻辑。在某些情况下,这可能如显示数据库中的某些信息一样简单。在另外的情况下,可能包含进行验证和计算的很多步骤。

一个事务脚本主要是将逻辑组织成一个过程,这个过程直接调用数据库或通过一个thin database wrapper。虽然常用的子任务可以分解成子事务,每一个事务都有它自己的事务脚本。每一个事务脚本都是一个类的公共方法。

它如何工作

在事务脚本中,逻辑根据你的系统中事务进行组织。如果你的需要是预订一个宾馆房间,逻辑就是在预订宾馆房间过程中检查房间是否可用,计算价格,更新数据库。

对于简单的情况,对于如何组织实在没什么好说的。当然,就像其它的程序,你应该以一种有意义的方式将代码组织入模块中。这种方式的一个好处是你不需要担心其它事务在做什么。你的任务就是取得输入、查询数据库、处理、保存结果到数据库。

放置事务脚本的地方取决于你如何组织你的层。它可能在server page,CGI script,distributed session object中。我喜欢尽可能地分解事务脚本。至少把它们放到不同的过程中,更好的方法是,将它们放到与处理表现和数据源相分离的类中。另外,在事务脚本中不要调用任何表现逻辑,这样会使得修改代码和测试事务脚本变得更容易。

你可以通过两种方式将事务脚本组织入类中:最常用的方法是在一个类中放置多个事务脚本,每一个类都定义一个相关的事务脚本的主题区域。对于大多数情况,这是最直接的方式。另一种方法是将每一个事务脚本放到它自己的类中,使用Command模式。在这情况下你需要为你的命令定义一个超类,这个干超类指定了在事务脚本逻辑符合的一些可执行方法。这种方法的好处是允许你在运行时把脚本实例作为对象进行操作,虽然我很少看到使用事务脚本组织域逻辑的系统需要这样做。当然,你可以在很多语言中你可以完全不使用类而只是使用全局方法。然而,你会常常发现创建一个新对象有助于线程问题,因为他使得隔离数据变得更加容易。

我使用术语事务脚本(Transaction Script)因为多数情况下,你会每个数据库事务创建一个事务脚本。这不是百分百的规则,但却是第一个最接近的术语。

何时使用它

事务脚本最大的优势就是简单。以这种方法组织逻辑拥有少量逻辑的应用程序是最自然的方式,它只会带来很少的性能和理解上的损耗。

然而,当业务逻辑变得更加复杂时,它将很难保持处在一个良好设计的状态。一个问题就是对事务间重复代码的控制。因为,它的着眼点就是处理一个事务,任何通用的代码都有可能重复。

小心的构建可以减轻很多这种问题,但是,更复杂的业务域需要创建域模型(Domain Model)。域模型可以给你构建代码的很多选择,增加可读性和减少重复。

 收入计算问题

对于这个问题,和其它对于域逻辑的讨论,我将会使用同一个问题进行演示。

收入计算问题在业务系统中是一个经常遇到的问题。它是一个关于何时可以在你的账本上真正计算你收到的钱的问题。如果我卖给你一杯咖啡,它是一个简单的问题:我给你一杯咖啡,我收了你的钱,我就可以将钱计入账本。甚而,对于很多情况,它会变得更加复杂。例如,你付给我了在年底可以兑付的retainer。即使你今天付给我一些费用,我也不能立即将它计入我的账本,因为这个服务会在一年以后才会执行。一个方案可能是在一年的每个月只计算费用的十二分之一。

收入计算问题是多种多样并且是不稳定的。一些是根据规章,一些根据专业标准,一些根据公司政策。收入跟踪最后变成一个非常复杂的问题。

现在我不想讨论过于复杂的问题,所以,我们想象一个只卖三种产品的公司:文字处理程序、数据库和电子表格。根据规则,当你签署了一个文字处理程序合同,你就可以立即计入收入。如果是电子表格,你今天可以计入三分之一,六十天后计入三分之一,九十天后计入三分之一。如果是数据库,你今天可以计入三分之一,三十天后计入三分之一,六十后计入三分之一。

 实例:收入确认(Java)

这个例子使用两个事务脚本:一个计算合同的收入确认数,一个返回对于一个合同给定一个日期已确认的收入。数据库结构有三张表:产品、合同和收入确认。

CREATE TABLE products (ID int primary key, name varchar, type varchar)
CREATE TABLE contracts (ID int primary key, product int, revenue decimal, dateSigned date)
CREATE TABLE revenueRecognitions (contract int, amount decimal, recognizedOn date,
                                  PRIMARY KEY (contract, recognizedOn))

第一个脚本根据一个日期计算确认总数。我可以用两步完成这个操作:第一步从收入确认表中选出合适的记录;第二步累加总数。

很多事务脚本设计都有直接操作数据库的脚本,将SQL代码放入过程中。在这里,我使用一个简单的Table Data Gateway封装SQL查询。因为这个例子如此简单,我只使用了一个网关而不是为每个表创建一个网关,我可以在网关中定义一个恰当的查找方法。

 

class  Gateway... 

   
public  ResultSet findRecognitionsFor( long  contractID, MfDate asof)  throws  SQLException {
      PreparedStatement stmt 
= db.prepareStatement(findRecognitionsStatement);
      stmt 
= db.prepareStatement(findRecognitionsStatement);
      stmt.setLong(
1, contractID);
      stmt.setDate(
2, asof.toSqlDate());
      ResultSet result 
= stmt.executeQuery();
      
return result;
   }

   
private   static   final  String findRecognitionsStatement  =
      
" SELECT amount  "   +
      
" FROM revenueRecognitions  "   +
      
" WHERE contract = ? AND recognizedOn <= ? " ;
   
private  Connection db;

然后,使用脚本网关返回的结果集进行累加。

 

class  RecognitionService... 

   
public  Money recognizedRevenue( long  contractNumber, MfDate asOf)  {
      Money result 
= Money.dollars(0);
      
try {
         ResultSet rs 
= db.findRecognitionsFor(contractNumber, asOf);
         
while (rs.next()) {
            result 
= result.add(Money.dollars(rs.getBigDecimal("amount")));
         }

         
return result;
      }
catch (SQLException e) {throw new ApplicationException (e);
      }

   }


如果计算像这个例子一样简单,你可以使用调用SQL语句替代脚本,这个SQL语句使用聚集函数计算总数。

对于计算一个已存在合同的收入确认额,我使用相似的方法。

class  RecognitionService... 

   
public   void  calculateRevenueRecognitions( long  contractNumber)  {
      
try {
         ResultSet contracts 
= db.findContract(contractNumber);
         contracts.next();
         Money totalRevenue 
= Money.dollars(contracts.getBigDecimal("revenue"));
         MfDate recognitionDate 
= new MfDate(contracts.getDate("dateSigned"));
         String type 
= contracts.getString("type");
         
if (type.equals("S")){
            Money[] allocation 
= totalRevenue.allocate(3);
            db.insertRecognition
               (contractNumber, allocation[
0], recognitionDate);
            db.insertRecognition
               (contractNumber, allocation[
1], recognitionDate.addDays(60));
            db.insertRecognition
          (contractNumber, allocation[
2], recognitionDate.addDays(90));
         }
else if (type.equals("W")){
            db.insertRecognition(contractNumber, totalRevenue, recognitionDate);
         }
else if (type.equals("D")) {
            Money[] allocation 
= totalRevenue.allocate(3);
            db.insertRecognition
                (contractNumber, allocation[
0], recognitionDate);
            db.insertRecognition
               (contractNumber, allocation[
1], recognitionDate.addDays(30));
            db.insertRecognition
               (contractNumber, allocation[
2], recognitionDate.addDays(60));
         }

      }
catch (SQLException e) {throw new ApplicationException (e);
      }

   }


Table Data Gateway提供了对SQL的支持。首先是一个合同查找类。

 

class  Gateway... 

   
public  ResultSet findContract ( long  contractID)  throws  SQLException {
      PreparedStatement stmt 
= db.prepareStatement(findContractStatement);
      stmt.setLong(
1, contractID);
      ResultSet result 
= stmt.executeQuery();
      
return result;
   }

   
private   static   final  String findContractStatement  =
      
" SELECT *  "   +
      
" FROM contracts c, products p  "   +
      
" WHERE ID = ? AND c.product = p.ID " ;

第二个是对插入操作的封闭。

class  Gateway... 

public   void  insertRecognition ( long  contractID, Money amount, MfDate asof)  throws  
SQLException 
{
      PreparedStatement stmt 
= db.prepareStatement(insertRecognitionStatement);
      stmt.setLong(
1, contractID);
      stmt.setBigDecimal(
2, amount.amount());
      stmt.setDate(
3, asof.toSqlDate());
      stmt.executeUpdate();
   }

   
private   static   final  String insertRecognitionStatement  =
      
" INSERT INTO revenueRecognitions VALUES (?, ?, ?) " ;

在Java系统中确认服务可能是一个常规类或一个session bean。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值