Skip to content

Commit

Permalink
Handle revert errors better (#6)
Browse files Browse the repository at this point in the history
* Catch and wrap up failures in applying compensation steps
  • Loading branch information
meadsteve committed Apr 18, 2019
1 parent 2745d91 commit 2c14200
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 5 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@

### Backwards incompatible changes

## v0.3.0 (2019-04-18)

### Enhancements
* Failures from applying compensations are caught allowing the process
to continue and are thrown at the end instead.

### Bug fixes
None

### Backwards incompatible changes
None

## v0.2.0 (2018-06-28)

### Enhancements
Expand Down
28 changes: 28 additions & 0 deletions src/Exceptions/FailedApplyingAllCompensations.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php


namespace MeadSteve\Tale\Exceptions;

use Throwable;

class FailedApplyingAllCompensations extends \RuntimeException implements TaleException
{
/**
* @var Throwable[]
*/
public $caughtExceptions;

/**
* FailedApplyingAllCompensations constructor.
* @param \Throwable[] $caughtExceptions
*/
public function __construct($caughtExceptions)
{
$previous = null;
if (sizeof($caughtExceptions) == 1) {
$previous = $caughtExceptions[0];
}
parent::__construct("Failed applying all compensation steps", 0, $previous);
$this->caughtExceptions = $caughtExceptions;
}
}
19 changes: 14 additions & 5 deletions src/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace MeadSteve\Tale;

use MeadSteve\Tale\Exceptions\FailedApplyingAllCompensations;
use MeadSteve\Tale\Execution\CompletedStep;
use MeadSteve\Tale\Execution\Failure;
use MeadSteve\Tale\Execution\Success;
Expand Down Expand Up @@ -87,12 +88,20 @@ private function addStep(Step $step): Transaction
*/
private function revertCompletedSteps(array $completedSteps): void
{
$errors = [];
foreach (array_reverse($completedSteps) as $completedStep) {
$step = $completedStep->step;
$stepId = $completedStep->stepId;
$this->logger->debug("Compensating for step {$this->stepName($step)} [{$stepId}]");
$step->compensate($completedStep->state);
$this->logger->debug("Compensation complete for step {$this->stepName($step)} [{$stepId}]");
try {
$step = $completedStep->step;
$stepId = $completedStep->stepId;
$this->logger->debug("Compensating for step {$this->stepName($step)} [{$stepId}]");
$step->compensate($completedStep->state);
$this->logger->debug("Compensation complete for step {$this->stepName($step)} [{$stepId}]");
} catch (\Throwable $error) {
$errors[] = $error;
}
}
if (sizeof($errors) !== 0) {
throw new FailedApplyingAllCompensations($errors);
}
}

Expand Down
30 changes: 30 additions & 0 deletions tests/Steps/Mocks/StepWithFailingCompensate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace MeadSteve\Tale\Tests\Steps\Mocks;

use MeadSteve\Tale\Steps\NamedStep;

class StepWithFailingCompensate implements NamedStep
{
public function execute($state)
{
return $state;
}

/**
* @param mixed $state
* @throws \Exception
*/
public function compensate($state): void
{
throw new \Exception("I failed compensating");
}

/**
* @return string the public name of this step
*/
public function stepName(): string
{
return "step with failing compensation";
}
}
23 changes: 23 additions & 0 deletions tests/TransactionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
namespace MeadSteve\Tale\Tests;

use Gamez\Psr\Log\TestLogger;
use MeadSteve\Tale\Exceptions\FailedApplyingAllCompensations;
use MeadSteve\Tale\Execution\Failure;
use MeadSteve\Tale\Steps\LambdaStep;
use MeadSteve\Tale\Tests\Steps\Mocks\FailingStep;
use MeadSteve\Tale\Tests\Steps\Mocks\MockFinalisingStep;
use MeadSteve\Tale\Tests\Steps\Mocks\MockStep;
use MeadSteve\Tale\Tests\Steps\Mocks\StepWithFailingCompensate;
use MeadSteve\Tale\Transaction;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -176,4 +178,25 @@ public function testProvidedLoggerGetsSomeDebugLogs()

$this->assertTrue($mockLogger->log->countRecordsWithLevel("debug") > 0);
}

public function testFailuresInCompensationAreCaught()
{

$firstStep = new MockStep();
$transaction = (new Transaction())
->add($firstStep)
->add(new StepWithFailingCompensate())
->add(new FailingStep());

$this->expectException(FailedApplyingAllCompensations::class);
$this->expectExceptionMessage("Failed applying all compensation steps");

try {
$transaction->run("some payload");
} finally {
# The reverted state should not be null as it should have
# been compensated
$this->assertNotNull($firstStep->revertedState);
}
}
}

0 comments on commit 2c14200

Please sign in to comment.