Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: CI
on:
pull_request:
push:
push:
branches: [ "3.x" ]
schedule:
- cron: '0 0 * * *'
Expand Down Expand Up @@ -30,7 +30,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: '${{ matrix.php-versions }}'
extensions: mbstring,intl,mongodb
extensions: mbstring,intl,mongodb,grpc
tools: composer:v2,flex
- name: Install dependencies
uses: ramsey/composer-install@v3
Expand Down
139 changes: 136 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ baldinof_road_runner:
kernel_reboot:
strategy: max_jobs
max_jobs: 1000 # maximum number of request
max_jobs_dispersion: 0.2 # dispersion 20% used to prevent simultaneous reboot of all active workers (kernel will rebooted between 800 and 1000 requests)
max_jobs_dispersion: 0.2 # dispersion 20% used to prevent simultaneous reboot of all active workers (kernel will rebooted between 800 and 1000 requests)
```

You can combine reboot strategies:
Expand Down Expand Up @@ -219,9 +219,9 @@ class Calculator implements CalculatorInterface

## KV caching

Roadrunner has a KV (Key-Value) plugin that can be used to cache data between requests.
Roadrunner has a KV (Key-Value) plugin that can be used to cache data between requests.

To use it, refer to the configuration reference at https://roadrunner.dev/docs/kv-overview.
To use it, refer to the configuration reference at https://roadrunner.dev/docs/kv-overview.
This requires the `spiral/roadrunner-kv`, `spiral/goridge` and `symfony/cache` composer dependencies. Basic configuration example:

Example configuration:
Expand Down Expand Up @@ -260,6 +260,139 @@ framework:
adapter: cache.adapter.roadrunner.kv_example
```

## Temporal Integration

Ability to serve temporal request from roadrunner and provide temporal client.

To enable integration, please install `ext-grpc` and `temporal/sdk`

```bash
composer require temporal/sdk
```

Complete list of configuration

```yaml
baldinof_road_runner:
temporal:
data_converters:
- Temporal\DataConverter\NullConverter
- Temporal\DataConverter\BinaryConverter
- Temporal\DataConverter\ProtoJsonConverter
- Temporal\DataConverter\JsonConverter
default_client: default
clients:
default:
namespace: default
address: 'localhost:7233'
Comment on lines +285 to +287

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be also good to have the ability to set grpc timeout (similar way to how it's in vanta bundle)

  clients:
      default:
          grpcContext:
              timeout:
                  value: 12

crt: <string>
client_key: <string>
client_pem: <sstring>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
client_pem: <sstring>
client_pem: <string>

override_server_name: <string>
identity: <string>
interceptors: { } # array of service id

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
interceptors: { } # array of service id
interceptors: [ ] # array of service id

query_reject_condition: <enum>
workers:
default:
queue: default
exception_interceptor: temporal.exception_interceptor
default_interceptors: true
options:
max_concurrent_activity_execution_size: <int>
worker_activities_per_second: <float>
max_concurrent_local_activity_execution_size: <int>
worker_local_activities_per_second: <int>
task_queue_activities_per_second: <int>
max_concurrent_activity_task_pollers: <int>
max_concurrent_workflow_task_execution_size: <int>
max_concurrent_workflow_task_pollers: <int>
sticky_schedule_to_start_timeout: <int>
worker_stop_timeout: <int>
enable_session_worker: <bool>
session_resource_id: <string>
max_concurrent_session_execution_size: <int>
interceptors: { } # array of service id

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's array, it should be array 🙂

Suggested change
interceptors: { } # array of service id
interceptors: [ ] # array of service id

```

To register a workflow and activity, you just need to tag actual class with `#[WorkflowInterface]` and `#[ActivityInterface]`

```php
<?php

declare(strict_types=1);

namespace App\Temporal;

use Temporal\Activity\ActivityOptions;
use Temporal\Workflow;
use Temporal\Workflow\WorkflowInterface;
use Temporal\Workflow\WorkflowMethod;

#[WorkflowInterface]
class ExampleWorkflow
Comment on lines +331 to +332

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably #[AssignToWorker] should be mentioned here as well

{
/**
* @var ExampleActivity
*/
private $exampleActivity;

public function __construct()
{
$this->exampleActivity = Workflow::newActivityStub(
ExampleActivity::class,
ActivityOptions::new()->withStartToCloseTimeout(2000)
);
}

#[WorkflowMethod]
public function greet(string $name): \Generator
{
return yield $this->exampleActivity->composeGreet($name);
}
}
```

```php
<?php

declare(strict_types=1);

namespace App\Temporal;

use App\Service\MyService;
use Temporal\Activity\ActivityInterface;
use Temporal\Activity\ActivityMethod;

#[ActivityInterface]
class ExampleActivity
{
public function __construct(private MyService $myService)
{
}

#[ActivityMethod]
public function composeGreet(string $name): string
{
return "Hello {$name}!";
}
}
```

Dependencies defined in the Activity constructor will be automatically resolved from the dependency injection container.

To use temporal default temporal client that listed in config you can type `WorkflowClientInterface` in constructor

```php
final class Example
{
public function __constructor(private readonly WorkflowClientInterface $workflowClient)
{}
}
```

Additional clients are registered under the service id `temporal.client.$name`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be pleasant to have the ability to debug stuff, similar to as it's already in vanta bundle:

debug:temporal:clients - list registered clients

image

  • debug:temporal:schedule-clients - list registered schedule clients
  • debug:temporal:workflows - list registered workflows
  • debug:temporal:activities - list registered activities
  • debug:temporal:workers - list registered workers


## Usage with Docker

```Dockerfile
Expand Down
7 changes: 5 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
"suggest": {
"nyholm/psr7": "For a super lightweight PSR-7/17 implementation",
"spiral/roadrunner-cli": "For easy installation of RoadRunner",
"symfony/proxy-manager-bridge": "For doctrine re-connection implementation"
"symfony/proxy-manager-bridge": "For doctrine re-connection implementation",
"temporal/sdk": "For using the temporal feature",
"ext-grpc": "When using a temporal client"
},
"require-dev": {
"symfony/var-dumper": "^6.0 || ^7.0",
Expand All @@ -57,7 +59,8 @@
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.6",
"phpspec/prophecy": "^1.11",
"phpspec/prophecy-phpunit": "^2.0"
"phpspec/prophecy-phpunit": "^2.0",
"temporal/sdk": "^2.10"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Baldinof , since temporal sdk dependency is optional (required only in -dev), right now there are no constraints about client's version. In other words, roadrunner-bundle will be installed regardless of temporal/sdk version, which might not be compatible.

I know of the following approach to this problem - one could write the required constraints in the "conflict" section so that even though dependency is not required, it'll still be bound by version constraints (in case it'll be required by the client code)

},
Comment on lines +63 to 64

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any particular reason for not using ^2.0?

"conflict": {
"doctrine/doctrine-bundle": "<2.1.1",
Expand Down
7 changes: 7 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
use Spiral\RoadRunner\Worker as RoadRunnerWorker;
use Spiral\RoadRunner\WorkerInterface as RoadRunnerWorkerInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Temporal\Exception\ExceptionInterceptor;
use Temporal\Workflow\WorkflowInterface;

return static function (ContainerConfigurator $container) {
$container->parameters()
Expand Down Expand Up @@ -149,4 +151,9 @@
service(InternalGrpcWorker::class),
]);
}

if (class_exists(WorkflowInterface::class)) {
$services->set('temporal.exception_interceptor', ExceptionInterceptor::class)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't ExceptionInterceptorInterface be used instead?

Suggested change
$services->set('temporal.exception_interceptor', ExceptionInterceptor::class)
$services->set('temporal.exception_interceptor', ExceptionInterceptorInterface::class)

->factory([ExceptionInterceptor::class, 'createDefault']);
}
};
7 changes: 7 additions & 0 deletions src/BaldinofRoadRunnerBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
use Baldinof\RoadRunnerBundle\DependencyInjection\CompilerPass\InterceptorCompilerPass;
use Baldinof\RoadRunnerBundle\DependencyInjection\CompilerPass\MiddlewareCompilerPass;
use Baldinof\RoadRunnerBundle\DependencyInjection\CompilerPass\RemoveConfigureVarDumperListenerPass;
use Baldinof\RoadRunnerBundle\DependencyInjection\CompilerPass\TemporalCompilerPass;
use Spiral\RoadRunner\GRPC\ServiceInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Temporal\Client\WorkflowClientInterface;
use Temporal\Workflow\WorkflowInterface;

final class BaldinofRoadRunnerBundle extends Bundle
{
Expand All @@ -24,5 +27,9 @@ public function build(ContainerBuilder $container): void
$container->addCompilerPass(new GrpcServiceCompilerPass());
$container->addCompilerPass(new InterceptorCompilerPass());
}

if (interface_exists(WorkflowClientInterface::class) && class_exists(WorkflowInterface::class)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think one check would be enough
It shouldn't be possible that one of these names exists, while the other doesn't

$container->addCompilerPass(new TemporalCompilerPass());
}
}
}
Loading