在Controller中执行事务管理是不好的做法吗? [英] Is doing Transaction Management in the Controller bad practice?

查看:63
本文介绍了在Controller中执行事务管理是不好的做法吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm working on a PHP/MySQL app using the Yii framework.

我遇到了以下情况:

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

In my VideoController, I have a actionCreate which creates a new Video and actionPrivacy which sets the privacy on the Video. The problem is that during the actionCreate the setPrivacy method of the Video model is called which currently has a transaction. I would like the creation of the Video to be in a transaction as well which leads to an error since a transaction is already active.

在对此答案的评论中,比尔·卡尔文(Bill Karwin)写道

In the comment on this answer, Bill Karwin writes

因此无需管理域模型类或DAO类 交易-只需在控制器级别执行

So there's no need to make Domain Model classes or DAO classes manage transactions -- just do it at the Controller level

此答案:

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

Since you're using PHP, the scope of your transactions is at most a single request. So you should just use container-managed transactions, not service-layer transa. That is, start the transaction at the start of handling the request, and commit (or rollback) as you finish handling the request.

如果我在控制器中管理交易,我将有一堆类似于以下的代码:

If I manage the transactions in the controller, I would have a bunch of code that looks like:

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

这导致在很多需要执行交易的地方重复代码.

That leads to duplicated code in a lot of places where I need transactions for the action.

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

Or I could refactor it into the beforeAction() and afterAction() methods of the parent Controller class which would then automatically create transactions for each action being performed.

这种方法会出现问题吗?在PHP应用程序中进行事务管理的最佳做法是什么?

Would there be any problems with this method? What is a good practice for transaction management for a PHP app?

推荐答案

我说事务不属于模型层的原因基本上是:

The reason that I say transactions don't belong in the model layer is basically this:

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

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

If a model tries to start a transaction, but it has no knowledge of whether its caller started a transaction already, then the model has to conditionally start a transaction, as shown in the code example in @Bubba's answer. The methods of the model have to accept a flag so that the caller can tell it whether it is permitted to start its own transaction or not. Or else the model has to have the ability to query its caller's "in a transaction" state.

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

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

    if (! $caller->isInTransaction() ) $this->commit();
}

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

What if the caller isn't an object? In PHP, it could be a static method or simply non-object-oriented code. This gets very messy, and leads to a lot of repeated code in models.

它也是 控制耦合 的示例,这被认为是不好的,因为调用者必须了解有关被调用对象内部工作的某些知识.例如,您模型的方法中的 some 可能具有$ transactional参数,但其他方法可能没有该参数.调用者应该如何知道参数何时重要?

It's also an example of Control Coupling, which is considered bad because the caller has to know something about the internal workings of the called object. For example, some of the methods of your Model may have a $transactional parameter, but other methods may not have that parameter. How is the caller supposed to know when the parameter matters?

// 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); 

我见过的另一个建议的解决方案(甚至在Propel等框架中实现)是在DBAL知道它已经在事务中时使beginTransaction()commit()不再运行.但这会导致异常,如果您的模型尝试提交并发现其并未真正提交.或尝试回滚并使该请求被忽略.我以前已经写过关于这些异常的文章.

The other solution I have seen suggested (or even implemented in some frameworks like Propel) is to make beginTransaction() and commit() no-ops when the DBAL knows it's already in a transaction. But this can lead to anomalies if your model tries to commit and finds that its doesn't really commit. Or tries to rollback and has that request ignored. I've written about these anomalies before.

我建议的折衷方案是模型不了解交易.模型不知道它对setPrivacy()的请求是否应该立即提交,还是它是大图的一部分,是涉及多个模型的一系列更复杂的更改,因此应仅 提交如果所有这些更改都成功. 这就是交易的重点.

The compromise I have suggested is that Models don't know about transactions. The model doesn't know if its request to setPrivacy() is something it should commit immediately or is it part of a larger picture, a more complex series of changes that involve multiple Models and should only be committed if all these changes succeed. That's the point of transactions.

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

So if Models don't know whether they can or should begin and commit their own transaction, then who does? GRASP includes a Controller pattern which is a non-UI class for a use case, and it is assigned the responsibility to create and control all the pieces to accomplish that use case. Controllers know about transactions because that's the place all the information is accessible about whether the complete use case is complex, and requires multiple changes to be done in Models, within one transaction (or perhaps within several transactions).

我以前写过的示例是简化,该示例在MVC Controller的beforeAction()方法中启动事务并在afterAction()方法中提交它. Controller应该可以自由地启动和提交逻辑上所需的事务,以完成当前操作.有时,控制器可能会避免进行明确的事务控制,并允许模型自动提交每个更改.

The example I have written about before, that is to start a transaction in the beforeAction() method of an MVC Controller and commit it in the afterAction() method, is a simplification. The Controller should be free to start and commit as many transactions as it logically requires to complete the current action. Or sometimes the Controller could refrain from explicit transaction control, and allow the Models to autocommit each change.

但是关键是,关于哪些交易是必要的信息是模型不知道的-必须告知它们(以$ transactional参数的形式)或从其查询呼叫者,则无论如何都必须将问题一直委派给控制者的行动.

But the point is that the information about what tranasction(s) are necessary is something that the Models don't know -- they have to be told (in the form of a $transactional parameter) or else query it from their caller, which would have to delegate the question all the way up to the Controller's action anyway.

您还可以创建一个服务层,每个类都知道如何执行这种复杂的用法案例,以及是否将所有更改都包含在单个事务中.这样一来,您就避免了很多重复的代码.但是,PHP应用程序通常包含一个独立的服务层,这并不常见.控制器的动作通常与服务层一致.

You may also create a Service Layer of classes that each know how to execute such complex use cases, and whether to enclose all the changes in a single transaction. That way you avoid a lot of repeated code. But it's not common for PHP apps to include a distinct Service Layer; the Controller's action is usually coincident with a Service Layer.

这篇关于在Controller中执行事务管理是不好的做法吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆