在Controller中进行事务管理是不是很糟糕?

Mat*_*ick 21 php mysql design-patterns transactions

我正在使用Yii框架开发PHP/MySQL应用程序.

我遇到过以下情况:

在我VideoController,我有一个actionCreate创建一个新的视频,actionPrivacy 并设置视频的隐私.问题是在调用模型actionCreatesetPrivacy方法期间Video,当前有一个事务.我希望视频的创建也在事务中,这会导致错误,因为事务已经处于活动状态.

在对这个答案的评论中,比尔卡文写道

因此,不需要使Domain Model类或DAO类管理事务 - 只需在Controller级别执行即可

这个答案中:

由于您使用的是PHP,因此您的交易范围最多只是一个请求.所以你应该只使用容器管理的事务,而不是服务层transa.也就是说,在处理请求开始时启动事务,并在完成处理请求时提交(或回滚).

如果我管理控制器中的事务,我会有一堆看起来像这样的代码:

public function actionCreate() {
  $trans = Yii::app()->getDb()->beginTransaction();
  ...action code...
  $trans->commit();
}
Run Code Online (Sandbox Code Playgroud)

这导致我需要事务处理的许多地方重复代码.

或者我可以将它重构为父类的beforeAction()afterAction()方法,Controller然后父类会自动为正在执行的每个操作创建事务.

这种方法会有问题吗?PHP应用程序的事务管理有什么好的做法?

Bil*_*win 18

我说交易不属于模型层的原因基本上是这样的:

模型可以调用其他模型中的方法.

如果模型尝试启动事务,但它不知道其调用者是否已启动事务,则模型必须有条件地启动事务,如@Bubba的答案中的代码示例所示.模型的方法必须接受一个标志,以便调用者可以告诉它是否允许它开始自己的事务.否则模型必须能够查询其调用者的"处于事务"状态.

public function setPrivacy($privacy, $caller){
    if (! $caller->isInTransaction() ) $this->beginTransaction();

    $this->privacy = $privacy;
    // ...action code..

    if (! $caller->isInTransaction() ) $this->commit();
}
Run Code Online (Sandbox Code Playgroud)

如果调用者不是对象怎么办?在PHP中,它可以是静态方法,也可以是非面向对象的代码.这变得非常混乱,并导致模型中的大量重复代码.

它也是控制耦合的一个例子,它被认为是错误的,因为调用者必须知道被调用对象的内部工作方式.例如,Model的某些方法可能具有$ transactional参数,但其他方法可能没有该参数.调用者如何知道参数何时重要?

// I need to override method's attempt to commit
$video->setPrivacy($privacy, false);  

// But I have no idea if this method might attempt to commit
$video->setFormat($format); 
Run Code Online (Sandbox Code Playgroud)

我看到的另一个解决方案建议(甚至在某些框架中实现,如Propel)是在DBAL知道它已经在事务中时生成beginTransaction()commit()禁止操作.但是如果你的模型试图提交并发现它没有真正提交,这可能会导致异常.或尝试回滚并忽略该请求.我以前写过关于这些异常的文章.

我建议的妥协是模型不知道交易.该模型不知道它的请求setPrivacy()是否应该立即提交,或者它是更大图片的一部分,更复杂的一系列更改涉及多个模型,并且只有在所有这些更改成功时才应该提交. 这是交易的重点.

因此,如果模型不知道他们是否可以或应该开始并提交他们自己的交易,那么谁呢?GRASP包含一个Controller模式,它是一个用例的非UI类,它被赋予了创建和控制所有部分以完成该用例的责任. 控制器了解事务,因为这是关于完整用例是否复杂所需的所有信息的位置,并且需要在模型中,在一个事务内(或者可能在多个事务中)中进行多个更改.

我之前写过的示例,即在beforeAction()MVC控制器的方法中启动事务并在方法中提交它afterAction()是一种简化.Controller应该可以自由地启动和提交与逻辑上完成当前操作所需的事务一样多的事务.或者有时Controller可以避免显式事务控制,并允许模型自动提交每个更改.

但问题是关于什么样的交易是必要的信息是模型不知道的 - 他们必须被告知(以$ transactional参数的形式)或者从他们的调用者查询它,无论如何,都必须将问题一直委托给Controller的行动.

您还可以创建一个类的服务层,每个类都知道如何执行这些复杂的用例,以及是否将所有更改包含在单个事务中.这样你就避免了很多重复的代码.但PHP应用程序包含不同的服务层并不常见; Controller的操作通常与服务层重合.

  • 好吧,如果控制器是开始并提交事务的控制器,这是否意味着控制器依赖于存储/连接适配器? (2认同)

bub*_*bba 7

最佳实践: 将事务放在模型中,不要将事务放在控制器中.

MVC设计模式的主要优点是:MVC使模型类可以重复使用而无需修改.轻松维护和实现新功能.

例如,假设您主要是为浏览器开发,用户一次输入一个数据集,并将数据操作移动到控制器中.稍后您意识到需要支持允许用户从命令行上载要在服务器上导入的大量数据集合.

如果所有数据操作都在模型中,您可以简单地在数据中啜饮并将其传递给模型进行处理.如果控制器中存在需要(事务性)功能,则必须在CLI脚本中复制该功能.

另一方面,也许你最终会得到另一个需要从不同点执行相同功能的控制器.您现在还需要在其他控制器中复制代码.

为此,您只需解决模型中的事务挑战.

假设你有一个带有setPrivacy()方法的Video类(模型),该方法已经有了事务构建; 并且你想从另一个方法中调用它persist(),它还需要将它的功能包装在一个更大的事务中,你只需要修改setPrivacy()来执行条件事务.

也许是这样的.

class Video{
    private $privacy;
    private $transaction;

    public function __construct($privacy){

        $this->privacy = $privacy;
    }

    public function persist(){
        $this->beginTransaction();
        // ...action code...
        $this->setPrivacy($this->privacy, false);
        // ...action code...
        $this->commit();
    }

    public function setPrivacy($privacy, $transactional = true){
        if ($transactional) $this->beginTransaction();

        $this->privacy = $privacy;
        // ...action code..

        if ($transactional) $this->commit();
    }


    private function beginTransaction(){
        $this->transaction = Yii::app()->getDb()->beginTransaction();
    }

    private function commit(){
        $this->transaction->commit();
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,你的直觉是正确的(重新:这导致我需要事务处理的许多地方重复代码.).构建您的模型以支持您拥有的大量事务需求,并让控制器仅确定它将在其自己的上下文中使用哪个入口点(方法).