spiral / temporal-bridge
Temporal integration package for Spiral Framework
Installs: 4 828
Dependents: 0
Suggesters: 0
Security: 0
Stars: 8
Watchers: 4
Forks: 5
Open Issues: 12
Requires
- php: ^8.1
- nette/php-generator: ^4.0
- spiral/attributes: ^2.8 || ^3.0
- spiral/boot: ^3.0
- spiral/roadrunner-bridge: ^2.0 || ^3.0
- spiral/tokenizer: ^3.0
- temporal/sdk: ^1.3 || ^2.0
Requires (Dev)
- spiral/framework: ^3.0
- spiral/testing: ^2.0
- vimeo/psalm: ^4.9
This package is auto-updated.
Last update: 2023-09-11 01:21:59 UTC
README
Temporal is the simple, scalable open source way to write and run reliable cloud applications.
Temporal screencasts
Requirements
Make sure that your server is configured with following PHP version and extensions:
- PHP 8.1+
- Spiral framework 3.0+
Installation
You can install the package via composer:
composer require spiral/temporal-bridge
After package install you need to register bootloader from the package.
protected const LOAD = [ // ... \Spiral\TemporalBridge\Bootloader\TemporalBridgeBootloader::class, ];
Note: if you are using
spiral-packages/discoverer
, you don't need to register bootloader by yourself.
Configuration
The package is already configured by default, use these features only if you need to change the default configuration.
The package provides the ability to configure address
, namespace
, defaultWorker
, workers
parameters.
Create file app/config/temporal.php
and configure options. For example:
declare(strict_types=1); use Temporal\Worker\WorkerFactoryInterface; use Temporal\Worker\WorkerOptions; return [ 'address' => env('TEMPORAL_ADDRESS', 'localhost:7233'), 'namespace' => 'App\\Workflow', 'defaultWorker' => WorkerFactoryInterface::DEFAULT_TASK_QUEUE, 'workers' => [ 'workerName' => WorkerOptions::new() ], ];
RoadRunner configuration
Add temporal
plugin section in your RoadRunner rr.yaml
config:
temporal: address: localhost:7233 activities: num_workers: 10
Temporal
You can run temporal server via docker by using the example below:
You can find official docker compose files here https://github.com/temporalio/docker-compose
version: '3.5' services: postgresql: container_name: temporal-postgresql image: postgres:13 environment: POSTGRES_PASSWORD: temporal POSTGRES_USER: temporal ports: - 5432:5432 temporal: container_name: temporal image: temporalio/auto-setup:1.14.2 depends_on: - postgresql environment: - DB=postgresql - DB_PORT=5432 - POSTGRES_USER=temporal - POSTGRES_PWD=temporal - POSTGRES_SEEDS=postgresql - DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development.yaml ports: - 7233:7233 volumes: - ./temporal:/etc/temporal/config/dynamicconfig temporal-web: container_name: temporal-web image: temporalio/web:1.13.0 depends_on: - temporal environment: - TEMPORAL_GRPC_ENDPOINT=temporal:7233 - TEMPORAL_PERMIT_WRITE_API=true ports: - 8088:8088
Please make sure that a configuration file for temporal server exists.
mkdir temporal && touch temporal/development.yaml
Creating workflow
You are able to create a new workflow via console command:
php app.php temporal:make-workflow MySuperWorkflow
The command will generate the following files with default namespace App\Workflow
:
project/
src/
Workflow/
MySuperWorkflow/
MySuperWorkflowInterface
MySuperWorkflow
You can redefine default namespace via
app/config/temporal.php
config file.
Workflow with activity classes
php app.php temporal:make-workflow MySuperWorkflow --with-activity
project/
src/
Workflow/
MySuperWorkflow/
...
MySuperWorkflowHandlerInterface
MySuperWorkflowHandler
Workflow with handler classes
php app.php temporal:make-workflow MySuperWorkflow --with-handler
project/
src/
Workflow/
MySuperWorkflow/
...
MySuperWorkflowActivityInterface
MySuperWorkflowActivity
You can mixin options
--with-activity --with-handler
Workflow method name definition
temporal:make-workflow PingSite -m ping
#[WorkflowInterface] interface PingSiteWorkflowInterface { #[WorkflowMethod] public function ping(string $name): \Generator; }
Workflow method parameters definition
temporal:make-workflow PingSite ... -p url:string -p name:string
#[WorkflowInterface] interface PingSiteWorkflowInterface { #[WorkflowMethod] public function ping(string $url, string $name): \Generator; }
Workflow query methods definition
temporal:make-workflow PingSite ... -r getStatusCode -r getHeaders:array
#[WorkflowInterface] interface PingSiteWorkflowInterface { #[WorkflowMethod] public function ping(...): \Generator; #[QueryMethod] function getStatusCode(): string; #[QueryMethod] function getHeaders(): array; }
Workflow with namespace definition
temporal:make-workflow Domain\\MyPackage\\MoneyTransfer ... -s withdraw -s deposit
Creating workflow from presets
The package provides the ability to create predefined Workflows. Presets for the package can be provided via third-party packages.
Example of usage
php app.php temporal:make-preset subscribtion-trial CustomerTrialSubscription
A preset will create all necessary classes.
You can show list of available presets using the console command
php app.php temporal:presets
Creating a preset
A preset class should implement Spiral\TemporalBridge\Preset\PresetInterface
and should have an
attribute Spiral\TemporalBridge\Preset\WorkflowPreset
use Spiral\TemporalBridge\Generator\WorkflowInterfaceGenerator; use Spiral\TemporalBridge\Generator\SignalWorkflowGenerator; use Spiral\TemporalBridge\Generator\ActivityInterfaceGenerator; use Spiral\TemporalBridge\Generator\ActivityGenerator; use Spiral\TemporalBridge\Generator\HandlerInterfaceGenerator; use Spiral\TemporalBridge\Generator\HandlerGenerator; use Spiral\TemporalBridge\Preset\PresetInterface; use Spiral\TemporalBridge\Preset\WorkflowPreset; #[WorkflowPreset('signal')] final class SignalWorkflow implements PresetInterface { public function getDescription(): ?string { return 'Workflow with signals'; } public function generators(Context $context): array { $generators = [ 'WorkflowInterface' => new WorkflowInterfaceGenerator(), 'Workflow' => new SignalWorkflowGenerator(), ]; if ($context->hasActivity()) { $generators = \array_merge($generators, [ 'ActivityInterface' => new ActivityInterfaceGenerator(), 'Activity' => new ActivityGenerator(), ]); } if ($context->hasHandler()) { $generators = \array_merge($generators, [ 'HandlerInterface' => new HandlerInterfaceGenerator(), 'Handler' => new HandlerGenerator(), ]); } return $generators; } }
Please note: If you are using WorkflowPreset
you have to add a directory with presets to tokenizer.
use Spiral\Tokenizer\Bootloader\TokenizerBootloader; class MyBootloader extends \Spiral\Boot\Bootloader\Bootloader { protected const DEPENDENCIES = [ TokenizerBootloader::class ]; public function start(TokenizerBootloader $tokenizer) { $tokenizer->addDirectory(__DIR__..'/presets'); } }
You can omit WorkflowPreset
attribute and register your preset via Bootloader
use Spiral\TemporalBridge\Preset\PresetRegistryInterface; class MyBootloader extends \Spiral\Boot\Bootloader\Bootloader { public function start(PresetRegistryInterface $registry) { $registry->register('signal', new SignalWorkflow()); } }
Workflow signal methods definition
temporal:make-workflow MoneyTransfer ... -s withdraw -s deposit
#[WorkflowInterface] interface MoneyTransferWorkflowInterface { #[WorkflowMethod] public function ping(...): \Generator; #[SignalMethod] function withdraw(): void; #[SignalMethod] function deposit(): void; }
You may discover available workflow samples here
Usage
Configure temporal address via env variables .env
TEMPORAL_ADDRESS=127.0.0.1:7233
Running workflow
class PingController { public function ping(StoreRequest $request, PingSiteHandler $handler): void { $this->handler->handle( $request->url, $request->name ); } }
Running workflows and activities with different task queue
Add a Spiral\TemporalBridge\Attribute\AssignWorker
attribute to your Workflow or Activity with the name
of the worker.
This Workflow or Activity will be processed by the specified worker.
Workflow example:
<?php declare(strict_types=1); namespace App\Workflow; use Spiral\TemporalBridge\Attribute\AssignWorker; use Temporal\Workflow\WorkflowInterface; #[AssignWorker(name: 'worker1')] #[WorkflowInterface] interface MoneyTransferWorkflowInterface { #[WorkflowMethod] public function transfer(...): \Generator; #[SignalMethod] function withdraw(): void; #[SignalMethod] function deposit(): void; }
Activity example:
<?php declare(strict_types=1); namespace App\Workflow; use Spiral\TemporalBridge\Attribute\AssignWorker; use Temporal\Activity\ActivityInterface; use Temporal\Activity\ActivityMethod; #[AssignWorker(name: 'worker1')] #[ActivityInterface(...)] interface MoneyTransferActivityInterface { #[ActivityMethod] public function transfer(...): int; #[ActivityMethod] public function cancel(...): bool; }
You can configure worker options via config file app/config/temporal.php
declare(strict_types=1); use Temporal\Worker\WorkerFactoryInterface; use Temporal\Worker\WorkerOptions; return [ //... 'workers' => [ 'worker1' => WorkerOptions::new() ], ];
Or via application bootloader
<?php declare(strict_types=1); namespace App\Bootloader; use Spiral\Bootloader\DomainBootloader; use Spiral\TemporalBridge\WorkersRegistryInterface;use Temporal\Worker\WorkerOptions; class AppBootloader extends DomainBootloader { public function init(WorkersRegistryInterface $workersRegistry): void { $workersRegistry->register( 'worker1', WorkerOptions::new()->... ); } }
Custom data converters
Temporal SDK hsa an ability to define custom data converters
By default it uses the following list of data converters:
Temporal\DataConverter\NullConverter
Temporal\DataConverter\BinaryConverter
Temporal\DataConverter\ProtoJsonConverter
Temporal\DataConverter\JsonConverter
If you want to specify custom list of data converters you need to bind your own implementation for
Temporal\DataConverter\DataConverterInterface
via container.
use Spiral\Boot\Bootloader\Bootloader; use Temporal\DataConverter\DataConverter; use Temporal\DataConverter\DataConverterInterface; class AppBootloader extends Bootloader { protected const SINGLETONS = [ DataConverterInterface::class => [self::class, 'initDataConverter'], ]; protected function initDataConverter(): DataConverterInterface { return new DataConverter( new \Temporal\DataConverter\NullConverter(), new \Temporal\DataConverter\BinaryConverter(), new \App\DataConverter\JmsSerializerConverter(), new \Temporal\DataConverter\ProtoJsonConverter(), new \Temporal\DataConverter\JsonConverter(), ); } }
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.