Skip to content

Commit

Permalink
Merge pull request #24 from sroze/operations-via-yaml
Browse files Browse the repository at this point in the history
Add a YML operation runner factory
  • Loading branch information
sroze committed Jan 13, 2016
2 parents ef5acf9 + 9b82723 commit e70998a
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 49 deletions.
Binary file added docs/_static/runner-factory.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 40 additions & 1 deletion docs/bridges/symfony.rst
Expand Up @@ -10,6 +10,45 @@ The Symfony bridge is a bundle that you can use in any Symfony application.
**Note**: you can also *manually* use the classes in the :code:`Tolerance\Bridge\Symfony` namespace if you do not want
to install this bundle in your Symfony application.

Operation runner factory
------------------------

When using simple operation runners, you can create them using the YML configuration of the bundle. Each operation runner
have a name (:code:`default` in the following example). The created operation runner will be available via the service named
:code:`tolerance.operation_runner.default`.

.. code-block:: yaml
tolerance:
operation_runners:
default:
retry:
runner:
callback: ~
strategy:
max:
count: 10
strategy:
exponential:
exponent: 1
waiter: tolerance.waiter.null
In that example, that will create a operation runner that is the retry operation runner decorating a callable operation runner.
The following image represents the imbrication of the different runners.

.. image:: ../_static/runner-factory.png

.. note::

This YML factory do not support recursive operation runner. That means that you can't use a chain runner inside
another chain runner. If you need to create more complex operation runners, you should create your own service
with a simple factory like `the one that was in the tests before this YML factory <https://github.com/sroze/Tolerance/blob/f95bb3ae6a5f331a8d0579a991438f68e28f66f9/tests/Tolerance/Bridge/Symfony/Bundle/AppBundle/Operation/ThirdPartyRunnerFactory.php>`_.

.. tip::

If you just need to add a decorator on a created operation runner, simply uses `Symfony DIC decorates features. <http://symfony.com/doc/current/components/dependency_injection/advanced.html#decorating-services>`_

AOP
---

Expand Down Expand Up @@ -60,7 +99,7 @@ service's methods inside an operation runner.
<service id="app.your_service" class="App\YourService">
<tag name="tolerance.operation_wrapper"
methods="getSomething"
runner="operation_runner.service_name" />
runner="tolerance.operation_runner.default" />
</service>
</services>
</container>
Expand Down
14 changes: 14 additions & 0 deletions features/symfony/app/config/config.yml
Expand Up @@ -6,4 +6,18 @@ framework:
storage_id: "session.storage.mock_file"

tolerance:
operation_runners:
default:
retry:
runner:
callback: ~

strategy:
max:
count: 10
strategy:
exponential:
exponent: 1
waiter: tolerance.waiter.null

aop: ~
Expand Up @@ -18,23 +18,130 @@ public function getConfigTreeBuilder()

$root
->children()
->arrayNode('request_identifier')
->addDefaultsIfNotSet()
->canBeEnabled()
->children()
->scalarNode('header')
->cannotBeEmpty()
->defaultValue('X-Request-Id')
->end()
->booleanNode('monolog')
->defaultTrue()
->end()
->end()
->end()
->append($this->getRequestIdentifierNode())
->append($this->getOperationRunnersNode())
->booleanNode('aop')->defaultFalse()->end()
->end()
;

return $builder;
}

private function getRequestIdentifierNode()
{
$builder = new TreeBuilder();
$node = $builder->root('request_identifier');

$node
->addDefaultsIfNotSet()
->canBeEnabled()
->children()
->scalarNode('header')
->cannotBeEmpty()
->defaultValue('X-Request-Id')
->end()
->booleanNode('monolog')
->defaultTrue()
->end()
->end()
;

return $node;
}

private function getOperationRunnersNode()
{
$builder = new TreeBuilder();
$node = $builder->root('operation_runners');

$children = $node
->useAttributeAsKey('name')
->prototype('array')
->children();

foreach ($this->getOperationRunnerNodes() as $runnerNode) {
$children->append($runnerNode);
}

$children
->end()
->end()
;

return $node;
}

private function getOperationRunnerNodes(array $except = [])
{
$builder = new TreeBuilder();
$nodes = [];

if (!in_array('retry', $except)) {
$retryNode = $builder->root('retry');
$children = $retryNode
->children()
->arrayNode('strategy')
->isRequired()
->children();

foreach ($this->getStrategyNodes() as $node) {
$children->append($node);
}

$runnerChildren = $retryNode
->children()
->arrayNode('runner')
->children();

foreach ($this->getOperationRunnerNodes($except + ['retry']) as $runner) {
$runnerChildren->append($runner);
}

$nodes[] = $retryNode;
}

if (!in_array('callback', $except)) {
$retryNode = $builder->root('callback');

$nodes[] = $retryNode;
}

return $nodes;
}

private function getStrategyNodes(array $except = [])
{
$builder = new TreeBuilder();
$nodes = [];

if (!in_array('max', $except)) {
$maxNode = $builder->root('max');
$strategyChildren = $maxNode
->children()
->integerNode('count')->isRequired()->end()
->arrayNode('strategy')
->isRequired()
->children();

foreach ($this->getStrategyNodes($except + ['max']) as $node) {
$strategyChildren->append($node);
}

$nodes[] = $maxNode;
}

if (!in_array('exponential', $except)) {
$exponentialNode = $builder->root('exponential');
$exponentialNode
->children()
->integerNode('exponent')->isRequired()->end()
->scalarNode('waiter')->isRequired()->end()
->end()
;

$nodes[] = $exponentialNode;
}

return $nodes;
}
}
Expand Up @@ -5,8 +5,14 @@
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Tolerance\Operation\Runner\CallbackOperationRunner;
use Tolerance\Operation\Runner\RetryOperationRunner;
use Tolerance\Waiter\Strategy\Exponential;
use Tolerance\Waiter\Strategy\Max;

class ToleranceExtension extends Extension
{
Expand All @@ -21,6 +27,7 @@ public function load(array $configs, ContainerBuilder $container)
$container->setParameter('tolerance.aop.enabled', $config['aop']);

$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('waiter.xml');

if ($config['request_identifier']['enabled']) {
$this->loadRequestIdentifier($container, $loader, $config['request_identifier']);
Expand All @@ -29,6 +36,12 @@ public function load(array $configs, ContainerBuilder $container)
if ($config['aop']) {
$this->loadAop($container, $loader);
}

foreach ($config['operation_runners'] as $name => $operationRunner) {
$name = sprintf('tolerance.operation_runners.%s', $name);

$this->createOperationRunnerDefinition($container, $name, $operationRunner);
}
}

private function loadRequestIdentifier(ContainerBuilder $container, LoaderInterface $loader, array $config)
Expand All @@ -52,4 +65,74 @@ private function loadAop(ContainerBuilder $container, LoaderInterface $loader)

$loader->load('aop.xml');
}

private function createOperationRunnerDefinition(ContainerBuilder $container, $name, array $config)
{
if (array_key_exists('retry', $config)) {
return $this->createRetryOperationRunnerDefinition($container, $name, $config['retry']);
} elseif (array_key_exists('callback', $config)) {
return $this->createCallbackOperationRunnerDefinition($container, $name);
}

throw new \RuntimeException(sprintf(
'No valid operation runner found in %s',
implode(', ', array_keys($config))
));
}

private function createRetryOperationRunnerDefinition(ContainerBuilder $container, $name, array $config)
{
$decoratedRunnerName = $this->createOperationRunnerDefinition($container, $name.'.runner', $config['runner']);
$waitStrategyName = $this->createWaitStrategyDefinition($container, $name.'.wait_strategy', $config['strategy']);

$container->setDefinition($name, new Definition(RetryOperationRunner::class, [
new Reference($decoratedRunnerName),
new Reference($waitStrategyName),
]));

return $name;
}

private function createCallbackOperationRunnerDefinition(ContainerBuilder $container, $name)
{
$container->setDefinition($name, new Definition(CallbackOperationRunner::class));

return $name;
}

private function createWaitStrategyDefinition(ContainerBuilder $container, $name, array $config)
{
if (array_key_exists('max', $config)) {
return $this->createMaxWaitStrategyDefinition($container, $name, $config['max']);
} elseif (array_key_exists('exponential', $config)) {
return $this->createExponentialWaitStrategyDefinition($container, $name, $config['exponential']);
}

throw new \RuntimeException(sprintf(
'No valid wait strategy found in %s',
implode(', ', array_keys($config))
));
}

private function createMaxWaitStrategyDefinition(ContainerBuilder $container, $name, array $config)
{
$decoratedStrategyName = $this->createWaitStrategyDefinition($container, $name.'.strategy', $config['strategy']);

$container->setDefinition($name, new Definition(Max::class, [
new Reference($decoratedStrategyName),
$config['count'],
]));

return $name;
}

private function createExponentialWaitStrategyDefinition(ContainerBuilder $container, $name, array $config)
{
$container->setDefinition($name, new Definition(Exponential::class, [
new Reference($config['waiter']),
$config['exponent'],
]));

return $name;
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="tolerance.waiter.null" class="Tolerance\Waiter\NullWaiter" />
<service id="tolerance.waiter.sleep" class="Tolerance\Waiter\SleepWaiter" />
</services>
</container>

This file was deleted.

Expand Up @@ -5,12 +5,8 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="app.api.client_operation_runner" class="Tolerance\Operation\Runner\OperationRunner">
<factory class="Tolerance\Bridge\Symfony\Bundle\AppBundle\Operation\ThirdPartyRunnerFactory" method="create" />
</service>

<service id="app.api.client" class="Tolerance\Bridge\Symfony\Bundle\AppBundle\ThirdParty\StepByStepHookApiClient">
<tag name="tolerance.operation_wrapper" methods="get" runner="app.api.client_operation_runner" />
<tag name="tolerance.operation_wrapper" methods="get" runner="tolerance.operation_runners.default" />
</service>
</services>
</container>

0 comments on commit e70998a

Please sign in to comment.