例如,有以下调用的远程 API:
getGroupCapacity(group)
setGroupCapacity(group, quantity)
getNumberOfItemsInGroup(group)
addItemToGroup(group, item)
deleteItemFromGroup(group, item)
任务是将一些项目添加到某个组。团体有容量。 所以首先我们应该检查组是否未满。如果是,增加容量,然后添加项目。像这样的东西(例如 API 是通过 SOAP 公开的):
function add_item($group, $item) {
$soap = new SoapClient(...);
$capacity = $soap->getGroupCapacity($group);
$itemsInGroup = $soap->getNumberOfItemsInGroup($group);
if ($itemsInGroup == $capacity) {
$soap->setGroupCapacity($group, $capacity + 1);
}
$soap->addItemToGroup($group, $item);
}
现在如果 addItemToGroup 失败怎么办(项目是坏的)?我们需要回滚组的容量。
现在假设您必须将 10 个项目添加到组中,然后为添加的项目设置一些属性 - 所有这一切都在一个事务中完成。这意味着如果它在中间某处失败,您必须将所有内容回滚到以前的状态。
没有一堆 IF 和意大利面条代码是否可能?任何可以简化此类操作(在 PHP 中)的库、框架、模式或架构决策?
UPD:SOAP 只是一个例子。解决方案应该适用于任何服务,甚至是原始 TCP。问题的要点是如何使用底层非事务性 API 来组织事务性行为。
UPD2:我想这个问题在所有编程语言中都差不多。所以欢迎任何答案,不仅仅是 PHP。
提前致谢!
最佳答案
<?php
//
// Obviously better if the service supports transactions but here's
// one possible solution using the Command pattern.
//
// tl;dr: Wrap all destructive API calls in IApiCommand objects and
// run them via an ApiTransaction instance. The IApiCommand object
// provides a method to roll the command back. You needn't wrap the
// non-destructive commands as there's no rolling those back anyway.
//
// There is one major outstanding issue: What do you want to do when
// an API command fails during a rollback? I've marked those areas
// with XXX.
//
// Barely tested but the idea is hopefully useful.
//
class ApiCommandFailedException extends Exception {}
class ApiCommandRollbackFailedException extends Exception {}
class ApiTransactionRollbackFailedException extends Exception {}
interface IApiCommand {
public function execute();
public function rollback();
}
// this tracks a history of executed commands and allows rollback
class ApiTransaction {
private $commandStack = array();
public function execute(IApiCommand $command) {
echo "EXECUTING " . get_class($command) . "\n";
$result = $command->execute();
$this->commandStack[] = $command;
return $result;
}
public function rollback() {
while ($command = array_pop($this->commandStack)) {
try {
echo "ROLLING BACK " . get_class($command) . "\n";
$command->rollback();
} catch (ApiCommandRollbackFailedException $rfe) {
throw new ApiTransactionRollbackFailedException();
}
}
}
}
// this groups all the api commands required to do your
// add_item function from the original post. it demonstrates
// a nested transaction.
class AddItemToGroupTransactionCommand implements IApiCommand {
private $soap;
private $group;
private $item;
private $transaction;
public function __construct($soap, $group, $item) {
$this->soap = $soap;
$this->group = $group;
$this->item = $item;
}
public function execute() {
try {
$this->transaction = new ApiTransaction();
$this->transaction->execute(new EnsureGroupAvailableSpaceCommand($this->soap, $this->group, 1));
$this->transaction->execute(new AddItemToGroupCommand($this->soap, $this->group, $this->item));
} catch (ApiCommandFailedException $ae) {
throw new ApiCommandFailedException();
}
}
public function rollback() {
try {
$this->transaction->rollback();
} catch (ApiTransactionRollbackFailedException $e) {
// XXX: determine if it's recoverable and take
// appropriate action, e.g. wait and try
// again or log the remaining undo stack
// for a human to look into it.
throw new ApiCommandRollbackFailedException();
}
}
}
// this wraps the setgroupcapacity api call and
// provides a method for rolling back
class EnsureGroupAvailableSpaceCommand implements IApiCommand {
private $soap;
private $group;
private $numItems;
private $previousCapacity;
public function __construct($soap, $group, $numItems=1) {
$this->soap = $soap;
$this->group = $group;
$this->numItems = $numItems;
}
public function execute() {
try {
$capacity = $this->soap->getGroupCapacity($this->group);
$itemsInGroup = $this->soap->getNumberOfItemsInGroup($this->group);
$availableSpace = $capacity - $itemsInGroup;
if ($availableSpace < $this->numItems) {
$newCapacity = $capacity + ($this->numItems - $availableSpace);
$this->soap->setGroupCapacity($this->group, $newCapacity);
$this->previousCapacity = $capacity;
}
} catch (SoapException $e) {
throw new ApiCommandFailedException();
}
}
public function rollback() {
try {
if (!is_null($this->previousCapacity)) {
$this->soap->setGroupCapacity($this->group, $this->previousCapacity);
}
} catch (SoapException $e) {
throw new ApiCommandRollbackFailedException();
}
}
}
// this wraps the additemtogroup soap api call
// and provides a method to roll the changes back
class AddItemToGroupCommand implements IApiCommand {
private $soap;
private $group;
private $item;
private $complete = false;
public function __construct($soap, $group, $item) {
$this->soap = $soap;
$this->group = $group;
$this->item = $item;
}
public function execute() {
try {
$this->soap->addItemToGroup($this->group, $this->item);
$this->complete = true;
} catch (SoapException $e) {
throw new ApiCommandFailedException();
}
}
public function rollback() {
try {
if ($this->complete) {
$this->soap->removeItemFromGroup($this->group, $this->item);
}
} catch (SoapException $e) {
throw new ApiCommandRollbackFailedException();
}
}
}
// a mock of your api
class SoapException extends Exception {}
class MockSoapClient {
private $items = array();
private $capacities = array();
public function addItemToGroup($group, $item) {
if ($group == "group2" && $item == "item1") throw new SoapException();
$this->items[$group][] = $item;
}
public function removeItemFromGroup($group, $item) {
foreach ($this->items[$group] as $k => $i) {
if ($item == $i) {
unset($this->items[$group][$k]);
}
}
}
public function setGroupCapacity($group, $capacity) {
$this->capacities[$group] = $capacity;
}
public function getGroupCapacity($group) {
return $this->capacities[$group];
}
public function getNumberOfItemsInGroup($group) {
return count($this->items[$group]);
}
}
// nested transaction example
// mock soap client is hardcoded to fail on the third additemtogroup attempt
// to show rollback
try {
$soap = new MockSoapClient();
$transaction = new ApiTransaction();
$transaction->execute(new AddItemToGroupTransactionCommand($soap, "group1", "item1"));
$transaction->execute(new AddItemToGroupTransactionCommand($soap, "group1", "item2"));
$transaction->execute(new AddItemToGroupTransactionCommand($soap, "group2", "item1"));
$transaction->execute(new AddItemToGroupTransactionCommand($soap, "group2", "item2"));
} catch (ApiCommandFailedException $e) {
$transaction->rollback();
// XXX: if the rollback fails, you'll need to figure out
// what you want to do depending on the nature of the failure.
// e.g. wait and try again, etc.
}
关于php - 设计/架构问题 : rollbacks with remote services,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2263417/
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
由于fast-stemmer的问题,我很难安装我想要的任何rubygem。我把我得到的错误放在下面。Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingfast-stemmer:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcreatingMakefilemake"DESTDIR="cleanmake"DESTDIR=
当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。
首先回顾一下拉格朗日定理的内容:函数f(x)是在闭区间[a,b]上连续、开区间(a,b)上可导的函数,那么至少存在一个,使得:通过这个表达式我们可以知道,f(x)是函数的主体,a和b可以看作是主体函数f(x)中所取的两个值。那么可以有, 也就意味着我们可以用来替换 这种替换可以用在求某些多项式差的极限中。方法: 外层函数f(x)是一致的,并且h(x)和g(x)是等价无穷小。此时,利用拉格朗日定理,将原式替换为 ,再进行求解,往往会省去复合函数求极限的很多麻烦。使用要注意:1.要先找到主体函数f(x),即外层函数必须相同。2.f(x)找到后,复合部分是等价无穷小。3.要满足作差的形式。如果是加