百度首页 | 百度空间
 
查看文章
 
PHP实现透明化事务处理
2008年03月06日 星期四 下午 08:13
作者:老王

事务是极其重要的,不然代码就和炸弹差不多,即便现在它能正常运行,可是说不定啥时候就会爆炸。

先看看一般情况下,我们是如何实现事务处理的,以AdoDB为例,代码如下:

$conn->BeginTrans();
$ok = $conn->Execute($sql);
if (
$ok) $ok = $conn->Execute($sql2);
if (!
$ok) $conn->RollbackTrans();
else
$conn->CommitTrans();


中规中矩的代码,虽然这样写没什么大毛病,但是早晚有一天,你会发现代码里到处充斥着和事务处理相关的代码,如果你不想到时候手忙脚乱的重构代码,就随我来看看如何实现透明化事务处理。

先来说说透明,它意味着我们的主体代码不应该显式的声明事务,取而代之的应该是隐式的配置事务。

再来看看事务的位置,对一个MVC程序来说,Controller中的Action是个绝好的位置,因为它勾画了事务的自然边界,在Action开始的时候打开事务,结束的时候提交事务,中间如果出现了问题就回滚事务。

现在我们要做的就是如何才能把事务处理透明的融合到Action中去。方法有很多,先唠叨一个很玄妙的方法:

class FooAction extends Action {
    
/**
     * @transaction
     */
    
function bar() {
        
// do something
    
}

    
// something else
}


我们在方法的注释上标上预先定好的标志,比如@transaction,这样,当在FrontController中处理这个Action的时候,我们可以通过反射功能先捕捉方法是否声明了@transaction,如果声明了的话,则在FrontController里操作事务的打开,提交,回滚。但是我不喜欢这样的方式,因为它太过玄妙,通过反射解析注释,怎么看都觉得是奇技淫巧。

下面讲讲另一个方法,利用装饰模式实现透明化事务,因为我感冒了,不想多写了,直接贴代码:

######################################################################

abstract class Wrapper extends Action {
    protected
$chain;

    public function
__construct($chain) {
        
$this->chain = $chain;
    }

    public function
getChain() {
        return
$this->chain;
    }

    public function
setChain($chain) {
        
$this->chain = $chain;
    }
}

abstract class
Action {
    protected
$wrappers;

    abstract public function
execute();

    public function
getWrappers() {
        return
$this->wrappers;
    }

    public function
setWrappers($wrappers) {
        
$this->wrappers = $wrappers;
    }
}

######################################################################

class AuthorizationWrapper extends Wrapper {
    public function
execute() {
        echo
"Authorization begin<br />\r\n";

        
$this->getChain()->execute();

        echo
"Authorization end<br />\r\n";
    }
}

class
TransactionWrapper extends Wrapper {
    public function
execute() {
        echo
"Transaction begin<br />\r\n";

        try {
            
$this->getChain()->execute();
        } catch (
Exception $e) {
            
// rollBack
        
}

        
// commit

        
echo "Transaction end<br />\r\n";
    }
}

######################################################################

class FooAction extends Action {
    protected
$wrappers = array('authorization', 'transaction');

    public function
execute() {
         echo
"<br />\r\n";
         echo
"FooAction::execute()<br />\r\n";
         echo
"<br />\r\n";
    }
}

######################################################################

$action = new FooAction();

$wrappers = $action->getWrappers();

for (
$i = count($wrappers) - 1; $i >= 0; $i--) {
    
$class = ucfirst($wrappers[$i] . 'Wrapper');

    
$action = new $class($action);
}

$action->execute();

######################################################################


代码中,为了说明装饰模式的用法,我使用了两个装饰对象,一个Authorization,一个Transaction,你只要看准Transaction就可以了。

运行上面的代码,屏幕会出现如下结果:

Authorization begin
Transaction begin

FooAction::execute()

Transaction end
Authorization end

可以看到,Action动作已经被Transaction包裹起来了,如果Action在执行过程中,抛出了异常,那么Transaction会自动完成回滚,否则就会提交。事务的支持是通过在Action对象里制定wrappers属性实现的,也算是比较透明了。哈哈,我们的目标算是达到了。

还有一点需要注意的就是:不能在Action里直接跳转,否则Transaction没法提交或者回滚,为了避免这个问题,我们需要实现一个Response对象,用来封装响应,在Action结束后才决定是跳转还是渲染一个页面。

======================================

后记:PHP5.3就要到来了,我也琢磨着写一个新的PHP框架,上面说的透明事务处理这个特色肯定会是这个框架的一部分,预计还会有很多别的比较酷的特色。不过一个人的力量总是有限的,不知道最后这个框架能不能实现。如果你有兴趣,我们可以一起来做,欢迎留言拍砖。

类别:Php | 添加到搜藏 | 浏览() | 评论 (22)
 
最近读者:
 
网友评论:
1
2008年03月06日 星期四 下午 09:26
顶,沙发
 
3
2008年03月07日 星期五 上午 09:27
测试了下,没感觉什么装饰不装饰,感觉就象“包裹”,哈哈!

老王写框架啊,敝人一定支持!
 
4
2008年03月07日 星期五 上午 09:30
有点疑问的是,action经过多次转变,已经不是原来的action,总感觉怪,象是变戏法一样,似乎不太合理,是否还有其他实现方式呢?
 
5
2008年03月07日 星期五 上午 09:45
你这个还不算是标准的Decrate模式。
 
6
2008年03月07日 星期五 上午 09:56
神仙能否给个完整的Decrate模式呢?
 
8
2008年03月07日 星期五 上午 10:15
To trooman:装饰和包裹是一个意思。Action确实经过了多次转换,这正是装饰模式的要点所在。实际操作的时候,Action转换的过程会被封装到FrontController中去,对于调用者而言是完全透明的,感觉不到转换的存在,他只要和平常一样使用Action即可。

To 神仙:我觉得大体上这个例子符合装饰模式,可能还不完善,有空给个你的例子。
 
9
2008年03月07日 星期五 上午 11:15
我仔细想了想,最终结合PHP的特点,用以下方式实现可能最好,这个设计完全是独自想到的,没参考任何先人的思想,难免有不足之处,欢迎指正!

(遗憾,不能全部发出来,有字数限制,晕,我一会到phpeye社区贴完整的,欢迎老王去看看)
$action = new FooAction();
$wrapper = new Wrapper($action);
$wrapper->add('execute', new Transaction());
$wrapper->add('execute', new Authorization());
$wrapper->execute();

 
10
2008年03月07日 星期五 上午 11:25
帖子见http://www.phpeye.com/bbs/thread-384-1-1.html
 
11
2008年03月07日 星期五 下午 12:00
To trooman:看了你的实现,使用的是基于before, after的回调函数的方式。这种方式在当前问题背景下有问题,因为你的TransactionWrapper和Action是割裂的,也就是说,你没法透明的在TransactionWrapper中捕捉到Action的异常,自然也就无法透明的实现rollback。
 
12
2008年03月07日 星期五 下午 12:36
哦,确实没考虑异常处理,但我的代码改一下就可用,我觉得有必要隔离Wrapper和Action,这种隔离并非割裂,而是为了“各司其职”。

总之,Wrapper继承至Action感觉很怪异,呵呵!
 
13
2008年03月07日 星期五 下午 01:35
楼主多关注下AOP
对编写新的框架有很大好处。
 
15
2008年03月07日 星期五 下午 03:23
To trooman:对于wrapper继承Action的问题,我觉得没啥。因为传统的装饰模式要求装饰对象和被装饰的对象要实现一个功能的接口,我为了简单,所以写成wrapper继承Action,一方面这样更符合装饰模式的定义,另一方面wrapper中其实也有可能会用到Action定义的某些方法。当然,也不必拘泥于此,对于弱类型的PHP来说,即使不实现一个共同的接口,类似上面的代码,只要保证wrapper和action都实现了execute方法即可。

To 匿名网友:对PHP而言,没有一个好的纯粹的AOP实现方式,换个角度讲,似乎纯粹的AOP也很少用到,大多数时候,我们做一个动态代理来模拟AOP即可。
 
16
2008年03月07日 星期五 下午 04:07
To 老王:经你那么一说,我算彻底领悟了先辈的装饰模式。不过,我认为我的方法完全可以实现这种效果,包括在wrapper中如何捕获异常,我改了下调试成功。我记得先辈说分离职责和分离关注点之类的原则,我的设计上正好体现这一点。

原来的装饰模式是针对于某个对象进行装饰(包裹)的,而我认为装饰模式是针对于某个对象中的某个方法的,我的实现就达到这样的目的,而且还要达到“通用”的目的,不知我这样理解有没有错误。
 
18
2008年03月07日 星期五 下午 04:29
To trooman:你的做法一般称之为回调函数形式,多少还有些差别,因为你用到了__call这样的魔术方法,这样的做法和传统的装饰模式有差别。装饰模式如我的代码所示,有一个包裹的过程,而回调函数不是包裹,而是通过预留的hooks方法,直接戳进方法内部。

你可以认为装饰模式是针对于某个对象中的某个方法的。但这就牵扯到一个问题,从广义的角度上看,装饰模式不侵入方法本身,但是如果你使用基于回调函数的方式,就需要在你方法主体里预先埋入hook,当然,你可以像你的代码里所写的那样,一切都通过__call来进行,但是对于魔术方法,我的看法是除非必要,否则不用,属于奇技淫巧的范畴。

另外,我对基于before、after的回调形式如何实现在Transaction里捕捉异常没有想清楚,你有代码的话不妨贴出来让我学习一下。
 
20
2008年03月07日 星期五 下午 05:03
To 老王:这里有字数限制,不好贴,我贴到phpeye了

http://www.phpeye.com/bbs/thread-384-1-1.html

phpeye真TNND没人气,一天都没人回复过,还不如老王的博客有人气!
 
22
2008年03月07日 星期五 下午 07:49
To trooman:你这个捕捉异常我觉得有问题,首先,你这个捕捉的代码其实仅仅是transaction用的,而你却把这段代码放在父类的__call实现,就让父类承担了本不该它承担的责任,换个角度讲,如果我们还有一个装饰对象foobar,也需要某个功能,那么我们也在父类中实现?!这样下去,你的父类就成了一个GodClass,明显的坏味道。
 
23
2008年03月07日 星期五 下午 09:32
真是很好,没有说了。。。。。谢谢~
 
24
2008年03月10日 星期一 上午 09:22
To 老王:首先Wrapper不是一个父类,它仅相当于代理者的角色,在Wrapper中,异常可以“传递”到WrapperBehavior的子类中,transaction能捕获异常就是通过这样的“传递”来实现的,如果第一个WrapperBehavior的子类(如Transaction)不去处理并中断这个异常,那么,异常还会继续往下一个WrapperBehavior的子类(如Authorization)进行传递,所以,我的代码中,Wrapper的__call方法进行的异常传递不是针对transaction,是针对所有WrapperBehavior的子类,至于WrapperBehavior的子类需不需要去捕获异常,那是WrapperBehavior子类的事情。

如果我们还有一个装饰对象foobar,也需要某个功能,当然是在这个装饰对象foobar实现,不需要在Wrapper中实现。

不过,我倒觉得我这样的实现有些怪异,我似乎在zend framework中看到类似于“异常”如何传递的代码,我不能肯定我的方法可行,仅是进行一种尝试,当然,我的出发点是分离职责,我不认为大师们所提出的装饰模式的设计是合理的,我认为Wrapper就是Wrapper,比如我们不能把一瓶酒的包装看着是这瓶酒的一部分或整体,包装仅仅是包装而已。
个人见解,不对之处欢迎老王和其他网友批评!
 
26
2008年03月10日 星期一 上午 09:58
To trooman:

虽然本文是讨论装饰模式的使用的,但是如果从整体来看,就是J2EE模式中常见的“责任链”的范畴,所谓责任链就是说在执行一个实际的命令前,预先执行若干个操作,这些命令呈链条状,一个一个传递下去执行,如阎宏大师所写的《Java与模式》一书中的击鼓传花的例子一样,责任链中链条上的操作可以决定是停止执行,还是继续执行。

我的责任链实现方式就如代码所示,采用的装饰模式的方式,它本身是非常简单的,举例来说明它的好处,比如说 TransactionWrapper,因为TransactionWrapper包裹着实际的Action,所以我能很自然的在它里面封装事务处理的代码,而不会把相关代码泄露到别处。

你的代码说到底也是一种责任链的实现,只不过是基于回调函数的方式,另外使用Wrapper做调度器,承担一个manager的角色,就责任链实现本身来说,不管是我的方法,还是你的方法,都是可以的,没有优劣之分。但是本文涉及一个客观的需求,就是要透明的封装事务处理,在你的代码里,事务处理的相关代码散落在Transaction和Wrapper中,其中Wrapper是manager的角色,由它承担部分的事务处理职责是不合适的,如果任由这样的情况,Wrapper早晚会成为一个GodClass。
 
27
2008年03月10日 星期一 下午 02:26
好东西 不顶不行
 
29
2008年03月20日 星期四 下午 04:50
。。。写php代码需要这么麻烦么,,奇怪
 
30
2008年05月04日 星期日 下午 09:46
连续看了老王好多篇文章,收获很多。

我也在尝试自己写框架,但是自己面向对象基础和模式设计知识很少,所以写的很乱,如果老王也有兴趣,我倒是很愿意跟老王一起做,希望能学到更多东西。

 
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
验证码:
 

     

©2008 Baidu