From 00dad7f2a93eedd20b8787a4460abf940f2afb49 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 12 Aug 2025 15:08:44 +0400 Subject: [PATCH 01/15] feat: add VersioningBehavior enum to manage workflow versioning --- src/Worker/Versioning/VersioningBehavior.php | 63 ++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/Worker/Versioning/VersioningBehavior.php diff --git a/src/Worker/Versioning/VersioningBehavior.php b/src/Worker/Versioning/VersioningBehavior.php new file mode 100644 index 00000000..db9c07f9 --- /dev/null +++ b/src/Worker/Versioning/VersioningBehavior.php @@ -0,0 +1,63 @@ + Date: Thu, 14 Aug 2025 16:26:47 +0400 Subject: [PATCH 02/15] refactor(tests): update Acceptance Tests Worker constructor to accept options as an array callable --- tests/Acceptance/App/Attribute/Worker.php | 3 ++- tests/Acceptance/App/Feature/WorkerFactory.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Acceptance/App/Attribute/Worker.php b/tests/Acceptance/App/Attribute/Worker.php index 7530e98e..81980692 100644 --- a/tests/Acceptance/App/Attribute/Worker.php +++ b/tests/Acceptance/App/Attribute/Worker.php @@ -19,11 +19,12 @@ final class Worker { /** + * @param array|null $options Callable that returns {@see WorkerOptions} * @param array|null $pipelineProvider Callable that returns {@see PipelineProvider} * @param array|null $logger Callable that returns {@see LoggerInterface} */ public function __construct( - public readonly ?WorkerOptions $options = null, + public readonly ?array $options = null, public readonly ?array $pipelineProvider = null, public readonly ?array $logger = null, ) {} diff --git a/tests/Acceptance/App/Feature/WorkerFactory.php b/tests/Acceptance/App/Feature/WorkerFactory.php index f8ad4d26..b6c007a7 100644 --- a/tests/Acceptance/App/Feature/WorkerFactory.php +++ b/tests/Acceptance/App/Feature/WorkerFactory.php @@ -36,7 +36,7 @@ public function createWorker( ...$feature->activities, ); if ($attr !== null) { - $options = $attr->options; + $attr->options === null or $options = $this->invoker->invoke($attr->options); $attr->pipelineProvider === null or $interceptorProvider = $this->invoker->invoke($attr->pipelineProvider); $attr->logger === null or $logger = $this->invoker->invoke($attr->logger); } From b763c126865d3679d1265a94e2e26dd84ae4c1b6 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Fri, 15 Aug 2025 14:59:56 +0400 Subject: [PATCH 03/15] chore: update Temporal Dev to ^1.4.1 --- dload.xml | 2 +- tests/Acceptance/App/Runtime/TemporalStarter.php | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dload.xml b/dload.xml index c4fb7013..38ba76f6 100644 --- a/dload.xml +++ b/dload.xml @@ -5,7 +5,7 @@ > - + diff --git a/tests/Acceptance/App/Runtime/TemporalStarter.php b/tests/Acceptance/App/Runtime/TemporalStarter.php index f987db5a..eabd35f5 100644 --- a/tests/Acceptance/App/Runtime/TemporalStarter.php +++ b/tests/Acceptance/App/Runtime/TemporalStarter.php @@ -26,8 +26,13 @@ public function start(): void $this->environment->startTemporalServer( parameters: [ + '--dynamic-config-value', 'frontend.enableUpdateWorkflowExecution=true', + '--dynamic-config-value', 'frontend.enableUpdateWorkflowExecutionAsyncAccepted=true', + '--dynamic-config-value', 'frontend.enableExecuteMultiOperation=true', '--dynamic-config-value', 'system.enableEagerWorkflowStart=true', '--dynamic-config-value', 'frontend.activityAPIsEnabled=true', + '--dynamic-config-value', 'frontend.workerVersioningWorkflowAPIs=true', + '--dynamic-config-value', 'system.enableDeploymentVersions=true', ], searchAttributes: [ 'foo' => ValueType::Text->value, From 53b64c0cc6ab9a4e76384ee4028924ab95ad74a4 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Fri, 15 Aug 2025 17:50:14 +0400 Subject: [PATCH 04/15] feat: add WorkerDeploymentOptions class for configuring Worker Versioning --- src/Worker/Versioning/VersioningBehavior.php | 2 +- .../Versioning/WorkerDeploymentOptions.php | 77 +++++++++++++++++++ .../Versioning/WorkerDeploymentVersion.php | 72 +++++++++++++++++ src/Worker/WorkerOptions.php | 40 +++++++++- tests/Unit/DTO/WorkerOptionsTestCase.php | 40 +++++++++- 5 files changed, 225 insertions(+), 6 deletions(-) create mode 100644 src/Worker/Versioning/WorkerDeploymentOptions.php create mode 100644 src/Worker/Versioning/WorkerDeploymentVersion.php diff --git a/src/Worker/Versioning/VersioningBehavior.php b/src/Worker/Versioning/VersioningBehavior.php index db9c07f9..3c47fc40 100644 --- a/src/Worker/Versioning/VersioningBehavior.php +++ b/src/Worker/Versioning/VersioningBehavior.php @@ -1,6 +1,6 @@ useVersioning = false; + $this->version = null; + $this->defaultVersioningBehavior = VersioningBehavior::Unspecified; + } + + public static function new(): self + { + return new self(); + } + + /** + * If set, opts this worker into the Worker Deployment Versioning feature. + * + * It will only operate on workflows it claims to be compatible with. + * You must also call {@see self::withVersion()} if this flag is true. + */ + public function withUseVersioning(bool $value): self + { + /** @see self::$useVersioning */ + return $this->with('useVersioning', $value); + } + + /** + * Sets the version of the worker deployment. + * + * @param non-empty-string|WorkerDeploymentVersion $version + */ + public function withVersion(string|WorkerDeploymentVersion $version): self + { + /** @see self::$version */ + return $this->with('version', (string) $version); + } + + /** + * Sets the default versioning behavior for this worker. + * + */ + public function withDefaultVersioningBehavior(VersioningBehavior $behavior): self + { + /** @see self::$defaultVersioningBehavior */ + return $this->with('defaultVersioningBehavior', $behavior); + } +} diff --git a/src/Worker/Versioning/WorkerDeploymentVersion.php b/src/Worker/Versioning/WorkerDeploymentVersion.php new file mode 100644 index 00000000..a9d13281 --- /dev/null +++ b/src/Worker/Versioning/WorkerDeploymentVersion.php @@ -0,0 +1,72 @@ +deploymentName}.{$this->buildId}"; + } +} diff --git a/src/Worker/WorkerOptions.php b/src/Worker/WorkerOptions.php index 2a287496..3a02a65a 100644 --- a/src/Worker/WorkerOptions.php +++ b/src/Worker/WorkerOptions.php @@ -18,10 +18,21 @@ use Temporal\Internal\Marshaller\Type\EnumValueType; use Temporal\Internal\Marshaller\Type\NullableType; use Temporal\Internal\Support\DateInterval; +use Temporal\Worker\Versioning\WorkerDeploymentOptions; use Temporal\Workflow; /** * @psalm-import-type DateIntervalValue from DateInterval + * + * todo: see {@see AutoscalingPollerBehavior} + * todo: see {@see SimplePollerBehavior} + * todo: see {@see \Temporal\Api\Sdk\V1\WorkerConfig} + * todo: see {@see \Temporal\Api\Taskqueue\V1\TaskQueueConfig} + * todo: see {@see \Temporal\Api\Worker\V1\PluginInfo} + * todo: see {@see \Temporal\Api\Worker\V1\WorkerInfo} + * todo: see {@see \Temporal\Api\Worker\V1\WorkerPollerInfo} + * + * @see WorkerOptions */ class WorkerOptions { @@ -295,7 +306,7 @@ class WorkerOptions * and is used to provide a unique identifier for a set of worker code, and is necessary * to opt in to the Worker Versioning feature. See {@see self::$useBuildIDForVersioning}. * - * @internal Experimental + * @deprecated */ #[Marshal(name: 'BuildID')] public string $buildID = ''; @@ -305,12 +316,20 @@ class WorkerOptions * It will only operate on workflows it claims to be compatible with. * You must set {@see self::$buildID} if this flag is true. * - * @internal Experimental * @note Cannot be enabled at the same time as {@see self::$enableSessionWorker} + * @deprecated */ #[Marshal(name: 'UseBuildIDForVersioning')] public bool $useBuildIDForVersioning = false; + /** + * Optional: If set it configures Worker Versioning for this worker. + * + * @internal Experimental. + */ + #[Marshal(name: 'DeploymentOptions')] + public WorkerDeploymentOptions $deploymentOptions; + #[Pure] public static function new(): self { @@ -802,7 +821,7 @@ public function withDisableRegistrationAliasing(bool $disable = true): self * * @param non-empty-string $buildID * - * @internal Experimental + * @deprecated */ #[Pure] public function withBuildID(string $buildID): self @@ -817,8 +836,8 @@ public function withBuildID(string $buildID): self * It will only operate on workflows it claims to be compatible with. * You must set {@see self::$buildID} if this flag is true. * - * @internal Experimental * @note Cannot be enabled at the same time as {@see self::$enableSessionWorker} + * @deprecated */ #[Pure] public function withUseBuildIDForVersioning(bool $useBuildIDForVersioning = true): self @@ -827,4 +846,17 @@ public function withUseBuildIDForVersioning(bool $useBuildIDForVersioning = true $self->useBuildIDForVersioning = $useBuildIDForVersioning; return $self; } + + /** + * Optional: If set it configures Worker Versioning for this worker. + * + * @internal Experimental. + */ + #[Pure] + public function withDeploymentOptions(WorkerDeploymentOptions $deploymentOptions): self + { + $self = clone $this; + $self->deploymentOptions = $deploymentOptions; + return $self; + } } diff --git a/tests/Unit/DTO/WorkerOptionsTestCase.php b/tests/Unit/DTO/WorkerOptionsTestCase.php index d2b80cec..20330273 100644 --- a/tests/Unit/DTO/WorkerOptionsTestCase.php +++ b/tests/Unit/DTO/WorkerOptionsTestCase.php @@ -11,6 +11,9 @@ namespace Temporal\Tests\Unit\DTO; +use Temporal\Worker\Versioning\VersioningBehavior; +use Temporal\Worker\Versioning\WorkerDeploymentOptions; +use Temporal\Worker\Versioning\WorkerDeploymentVersion; use Temporal\Worker\WorkerOptions; use Temporal\Worker\WorkflowPanicPolicy; @@ -50,10 +53,45 @@ public function testMarshalling(): void 'MaxConcurrentEagerActivityExecutionSize' => 0, 'DisableRegistrationAliasing' => false, 'BuildID' => "", + 'DeploymentOptions' => null, 'UseBuildIDForVersioning' => false, ]; - $this->assertSame($expected, $this->marshal($dto)); + $this->assertEquals($expected, $this->marshal($dto)); + } + + public function testDeploymentOptionsNoUse(): void + { + $dto = new WorkerOptions(); + $result = $dto->withDeploymentOptions( + WorkerDeploymentOptions::new() + ->withUseVersioning(false), + ); + + self::assertNotSame($dto, $result); + $options = $this->marshal($result)['DeploymentOptions']; + + self::assertFalse($options['UseVersioning']); + self::assertSame(VersioningBehavior::Unspecified->value, $options['DefaultVersioningBehavior']); + self::assertNull($options['Version']); + } + + public function testDeploymentOptionsUseVersion(): void + { + $dto = new WorkerOptions(); + $result = $dto->withDeploymentOptions( + WorkerDeploymentOptions::new() + ->withUseVersioning(true) + ->withVersion(WorkerDeploymentVersion::new('foo', 'bar')) + ->withDefaultVersioningBehavior(VersioningBehavior::AutoUpgrade), + ); + + self::assertNotSame($dto, $result); + $options = $this->marshal($result)['DeploymentOptions']; + + self::assertTrue($options['UseVersioning']); + self::assertSame(VersioningBehavior::AutoUpgrade->value, $options['DefaultVersioningBehavior']); + self::assertSame('foo.bar', $options['Version']); } public function testMaxConcurrentActivityExecutionSize(): void From cac036d23bd357b23e447e6cc4655d286e479498 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 15 Aug 2025 19:45:14 +0000 Subject: [PATCH 05/15] style(php-cs-fixer): fix coding standards --- src/Worker/Versioning/WorkerDeploymentVersion.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Worker/Versioning/WorkerDeploymentVersion.php b/src/Worker/Versioning/WorkerDeploymentVersion.php index a9d13281..efa29c96 100644 --- a/src/Worker/Versioning/WorkerDeploymentVersion.php +++ b/src/Worker/Versioning/WorkerDeploymentVersion.php @@ -56,7 +56,7 @@ public static function fromString(string $canonicalString): self { $parts = \explode('.', $canonicalString, 2); \count($parts) === 2 or throw new InvalidArgumentException( - "Invalid canonical string format. Expected 'deploymentName.buildId'" + "Invalid canonical string format. Expected 'deploymentName.buildId'", ); return new self($parts[0], $parts[1]); From 52227b04debe5e3359208f75799cad801297120e Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 18 Aug 2025 11:48:19 +0400 Subject: [PATCH 06/15] test(acceptance): reorganize versioning tests --- .../{ => Classic}/Versioning-default.json | 49 ++++++++------- .../{ => Classic}/Versioning-v1.json | 63 ++++++++++--------- .../{VersioningTest.php => ClassicTest.php} | 19 +++--- 3 files changed, 67 insertions(+), 64 deletions(-) rename tests/Acceptance/Extra/Versioning/{ => Classic}/Versioning-default.json (66%) rename tests/Acceptance/Extra/Versioning/{ => Classic}/Versioning-v1.json (76%) rename tests/Acceptance/Extra/Versioning/{VersioningTest.php => ClassicTest.php} (70%) diff --git a/tests/Acceptance/Extra/Versioning/Versioning-default.json b/tests/Acceptance/Extra/Versioning/Classic/Versioning-default.json similarity index 66% rename from tests/Acceptance/Extra/Versioning/Versioning-default.json rename to tests/Acceptance/Extra/Versioning/Classic/Versioning-default.json index 2457bebc..768be5a7 100644 --- a/tests/Acceptance/Extra/Versioning/Versioning-default.json +++ b/tests/Acceptance/Extra/Versioning/Classic/Versioning-default.json @@ -2,37 +2,38 @@ "events": [ { "eventId": "1", - "eventTime": "2025-05-10T11:08:43.136190100Z", + "eventTime": "2025-08-18T07:43:35.810544500Z", "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", - "taskId": "1048687", + "taskId": "1048849", "workflowExecutionStartedEventAttributes": { "workflowType": { - "name": "Extra_Versioning_Versioning" + "name": "Extra_Versioning_Classic" }, "taskQueue": { - "name": "Temporal\\Tests\\Acceptance\\Extra\\Workflow\\Versioning", + "name": "Temporal\\Tests\\Acceptance\\Extra\\Versioning\\Classic", "kind": "TASK_QUEUE_KIND_NORMAL" }, "workflowExecutionTimeout": "60s", "workflowRunTimeout": "60s", "workflowTaskTimeout": "10s", - "originalExecutionRunId": "0196b9e2-1300-72e6-a156-df5a49685436", - "identity": "42484@roxblnfk-book", - "firstExecutionRunId": "0196b9e2-1300-72e6-a156-df5a49685436", + "originalExecutionRunId": "0198bc22-3782-784e-afe3-9a4f11c76556", + "identity": "14828@roxblnfk-book", + "firstExecutionRunId": "0198bc22-3782-784e-afe3-9a4f11c76556", "attempt": 1, - "workflowExecutionExpirationTime": "2025-05-10T11:09:43.136Z", + "workflowExecutionExpirationTime": "2025-08-18T07:44:35.810Z", "firstWorkflowTaskBackoff": "0s", - "workflowId": "b2c07b59-cb14-4e46-8650-0da7f50d6f0a" + "workflowId": "4a4cefaa-3615-4571-969a-d4e5eb489361", + "priority": {} } }, { "eventId": "2", - "eventTime": "2025-05-10T11:08:43.136190100Z", + "eventTime": "2025-08-18T07:43:35.810544500Z", "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", - "taskId": "1048688", + "taskId": "1048850", "workflowTaskScheduledEventAttributes": { "taskQueue": { - "name": "Temporal\\Tests\\Acceptance\\Extra\\Workflow\\Versioning", + "name": "Temporal\\Tests\\Acceptance\\Extra\\Versioning\\Classic", "kind": "TASK_QUEUE_KIND_NORMAL" }, "startToCloseTimeout": "10s", @@ -41,30 +42,30 @@ }, { "eventId": "3", - "eventTime": "2025-05-10T11:08:43.138391700Z", + "eventTime": "2025-08-18T07:43:35.811577500Z", "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", - "taskId": "1048694", + "taskId": "1048856", "workflowTaskStartedEventAttributes": { "scheduledEventId": "2", - "identity": "Temporal\\Tests\\Acceptance\\Extra\\Workflow\\Versioning:44882e6a-8930-4f81-b987-69210c955363", - "requestId": "32693089-d58e-4058-be32-f10928a5ab0d", - "historySizeBytes": "372", + "identity": "Temporal\\Tests\\Acceptance\\Extra\\Versioning\\Classic:26718a3a-4a45-4758-8e30-bdd1396b3316", + "requestId": "ccb9955b-3ec0-4d4a-be57-6bee2810f268", + "historySizeBytes": "373", "workerVersion": { - "buildId": "f56858783d7ba07ee263f7740e6a9993" + "buildId": "9518ff0cb6b50ae08577a6c5fc24c4d7" } } }, { "eventId": "4", - "eventTime": "2025-05-10T11:08:43.160702200Z", + "eventTime": "2025-08-18T07:43:35.832299300Z", "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", - "taskId": "1048698", + "taskId": "1048860", "workflowTaskCompletedEventAttributes": { "scheduledEventId": "2", "startedEventId": "3", - "identity": "Temporal\\Tests\\Acceptance\\Extra\\Workflow\\Versioning:44882e6a-8930-4f81-b987-69210c955363", + "identity": "Temporal\\Tests\\Acceptance\\Extra\\Versioning\\Classic:26718a3a-4a45-4758-8e30-bdd1396b3316", "workerVersion": { - "buildId": "f56858783d7ba07ee263f7740e6a9993" + "buildId": "9518ff0cb6b50ae08577a6c5fc24c4d7" }, "sdkMetadata": { "langUsedFlags": [ @@ -78,9 +79,9 @@ }, { "eventId": "5", - "eventTime": "2025-05-10T11:08:43.160702200Z", + "eventTime": "2025-08-18T07:43:35.832299300Z", "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", - "taskId": "1048699", + "taskId": "1048861", "workflowExecutionCompletedEventAttributes": { "result": { "payloads": [ diff --git a/tests/Acceptance/Extra/Versioning/Versioning-v1.json b/tests/Acceptance/Extra/Versioning/Classic/Versioning-v1.json similarity index 76% rename from tests/Acceptance/Extra/Versioning/Versioning-v1.json rename to tests/Acceptance/Extra/Versioning/Classic/Versioning-v1.json index a9848983..394c8175 100644 --- a/tests/Acceptance/Extra/Versioning/Versioning-v1.json +++ b/tests/Acceptance/Extra/Versioning/Classic/Versioning-v1.json @@ -2,37 +2,38 @@ "events": [ { "eventId": "1", - "eventTime": "2025-05-10T10:58:43.335213500Z", + "eventTime": "2025-08-18T07:43:10.001148600Z", "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", - "taskId": "1048607", + "taskId": "1048829", "workflowExecutionStartedEventAttributes": { "workflowType": { - "name": "Extra_Versioning_Versioning" + "name": "Extra_Versioning_Classic" }, "taskQueue": { - "name": "Temporal\\Tests\\Acceptance\\Extra\\Workflow\\Versioning", + "name": "Temporal\\Tests\\Acceptance\\Extra\\Versioning\\Classic", "kind": "TASK_QUEUE_KIND_NORMAL" }, "workflowExecutionTimeout": "60s", "workflowRunTimeout": "60s", "workflowTaskTimeout": "10s", - "originalExecutionRunId": "0196b9d8-ec07-7341-b56b-620f48a7eba9", - "identity": "36696@roxblnfk-book", - "firstExecutionRunId": "0196b9d8-ec07-7341-b56b-620f48a7eba9", + "originalExecutionRunId": "0198bc21-d2b1-7244-bc88-22bdeaf2b880", + "identity": "40464@roxblnfk-book", + "firstExecutionRunId": "0198bc21-d2b1-7244-bc88-22bdeaf2b880", "attempt": 1, - "workflowExecutionExpirationTime": "2025-05-10T10:59:43.335Z", + "workflowExecutionExpirationTime": "2025-08-18T07:44:10.001Z", "firstWorkflowTaskBackoff": "0s", - "workflowId": "31a01a98-7406-4130-b462-4adf7026b1db" + "workflowId": "2f535201-af15-477b-8759-a258f174b246", + "priority": {} } }, { "eventId": "2", - "eventTime": "2025-05-10T10:58:43.335213500Z", + "eventTime": "2025-08-18T07:43:10.001148600Z", "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", - "taskId": "1048608", + "taskId": "1048830", "workflowTaskScheduledEventAttributes": { "taskQueue": { - "name": "Temporal\\Tests\\Acceptance\\Extra\\Workflow\\Versioning", + "name": "Temporal\\Tests\\Acceptance\\Extra\\Versioning\\Classic", "kind": "TASK_QUEUE_KIND_NORMAL" }, "startToCloseTimeout": "10s", @@ -41,30 +42,30 @@ }, { "eventId": "3", - "eventTime": "2025-05-10T10:58:43.336783600Z", + "eventTime": "2025-08-18T07:43:10.002204400Z", "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", - "taskId": "1048614", + "taskId": "1048836", "workflowTaskStartedEventAttributes": { "scheduledEventId": "2", - "identity": "Temporal\\Tests\\Acceptance\\Extra\\Workflow\\Versioning:b7e7bdac-03cb-40fe-bc45-896345d005f9", - "requestId": "29e46cf8-0357-40f7-9d82-e1b13b42dcdf", - "historySizeBytes": "375", + "identity": "Temporal\\Tests\\Acceptance\\Extra\\Versioning\\Classic:2046266d-f855-4ea4-8d8b-52e483c89c88", + "requestId": "cf12ae85-5485-45b0-9734-2fa20736b968", + "historySizeBytes": "367", "workerVersion": { - "buildId": "f56858783d7ba07ee263f7740e6a9993" + "buildId": "9518ff0cb6b50ae08577a6c5fc24c4d7" } } }, { "eventId": "4", - "eventTime": "2025-05-10T10:58:43.378803600Z", + "eventTime": "2025-08-18T07:43:10.045812500Z", "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", - "taskId": "1048618", + "taskId": "1048840", "workflowTaskCompletedEventAttributes": { "scheduledEventId": "2", "startedEventId": "3", - "identity": "Temporal\\Tests\\Acceptance\\Extra\\Workflow\\Versioning:b7e7bdac-03cb-40fe-bc45-896345d005f9", + "identity": "Temporal\\Tests\\Acceptance\\Extra\\Versioning\\Classic:2046266d-f855-4ea4-8d8b-52e483c89c88", "workerVersion": { - "buildId": "f56858783d7ba07ee263f7740e6a9993" + "buildId": "9518ff0cb6b50ae08577a6c5fc24c4d7" }, "sdkMetadata": { "langUsedFlags": [ @@ -79,9 +80,9 @@ }, { "eventId": "5", - "eventTime": "2025-05-10T10:58:43.378803600Z", + "eventTime": "2025-08-18T07:43:10.045812500Z", "eventType": "EVENT_TYPE_MARKER_RECORDED", - "taskId": "1048619", + "taskId": "1048841", "markerRecordedEventAttributes": { "markerName": "Version", "details": { @@ -111,9 +112,9 @@ }, { "eventId": "6", - "eventTime": "2025-05-10T10:58:43.379333100Z", + "eventTime": "2025-08-18T07:43:10.046354200Z", "eventType": "EVENT_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES", - "taskId": "1048620", + "taskId": "1048842", "upsertWorkflowSearchAttributesEventAttributes": { "workflowTaskCompletedEventId": "4", "searchAttributes": { @@ -131,9 +132,9 @@ }, { "eventId": "7", - "eventTime": "2025-05-10T10:58:43.379333100Z", + "eventTime": "2025-08-18T07:43:10.046354200Z", "eventType": "EVENT_TYPE_MARKER_RECORDED", - "taskId": "1048621", + "taskId": "1048843", "markerRecordedEventAttributes": { "markerName": "SideEffect", "details": { @@ -163,9 +164,9 @@ }, { "eventId": "8", - "eventTime": "2025-05-10T10:58:43.379333100Z", + "eventTime": "2025-08-18T07:43:10.046354200Z", "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", - "taskId": "1048622", + "taskId": "1048844", "workflowExecutionCompletedEventAttributes": { "result": { "payloads": [ @@ -173,7 +174,7 @@ "metadata": { "encoding": "anNvbi9wbGFpbg==" }, - "data": "InRlc3Qi" + "data": "InYxIg==" } ] }, diff --git a/tests/Acceptance/Extra/Versioning/VersioningTest.php b/tests/Acceptance/Extra/Versioning/ClassicTest.php similarity index 70% rename from tests/Acceptance/Extra/Versioning/VersioningTest.php rename to tests/Acceptance/Extra/Versioning/ClassicTest.php index 9ba4815a..d46f278c 100644 --- a/tests/Acceptance/Extra/Versioning/VersioningTest.php +++ b/tests/Acceptance/Extra/Versioning/ClassicTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Temporal\Tests\Acceptance\Extra\Workflow\Versioning; +namespace Temporal\Tests\Acceptance\Extra\Versioning\Classic; use PHPUnit\Framework\Attributes\Test; use Temporal\Activity\ActivityInterface; @@ -16,20 +16,20 @@ use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; -class VersioningTest extends TestCase +class ClassicTest extends TestCase { #[Test] - public function sendEmpty( + public function replayDifferentVersions( #[Stub( - type: 'Extra_Versioning_Versioning', + type: 'Extra_Versioning_Classic', )] WorkflowStubInterface $stub, ): void { $result = $stub->getResult(); self::assertSame('v2', $result); $replayer = new WorkflowReplayer(); - $replayer->replayFromJSON('Extra_Versioning_Versioning', __DIR__ . '/Versioning-default.json'); - $replayer->replayFromJSON('Extra_Versioning_Versioning', __DIR__ . '/Versioning-v1.json'); + $replayer->replayFromJSON('Extra_Versioning_Classic', __DIR__ . '/Classic/Versioning-default.json'); + $replayer->replayFromJSON('Extra_Versioning_Classic', __DIR__ . '/Classic/Versioning-v1.json'); $replayer->replayFromServer($stub->getWorkflowType(), $stub->getExecution()); } @@ -38,7 +38,7 @@ public function sendEmpty( #[WorkflowInterface] class TestWorkflow { - #[WorkflowMethod(name: "Extra_Versioning_Versioning")] + #[WorkflowMethod(name: "Extra_Versioning_Classic")] public function handle() { $version = yield Workflow::getVersion('test', Workflow::DEFAULT_VERSION, 2); @@ -50,7 +50,8 @@ public function handle() if ($version === 2) { return yield Workflow::executeActivity( - 'Extra_Versioning_Versioning.handler', + /** @see TestActivity::handler() */ + 'Extra_Versioning_Classic.handler', args: ['v2'], options: ActivityOptions::new()->withScheduleToCloseTimeout(5), ); @@ -60,7 +61,7 @@ public function handle() } } -#[ActivityInterface(prefix: 'Extra_Versioning_Versioning.')] +#[ActivityInterface(prefix: 'Extra_Versioning_Classic.')] class TestActivity { #[ActivityMethod] From 95f26ccea455577f1be05334092e40c85f0ac312 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 18 Aug 2025 16:59:38 +0400 Subject: [PATCH 07/15] test(acceptance): add Deployment Versioning test --- src/Worker/Transport/RoadRunner.php | 2 +- testing/src/Environment.php | 18 +++- .../App/Runtime/TemporalStarter.php | 17 ++- .../Extra/Versioning/DeploymentTest.php | 101 ++++++++++++++++++ 4 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 tests/Acceptance/Extra/Versioning/DeploymentTest.php diff --git a/src/Worker/Transport/RoadRunner.php b/src/Worker/Transport/RoadRunner.php index 955a214a..dea3a9c7 100644 --- a/src/Worker/Transport/RoadRunner.php +++ b/src/Worker/Transport/RoadRunner.php @@ -39,7 +39,7 @@ final class RoadRunner implements HostConnectionInterface private RoadRunnerWorker $worker; private CodecInterface $codec; - public function __construct(RoadRunnerWorker $worker) + private function __construct(RoadRunnerWorker $worker) { $this->worker = $worker; $this->codec = new JsonCodec(); diff --git a/testing/src/Environment.php b/testing/src/Environment.php index 7b65a7af..bba483c5 100644 --- a/testing/src/Environment.php +++ b/testing/src/Environment.php @@ -94,9 +94,6 @@ public function startTemporalServer( $this->systemInfo->temporalCliExecutable, "server", "start-dev", "--port", $temporalPort, - '--dynamic-config-value', 'frontend.enableUpdateWorkflowExecution=true', - '--dynamic-config-value', 'frontend.enableUpdateWorkflowExecutionAsyncAccepted=true', - '--dynamic-config-value', 'frontend.enableExecuteMultiOperation=true', '--log-level', 'error', '--headless', ...$parameters, @@ -202,4 +199,19 @@ public function stop(): void $this->output->writeln('done.'); } } + + /** + * @internal + */ + public function executeTemporalCommand(array|string $command, int $timeout = 10): void + { + $command = \array_merge( + [$this->systemInfo->temporalCliExecutable], + (array) $command, + ); + + $process = new Process($command); + $process->setTimeout($timeout); + $process->run(); + } } diff --git a/tests/Acceptance/App/Runtime/TemporalStarter.php b/tests/Acceptance/App/Runtime/TemporalStarter.php index eabd35f5..7ef1a530 100644 --- a/tests/Acceptance/App/Runtime/TemporalStarter.php +++ b/tests/Acceptance/App/Runtime/TemporalStarter.php @@ -4,6 +4,7 @@ namespace Temporal\Tests\Acceptance\App\Runtime; +use Symfony\Component\Process\Process; use Temporal\Common\SearchAttributes\ValueType; use Temporal\Testing\Environment; @@ -49,13 +50,25 @@ public function start(): void $this->started = true; } - public function stop(): void + public function executeTemporalCommand(array|string $command, int $timeout = 10): void + { + $this->environment->executeTemporalCommand( + command: $command, + timeout: $timeout, + ); + } + + /** + * @return bool Returns true if the server was stopped successfully, false if it was not started. + */ + public function stop(): bool { if (!$this->started) { - return; + return false; } $this->environment->stop(); $this->started = false; + return true; } } diff --git a/tests/Acceptance/Extra/Versioning/DeploymentTest.php b/tests/Acceptance/Extra/Versioning/DeploymentTest.php new file mode 100644 index 00000000..780504ab --- /dev/null +++ b/tests/Acceptance/Extra/Versioning/DeploymentTest.php @@ -0,0 +1,101 @@ +executeTemporalCommand([ + 'worker', + 'deployment', + 'set-current-version', + '--deployment-name', WorkerFactory::DEPLOYMENT_NAME, + '--build-id', WorkerFactory::BUILD_ID, + '--yes', + ], timeout: 5); + + try { + # Create a Workflow stub with an execution timeout 12 seconds + $stub = $client + ->withTimeout(10) + ->newUntypedWorkflowStub( + 'Extra_Versioning_Deployment', + WorkflowOptions::new() + ->withTaskQueue($feature->taskQueue) + ->withWorkflowExecutionTimeout(20), + ); + + # Start the Workflow + $client->start($stub); + + $result = $stub->getResult(timeout: 5); + self::assertSame('default', $result); + + $version = null; + $behavior = null; + foreach ($client->getWorkflowHistory($stub->getExecution()) as $event) { + if ($event->hasWorkflowTaskCompletedEventAttributes()) { + $version = $event->getWorkflowTaskCompletedEventAttributes()?->getDeploymentVersion(); + $behavior = $event->getWorkflowTaskCompletedEventAttributes()?->getVersioningBehavior(); + break; + } + } + + self::assertNotNull($version); + self::assertSame(WorkerFactory::DEPLOYMENT_NAME, $version->getDeploymentName()); + self::assertSame(WorkerFactory::BUILD_ID, $version->getBuildId()); + self::assertSame(VersioningBehavior::AutoUpgrade->value, $behavior); + } finally { + $starter->stop() and $starter->start(); + } + } +} + +class WorkerFactory +{ + public const DEPLOYMENT_NAME = 'foo'; + public const BUILD_ID = 'baz'; + + public static function options(): WorkerOptions + { + return WorkerOptions::new() + ->withDeploymentOptions( + WorkerDeploymentOptions::new() + ->withUseVersioning(true) + ->withVersion(WorkerDeploymentVersion::new(self::DEPLOYMENT_NAME, self::BUILD_ID)) + ->withDefaultVersioningBehavior(VersioningBehavior::AutoUpgrade), + ); + } +} + +#[WorkflowInterface] +class TestWorkflow +{ + #[WorkflowMethod(name: "Extra_Versioning_Deployment")] + public function handle() + { + return 'default'; + } +} From fe7ebd692511bc49b5fbd15c5758ce55bd84fd2f Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 18 Aug 2025 21:10:11 +0400 Subject: [PATCH 08/15] feat: introduce WorkflowVersioningBehavior attribute and enhance workflow versioning support --- src/Internal/Declaration/Graph/ClassNode.php | 7 +++- .../Prototype/WorkflowPrototype.php | 13 +++++++ .../Declaration/Reader/WorkflowReader.php | 33 ++++++++-------- .../Transport/Router/GetWorkerInfo.php | 4 +- src/Workflow/WorkflowVersioningBehavior.php | 39 +++++++++++++++++++ 5 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 src/Workflow/WorkflowVersioningBehavior.php diff --git a/src/Internal/Declaration/Graph/ClassNode.php b/src/Internal/Declaration/Graph/ClassNode.php index dea4b08f..996b894b 100644 --- a/src/Internal/Declaration/Graph/ClassNode.php +++ b/src/Internal/Declaration/Graph/ClassNode.php @@ -58,7 +58,7 @@ public function count(): int * Get a method with all the declared classes. * * @param non-empty-string $name - * @return \Traversable + * @return \Traversable> * @throws \ReflectionException */ public function getMethods(string $name, bool $reverse = true): \Traversable @@ -190,6 +190,7 @@ private function getInheritance(): array /** * @param iterable $classes + * @return list * @throws \ReflectionException */ private function boxMethods(iterable $classes, string $name): array @@ -205,6 +206,10 @@ private function boxMethods(iterable $classes, string $name): array return $result; } + /** + * @param array $boxed + * @return \Traversable + */ private function unboxMethods(array $boxed): \Traversable { $unpack = static function () use ($boxed) { diff --git a/src/Internal/Declaration/Prototype/WorkflowPrototype.php b/src/Internal/Declaration/Prototype/WorkflowPrototype.php index 328d0716..fa9cf95e 100644 --- a/src/Internal/Declaration/Prototype/WorkflowPrototype.php +++ b/src/Internal/Declaration/Prototype/WorkflowPrototype.php @@ -14,6 +14,7 @@ use Temporal\Common\CronSchedule; use Temporal\Common\MethodRetry; use Temporal\Internal\Declaration\EntityNameValidator; +use Temporal\Worker\Versioning\VersioningBehavior; use Temporal\Workflow\ReturnType; use Temporal\Workflow\WorkflowInit; @@ -43,12 +44,14 @@ final class WorkflowPrototype extends Prototype private ?MethodRetry $methodRetry = null; private ?ReturnType $returnType = null; private bool $hasInitializer = false; + private VersioningBehavior $versioningBehavior; public function __construct( string $name, ?\ReflectionMethod $handler, \ReflectionClass $class, ) { + $this->versioningBehavior = VersioningBehavior::Unspecified; EntityNameValidator::validateWorkflow($name); parent::__construct($name, $handler, $class); } @@ -96,6 +99,11 @@ public function setReturnType(?ReturnType $attribute): void $this->returnType = $attribute; } + public function setVersioningBehavior(VersioningBehavior $behavior): void + { + $this->versioningBehavior = $behavior; + } + public function addQueryHandler(QueryDefinition $definition): void { EntityNameValidator::validateQueryMethod($definition->name); @@ -156,4 +164,9 @@ public function getValidateUpdateHandlers(): array { return $this->updateValidators; } + + public function getVersioningBehavior(): VersioningBehavior + { + return $this->versioningBehavior; + } } diff --git a/src/Internal/Declaration/Reader/WorkflowReader.php b/src/Internal/Declaration/Reader/WorkflowReader.php index af64f647..d97877a2 100644 --- a/src/Internal/Declaration/Reader/WorkflowReader.php +++ b/src/Internal/Declaration/Reader/WorkflowReader.php @@ -27,6 +27,7 @@ use Temporal\Workflow\WorkflowInit; use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; +use Temporal\Workflow\WorkflowVersioningBehavior; /** * @template-extends Reader @@ -292,14 +293,16 @@ private function getAttributedMethod(ClassNode $graph, \ReflectionMethod $handle } /** + * Walk through the method hierarchy and build the prototype for the workflow method. + * * @throws \ReflectionException */ private function getPrototype(ClassNode $graph, \ReflectionMethod $handler): ?WorkflowPrototype { - $cronSchedule = $previousRetry = $prototype = $returnType = null; + $cronSchedule = $previousRetry = $prototype = $returnType = $versionBehavior = null; + /** @var \Traversable $group */ foreach ($graph->getMethods($handler->getName()) as $group) { - // $contextualRetry = $previousRetry; foreach ($group as $method) { @@ -326,6 +329,11 @@ private function getPrototype(ClassNode $graph, \ReflectionMethod $handler): ?Wo ?? $returnType ; + // Version Behavior + $versionBehavior = $this->reader->firstFunctionMetadata($method, WorkflowVersioningBehavior::class) + ?? $versionBehavior + ; + // // In the future, workflow methods are available only in // those classes that contain the attribute: @@ -347,27 +355,18 @@ private function getPrototype(ClassNode $graph, \ReflectionMethod $handler): ?Wo } } - // In case + // Skip if no interface found if ($interface === null) { continue; } \assert($context !== null); - if ($prototype === null) { - $prototype = $this->findProto($handler, $method, $context, $graph->getReflection()); - } - - if ($prototype !== null && $retry !== null) { - $prototype->setMethodRetry($retry); - } - - if ($prototype !== null && $cronSchedule !== null) { - $prototype->setCronSchedule($cronSchedule); - } + $prototype ??= $this->findProto($handler, $method, $context, $graph->getReflection()); - if ($prototype !== null && $returnType !== null) { - $prototype->setReturnType($returnType); - } + $retry === null or $prototype?->setMethodRetry($retry); + $cronSchedule === null or $prototype?->setCronSchedule($cronSchedule); + $returnType === null or $prototype?->setReturnType($returnType); + $versionBehavior === null or $prototype?->setVersioningBehavior($versionBehavior->value); } $previousRetry = $contextualRetry; diff --git a/src/Internal/Transport/Router/GetWorkerInfo.php b/src/Internal/Transport/Router/GetWorkerInfo.php index 70925727..362392fd 100644 --- a/src/Internal/Transport/Router/GetWorkerInfo.php +++ b/src/Internal/Transport/Router/GetWorkerInfo.php @@ -44,10 +44,10 @@ public function handle(ServerRequestInterface $request, array $headers, Deferred private function workerToArray(WorkerInterface $worker): array { $workflowMap = static fn(WorkflowPrototype $workflow): array => [ - 'Name' => $workflow->getID(), + 'Name' => $workflow->getID(), 'Queries' => \array_keys($workflow->getQueryHandlers()), 'Signals' => \array_keys($workflow->getSignalHandlers()), - // 'Updates' => $this->keys($workflow->getUpdateHandlers()), + 'VersioningBehavior' => $workflow->getVersioningBehavior()->value, ]; $activityMap = static fn(ActivityPrototype $activity): array => [ diff --git a/src/Workflow/WorkflowVersioningBehavior.php b/src/Workflow/WorkflowVersioningBehavior.php new file mode 100644 index 00000000..cebfc8fb --- /dev/null +++ b/src/Workflow/WorkflowVersioningBehavior.php @@ -0,0 +1,39 @@ + Date: Tue, 19 Aug 2025 13:28:19 +0400 Subject: [PATCH 09/15] refactor: update WorkerDeploymentOptions to use WorkerDeploymentVersion instead of string --- src/Worker/Versioning/WorkerDeploymentOptions.php | 4 ++-- src/Worker/Versioning/WorkerDeploymentVersion.php | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Worker/Versioning/WorkerDeploymentOptions.php b/src/Worker/Versioning/WorkerDeploymentOptions.php index 93a54b72..a9486fa8 100644 --- a/src/Worker/Versioning/WorkerDeploymentOptions.php +++ b/src/Worker/Versioning/WorkerDeploymentOptions.php @@ -25,7 +25,7 @@ class WorkerDeploymentOptions private readonly bool $useVersioning; #[Marshal(name: 'Version', nullable: true)] - private readonly ?string $version; + private readonly ?WorkerDeploymentVersion $version; #[Marshal(name: 'DefaultVersioningBehavior', type: EnumValueType::class)] private readonly VersioningBehavior $defaultVersioningBehavior; @@ -62,7 +62,7 @@ public function withUseVersioning(bool $value): self public function withVersion(string|WorkerDeploymentVersion $version): self { /** @see self::$version */ - return $this->with('version', (string) $version); + return $this->with('version', \is_string($version) ? WorkerDeploymentVersion::fromString($version) : $version); } /** diff --git a/src/Worker/Versioning/WorkerDeploymentVersion.php b/src/Worker/Versioning/WorkerDeploymentVersion.php index efa29c96..81dde157 100644 --- a/src/Worker/Versioning/WorkerDeploymentVersion.php +++ b/src/Worker/Versioning/WorkerDeploymentVersion.php @@ -5,6 +5,7 @@ namespace Temporal\Worker\Versioning; use Temporal\Exception\InvalidArgumentException; +use Temporal\Internal\Marshaller\Meta\Marshal; use Temporal\Internal\Traits\CloneWith; /** @@ -23,11 +24,13 @@ private function __construct( * The combination of {@see $deployment_name} and {@see $buildId} uniquely identifies this * Version within the namespace, because Deployment names are unique within a namespace. */ + #[Marshal('DeploymentName')] public readonly string $deploymentName, /** * Identifies the Worker Deployment this Version is part of. */ + #[Marshal('BuildId')] public readonly string $buildId, ) {} From 5cc27a39fb4e67c64a620eaf81d62be1b337fc54 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 19 Aug 2025 13:44:49 +0400 Subject: [PATCH 10/15] test(acceptance): add Versioning test with Pinned behavior --- .../Extra/Versioning/DeploymentTest.php | 91 ++++++++++++++++--- 1 file changed, 79 insertions(+), 12 deletions(-) diff --git a/tests/Acceptance/Extra/Versioning/DeploymentTest.php b/tests/Acceptance/Extra/Versioning/DeploymentTest.php index 780504ab..834b2f6d 100644 --- a/tests/Acceptance/Extra/Versioning/DeploymentTest.php +++ b/tests/Acceptance/Extra/Versioning/DeploymentTest.php @@ -17,31 +17,26 @@ use Temporal\Worker\WorkerOptions; use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; +use Temporal\Workflow\WorkflowVersioningBehavior; #[Worker(options: [WorkerFactory::class, 'options'])] class DeploymentTest extends TestCase { #[Test] - public function sendEmpty( + public function defaultBehaviorAuto( TemporalStarter $starter, WorkflowClientInterface $client, Feature $feature, ): void { - $starter->executeTemporalCommand([ - 'worker', - 'deployment', - 'set-current-version', - '--deployment-name', WorkerFactory::DEPLOYMENT_NAME, - '--build-id', WorkerFactory::BUILD_ID, - '--yes', - ], timeout: 5); + WorkerFactory::setCurrentDeployment($starter); try { # Create a Workflow stub with an execution timeout 12 seconds $stub = $client ->withTimeout(10) ->newUntypedWorkflowStub( - 'Extra_Versioning_Deployment', + /** @see PinnedWorkflow */ + 'Extra_Versioning_Deployment_Default', WorkflowOptions::new() ->withTaskQueue($feature->taskQueue) ->withWorkflowExecutionTimeout(20), @@ -50,9 +45,11 @@ public function sendEmpty( # Start the Workflow $client->start($stub); + # Check the result $result = $stub->getResult(timeout: 5); self::assertSame('default', $result); + # Check the Workflow History $version = null; $behavior = null; foreach ($client->getWorkflowHistory($stub->getExecution()) as $event) { @@ -71,6 +68,53 @@ public function sendEmpty( $starter->stop() and $starter->start(); } } + + #[Test] + public function customBehaviorPinned( + TemporalStarter $starter, + WorkflowClientInterface $client, + Feature $feature, + ): void { + WorkerFactory::setCurrentDeployment($starter); + + try { + # Create a Workflow stub with an execution timeout 12 seconds + $stub = $client + ->withTimeout(10) + ->newUntypedWorkflowStub( + /** @see DefaultWorkflow */ + 'Extra_Versioning_Deployment_Pinned', + WorkflowOptions::new() + ->withTaskQueue($feature->taskQueue) + ->withWorkflowExecutionTimeout(20), + ); + + # Start the Workflow + $client->start($stub); + + # Check the result + $result = $stub->getResult(timeout: 5); + self::assertSame('pinned', $result); + + # Check the Workflow History + $version = null; + $behavior = null; + foreach ($client->getWorkflowHistory($stub->getExecution()) as $event) { + if ($event->hasWorkflowTaskCompletedEventAttributes()) { + $version = $event->getWorkflowTaskCompletedEventAttributes()?->getDeploymentVersion(); + $behavior = $event->getWorkflowTaskCompletedEventAttributes()?->getVersioningBehavior(); + break; + } + } + + self::assertNotNull($version); + self::assertSame(WorkerFactory::DEPLOYMENT_NAME, $version->getDeploymentName()); + self::assertSame(WorkerFactory::BUILD_ID, $version->getBuildId()); + self::assertSame(VersioningBehavior::Pinned->value, $behavior); + } finally { + $starter->stop() and $starter->start(); + } + } } class WorkerFactory @@ -88,14 +132,37 @@ public static function options(): WorkerOptions ->withDefaultVersioningBehavior(VersioningBehavior::AutoUpgrade), ); } + + public static function setCurrentDeployment(TemporalStarter $starter): void + { + $starter->executeTemporalCommand([ + 'worker', + 'deployment', + 'set-current-version', + '--deployment-name', WorkerFactory::DEPLOYMENT_NAME, + '--build-id', WorkerFactory::BUILD_ID, + '--yes', + ], timeout: 5); + } } #[WorkflowInterface] -class TestWorkflow +class DefaultWorkflow { - #[WorkflowMethod(name: "Extra_Versioning_Deployment")] + #[WorkflowMethod(name: "Extra_Versioning_Deployment_Default")] public function handle() { return 'default'; } } + +#[WorkflowInterface] +class PinnedWorkflow +{ + #[WorkflowMethod(name: "Extra_Versioning_Deployment_Pinned")] + #[WorkflowVersioningBehavior(VersioningBehavior::Pinned)] + public function handle() + { + return 'pinned'; + } +} From 09c3a8f38c54633f8bfc2331ecf09241a8b4e3f2 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 21 Aug 2025 14:14:38 +0400 Subject: [PATCH 11/15] refactor: move VersioningBehavior to Common namespace --- src/{Worker/Versioning => Common}/VersioningBehavior.php | 2 +- src/Internal/Declaration/Prototype/WorkflowPrototype.php | 2 +- src/Worker/Versioning/WorkerDeploymentOptions.php | 1 + src/Worker/WorkerOptions.php | 2 +- src/Workflow/WorkflowVersioningBehavior.php | 2 +- tests/Acceptance/Extra/Versioning/DeploymentTest.php | 2 +- tests/Unit/DTO/WorkerOptionsTestCase.php | 2 +- 7 files changed, 7 insertions(+), 6 deletions(-) rename src/{Worker/Versioning => Common}/VersioningBehavior.php (98%) diff --git a/src/Worker/Versioning/VersioningBehavior.php b/src/Common/VersioningBehavior.php similarity index 98% rename from src/Worker/Versioning/VersioningBehavior.php rename to src/Common/VersioningBehavior.php index 3c47fc40..94f8853b 100644 --- a/src/Worker/Versioning/VersioningBehavior.php +++ b/src/Common/VersioningBehavior.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Temporal\Worker\Versioning; +namespace Temporal\Common; /** * Specifies when a workflow might move from a worker of one Build Id to another. diff --git a/src/Internal/Declaration/Prototype/WorkflowPrototype.php b/src/Internal/Declaration/Prototype/WorkflowPrototype.php index fa9cf95e..8b5970bc 100644 --- a/src/Internal/Declaration/Prototype/WorkflowPrototype.php +++ b/src/Internal/Declaration/Prototype/WorkflowPrototype.php @@ -13,8 +13,8 @@ use Temporal\Common\CronSchedule; use Temporal\Common\MethodRetry; +use Temporal\Common\VersioningBehavior; use Temporal\Internal\Declaration\EntityNameValidator; -use Temporal\Worker\Versioning\VersioningBehavior; use Temporal\Workflow\ReturnType; use Temporal\Workflow\WorkflowInit; diff --git a/src/Worker/Versioning/WorkerDeploymentOptions.php b/src/Worker/Versioning/WorkerDeploymentOptions.php index a9486fa8..0ede6a02 100644 --- a/src/Worker/Versioning/WorkerDeploymentOptions.php +++ b/src/Worker/Versioning/WorkerDeploymentOptions.php @@ -4,6 +4,7 @@ namespace Temporal\Worker\Versioning; +use Temporal\Common\VersioningBehavior; use Temporal\Internal\Marshaller\Meta\Marshal; use Temporal\Internal\Marshaller\Type\EnumValueType; use Temporal\Internal\Traits\CloneWith; diff --git a/src/Worker/WorkerOptions.php b/src/Worker/WorkerOptions.php index 3a02a65a..4f76cfcb 100644 --- a/src/Worker/WorkerOptions.php +++ b/src/Worker/WorkerOptions.php @@ -848,7 +848,7 @@ public function withUseBuildIDForVersioning(bool $useBuildIDForVersioning = true } /** - * Optional: If set it configures Worker Versioning for this worker. + * Set deployment options for the worker. * * @internal Experimental. */ diff --git a/src/Workflow/WorkflowVersioningBehavior.php b/src/Workflow/WorkflowVersioningBehavior.php index cebfc8fb..6215d406 100644 --- a/src/Workflow/WorkflowVersioningBehavior.php +++ b/src/Workflow/WorkflowVersioningBehavior.php @@ -13,7 +13,7 @@ use Doctrine\Common\Annotations\Annotation\Target; use Spiral\Attributes\NamedArgumentConstructor; -use Temporal\Worker\Versioning\VersioningBehavior; +use Temporal\Common\VersioningBehavior; /** * Indicates the versioning behavior of the Workflow. diff --git a/tests/Acceptance/Extra/Versioning/DeploymentTest.php b/tests/Acceptance/Extra/Versioning/DeploymentTest.php index 834b2f6d..1ffe94ab 100644 --- a/tests/Acceptance/Extra/Versioning/DeploymentTest.php +++ b/tests/Acceptance/Extra/Versioning/DeploymentTest.php @@ -7,11 +7,11 @@ use PHPUnit\Framework\Attributes\Test; use Temporal\Client\WorkflowClientInterface; use Temporal\Client\WorkflowOptions; +use Temporal\Common\VersioningBehavior; use Temporal\Tests\Acceptance\App\Attribute\Worker; use Temporal\Tests\Acceptance\App\Runtime\Feature; use Temporal\Tests\Acceptance\App\Runtime\TemporalStarter; use Temporal\Tests\Acceptance\App\TestCase; -use Temporal\Worker\Versioning\VersioningBehavior; use Temporal\Worker\Versioning\WorkerDeploymentOptions; use Temporal\Worker\Versioning\WorkerDeploymentVersion; use Temporal\Worker\WorkerOptions; diff --git a/tests/Unit/DTO/WorkerOptionsTestCase.php b/tests/Unit/DTO/WorkerOptionsTestCase.php index 20330273..34eae48a 100644 --- a/tests/Unit/DTO/WorkerOptionsTestCase.php +++ b/tests/Unit/DTO/WorkerOptionsTestCase.php @@ -11,7 +11,7 @@ namespace Temporal\Tests\Unit\DTO; -use Temporal\Worker\Versioning\VersioningBehavior; +use Temporal\Common\VersioningBehavior; use Temporal\Worker\Versioning\WorkerDeploymentOptions; use Temporal\Worker\Versioning\WorkerDeploymentVersion; use Temporal\Worker\WorkerOptions; From d14d66c12ca46090bc9e103cb5491ac069dfa2c0 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 21 Aug 2025 20:54:59 +0400 Subject: [PATCH 12/15] feat(WorkflowClient): add VersioningOverride feature; refactor: move versioning DTOs into common folder; --- src/Client/WorkflowOptions.php | 19 ++- .../{ => Versioning}/VersioningBehavior.php | 2 +- src/Common/Versioning/VersioningOverride.php | 46 ++++++ .../Versioning/WorkerDeploymentVersion.php | 8 +- src/Internal/Client/WorkflowStarter.php | 28 ++++ .../Prototype/WorkflowPrototype.php | 2 +- .../WorkerDeploymentOptions.php | 5 +- src/Worker/WorkerOptions.php | 1 - src/Workflow/WorkflowVersioningBehavior.php | 2 +- .../Extra/Versioning/DeploymentTest.php | 139 +++++++++++------- tests/Unit/DTO/WorkerOptionsTestCase.php | 6 +- 11 files changed, 188 insertions(+), 70 deletions(-) rename src/Common/{ => Versioning}/VersioningBehavior.php (98%) create mode 100644 src/Common/Versioning/VersioningOverride.php rename src/{Worker => Common}/Versioning/WorkerDeploymentVersion.php (89%) rename src/Worker/{Versioning => }/WorkerDeploymentOptions.php (94%) diff --git a/src/Client/WorkflowOptions.php b/src/Client/WorkflowOptions.php index 7edc60a1..94e2ab6b 100644 --- a/src/Client/WorkflowOptions.php +++ b/src/Client/WorkflowOptions.php @@ -23,6 +23,7 @@ use Temporal\Common\SearchAttributes\SearchAttributeKey; use Temporal\Common\TypedSearchAttributes; use Temporal\Common\Uuid; +use Temporal\Common\Versioning\VersioningOverride; use Temporal\Common\WorkflowIdConflictPolicy; use Temporal\DataConverter\DataConverterInterface; use Temporal\Internal\Marshaller\Meta\Marshal; @@ -32,8 +33,8 @@ use Temporal\Internal\Marshaller\Type\NullableType; use Temporal\Internal\Support\DateInterval; use Temporal\Internal\Support\Options; -use Temporal\Worker\WorkerFactoryInterface; use Temporal\Worker\Worker; +use Temporal\Worker\WorkerFactoryInterface; /** * WorkflowOptions configuration parameters for starting a workflow execution. @@ -182,6 +183,12 @@ final class WorkflowOptions extends Options #[Marshal(name: 'Priority')] public Priority $priority; + /** + * Override the version of the workflow. + */ + #[Marshal(name: 'VersioningOverride')] + public ?VersioningOverride $versioningOverride = null; + /** * @throws \Exception */ @@ -521,6 +528,16 @@ public function withStaticDetails(string $details): self return $self; } + /** + * Sets the versioning override to use when starting this workflow. + */ + public function withVersioningOverride(?VersioningOverride $override): self + { + $self = clone $this; + $self->versioningOverride = $override; + return $self; + } + /** * @internal */ diff --git a/src/Common/VersioningBehavior.php b/src/Common/Versioning/VersioningBehavior.php similarity index 98% rename from src/Common/VersioningBehavior.php rename to src/Common/Versioning/VersioningBehavior.php index 94f8853b..88a70c38 100644 --- a/src/Common/VersioningBehavior.php +++ b/src/Common/Versioning/VersioningBehavior.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Temporal\Common; +namespace Temporal\Common\Versioning; /** * Specifies when a workflow might move from a worker of one Build Id to another. diff --git a/src/Common/Versioning/VersioningOverride.php b/src/Common/Versioning/VersioningOverride.php new file mode 100644 index 00000000..9a621b04 --- /dev/null +++ b/src/Common/Versioning/VersioningOverride.php @@ -0,0 +1,46 @@ +setWorkflowTaskTimeout(DateInterval::toDuration($options->workflowTaskTimeout)) ->setPriority($options->priority->toProto()); + // Versioning override + if ($options->versioningOverride !== null) { + $value = new VersioningOverride(); + + if ($options->versioningOverride->behavior === VersioningBehavior::Pinned) { + $version = $options->versioningOverride->version; + \assert($version !== null); + + $value->setPinned( + (new PinnedOverride()) + ->setBehavior(VersioningBehavior::Pinned->value) + ->setVersion( + (new WorkerDeploymentVersion()) + ->setBuildId($version->buildId) + ->setDeploymentName($version->deploymentName), + ), + ); + } elseif ($options->versioningOverride->behavior === VersioningBehavior::AutoUpgrade) { + $value->setAutoUpgrade(true); + } + + $req->setVersioningOverride($value); + } + // Retry Policy $options->retryOptions === null or $req->setRetryPolicy($options->retryOptions->toWorkflowRetryPolicy()); diff --git a/src/Internal/Declaration/Prototype/WorkflowPrototype.php b/src/Internal/Declaration/Prototype/WorkflowPrototype.php index 8b5970bc..da5d310a 100644 --- a/src/Internal/Declaration/Prototype/WorkflowPrototype.php +++ b/src/Internal/Declaration/Prototype/WorkflowPrototype.php @@ -13,7 +13,7 @@ use Temporal\Common\CronSchedule; use Temporal\Common\MethodRetry; -use Temporal\Common\VersioningBehavior; +use Temporal\Common\Versioning\VersioningBehavior; use Temporal\Internal\Declaration\EntityNameValidator; use Temporal\Workflow\ReturnType; use Temporal\Workflow\WorkflowInit; diff --git a/src/Worker/Versioning/WorkerDeploymentOptions.php b/src/Worker/WorkerDeploymentOptions.php similarity index 94% rename from src/Worker/Versioning/WorkerDeploymentOptions.php rename to src/Worker/WorkerDeploymentOptions.php index 0ede6a02..1f61c1e8 100644 --- a/src/Worker/Versioning/WorkerDeploymentOptions.php +++ b/src/Worker/WorkerDeploymentOptions.php @@ -2,9 +2,10 @@ declare(strict_types=1); -namespace Temporal\Worker\Versioning; +namespace Temporal\Worker; -use Temporal\Common\VersioningBehavior; +use Temporal\Common\Versioning\VersioningBehavior; +use Temporal\Common\Versioning\WorkerDeploymentVersion; use Temporal\Internal\Marshaller\Meta\Marshal; use Temporal\Internal\Marshaller\Type\EnumValueType; use Temporal\Internal\Traits\CloneWith; diff --git a/src/Worker/WorkerOptions.php b/src/Worker/WorkerOptions.php index 4f76cfcb..84ef6a10 100644 --- a/src/Worker/WorkerOptions.php +++ b/src/Worker/WorkerOptions.php @@ -18,7 +18,6 @@ use Temporal\Internal\Marshaller\Type\EnumValueType; use Temporal\Internal\Marshaller\Type\NullableType; use Temporal\Internal\Support\DateInterval; -use Temporal\Worker\Versioning\WorkerDeploymentOptions; use Temporal\Workflow; /** diff --git a/src/Workflow/WorkflowVersioningBehavior.php b/src/Workflow/WorkflowVersioningBehavior.php index 6215d406..605e9c02 100644 --- a/src/Workflow/WorkflowVersioningBehavior.php +++ b/src/Workflow/WorkflowVersioningBehavior.php @@ -13,7 +13,7 @@ use Doctrine\Common\Annotations\Annotation\Target; use Spiral\Attributes\NamedArgumentConstructor; -use Temporal\Common\VersioningBehavior; +use Temporal\Common\Versioning\VersioningBehavior; /** * Indicates the versioning behavior of the Workflow. diff --git a/tests/Acceptance/Extra/Versioning/DeploymentTest.php b/tests/Acceptance/Extra/Versioning/DeploymentTest.php index 1ffe94ab..f26a0cc2 100644 --- a/tests/Acceptance/Extra/Versioning/DeploymentTest.php +++ b/tests/Acceptance/Extra/Versioning/DeploymentTest.php @@ -7,13 +7,14 @@ use PHPUnit\Framework\Attributes\Test; use Temporal\Client\WorkflowClientInterface; use Temporal\Client\WorkflowOptions; -use Temporal\Common\VersioningBehavior; +use Temporal\Common\Versioning\VersioningBehavior; +use Temporal\Common\Versioning\VersioningOverride; +use Temporal\Common\Versioning\WorkerDeploymentVersion; use Temporal\Tests\Acceptance\App\Attribute\Worker; use Temporal\Tests\Acceptance\App\Runtime\Feature; use Temporal\Tests\Acceptance\App\Runtime\TemporalStarter; use Temporal\Tests\Acceptance\App\TestCase; -use Temporal\Worker\Versioning\WorkerDeploymentOptions; -use Temporal\Worker\Versioning\WorkerDeploymentVersion; +use Temporal\Worker\WorkerDeploymentOptions; use Temporal\Worker\WorkerOptions; use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; @@ -22,12 +23,13 @@ #[Worker(options: [WorkerFactory::class, 'options'])] class DeploymentTest extends TestCase { - #[Test] - public function defaultBehaviorAuto( + public static function executeWorkflow( TemporalStarter $starter, WorkflowClientInterface $client, Feature $feature, - ): void { + string $workflowType, + WorkflowOptions $options, + ): ?VersioningBehavior { WorkerFactory::setCurrentDeployment($starter); try { @@ -36,8 +38,8 @@ public function defaultBehaviorAuto( ->withTimeout(10) ->newUntypedWorkflowStub( /** @see PinnedWorkflow */ - 'Extra_Versioning_Deployment_Default', - WorkflowOptions::new() + $workflowType, + $options ->withTaskQueue($feature->taskQueue) ->withWorkflowExecutionTimeout(20), ); @@ -45,75 +47,100 @@ public function defaultBehaviorAuto( # Start the Workflow $client->start($stub); - # Check the result - $result = $stub->getResult(timeout: 5); - self::assertSame('default', $result); + # Wait for the Workflow to complete + $stub->getResult(timeout: 5); # Check the Workflow History - $version = null; - $behavior = null; foreach ($client->getWorkflowHistory($stub->getExecution()) as $event) { if ($event->hasWorkflowTaskCompletedEventAttributes()) { $version = $event->getWorkflowTaskCompletedEventAttributes()?->getDeploymentVersion(); - $behavior = $event->getWorkflowTaskCompletedEventAttributes()?->getVersioningBehavior(); - break; + self::assertNotNull($version); + self::assertSame(WorkerFactory::DEPLOYMENT_NAME, $version->getDeploymentName()); + self::assertSame(WorkerFactory::BUILD_ID, $version->getBuildId()); + + return VersioningBehavior::tryFrom( + $event->getWorkflowTaskCompletedEventAttributes()?->getVersioningBehavior(), + ); } } - self::assertNotNull($version); - self::assertSame(WorkerFactory::DEPLOYMENT_NAME, $version->getDeploymentName()); - self::assertSame(WorkerFactory::BUILD_ID, $version->getBuildId()); - self::assertSame(VersioningBehavior::AutoUpgrade->value, $behavior); + throw new \RuntimeException('The WorkflowTaskCompletedEventAttributes not found in the Workflow history.'); } finally { $starter->stop() and $starter->start(); } } #[Test] - public function customBehaviorPinned( + public function defaultBehaviorAuto( TemporalStarter $starter, WorkflowClientInterface $client, Feature $feature, ): void { - WorkerFactory::setCurrentDeployment($starter); - - try { - # Create a Workflow stub with an execution timeout 12 seconds - $stub = $client - ->withTimeout(10) - ->newUntypedWorkflowStub( - /** @see DefaultWorkflow */ - 'Extra_Versioning_Deployment_Pinned', - WorkflowOptions::new() - ->withTaskQueue($feature->taskQueue) - ->withWorkflowExecutionTimeout(20), - ); - - # Start the Workflow - $client->start($stub); + $behavior = self::executeWorkflow( + $starter, + $client, + $feature, + /** @see DefaultWorkflow */ + 'Extra_Versioning_Deployment_Default', + WorkflowOptions::new(), + ); + self::assertSame(VersioningBehavior::AutoUpgrade, $behavior); + } - # Check the result - $result = $stub->getResult(timeout: 5); - self::assertSame('pinned', $result); + #[Test] + public function customBehaviorPinned( + TemporalStarter $starter, + WorkflowClientInterface $client, + Feature $feature, + ): void { + $behavior = self::executeWorkflow( + $starter, + $client, + $feature, + /** @see PinnedWorkflow */ + 'Extra_Versioning_Deployment_Pinned', + WorkflowOptions::new(), + ); + self::assertSame(VersioningBehavior::Pinned, $behavior); + } - # Check the Workflow History - $version = null; - $behavior = null; - foreach ($client->getWorkflowHistory($stub->getExecution()) as $event) { - if ($event->hasWorkflowTaskCompletedEventAttributes()) { - $version = $event->getWorkflowTaskCompletedEventAttributes()?->getDeploymentVersion(); - $behavior = $event->getWorkflowTaskCompletedEventAttributes()?->getVersioningBehavior(); - break; - } - } + #[Test] + public function versionBehaviorOverrideAutoUpgrade( + TemporalStarter $starter, + WorkflowClientInterface $client, + Feature $feature, + ): void { + $behavior = self::executeWorkflow( + $starter, + $client, + $feature, + /** @see PinnedWorkflow */ + 'Extra_Versioning_Deployment_Pinned', + WorkflowOptions::new()->withVersioningOverride(VersioningOverride::autoUpgrade()), + ); + self::assertSame(VersioningBehavior::AutoUpgrade, $behavior); + } - self::assertNotNull($version); - self::assertSame(WorkerFactory::DEPLOYMENT_NAME, $version->getDeploymentName()); - self::assertSame(WorkerFactory::BUILD_ID, $version->getBuildId()); - self::assertSame(VersioningBehavior::Pinned->value, $behavior); - } finally { - $starter->stop() and $starter->start(); - } + #[Test] + public function versionBehaviorOverridePinned( + TemporalStarter $starter, + WorkflowClientInterface $client, + Feature $feature, + ): void { + $behavior = self::executeWorkflow( + $starter, + $client, + $feature, + /** @see PinnedWorkflow */ + 'Extra_Versioning_Deployment_Default', + WorkflowOptions::new()->withVersioningOverride(VersioningOverride::pinned( + version: WorkerDeploymentVersion::new( + deploymentName: WorkerFactory::DEPLOYMENT_NAME, + buildId: WorkerFactory::BUILD_ID, + ), + )), + ); + self::assertSame(VersioningBehavior::Pinned, $behavior); } } diff --git a/tests/Unit/DTO/WorkerOptionsTestCase.php b/tests/Unit/DTO/WorkerOptionsTestCase.php index 34eae48a..6add9842 100644 --- a/tests/Unit/DTO/WorkerOptionsTestCase.php +++ b/tests/Unit/DTO/WorkerOptionsTestCase.php @@ -11,9 +11,9 @@ namespace Temporal\Tests\Unit\DTO; -use Temporal\Common\VersioningBehavior; -use Temporal\Worker\Versioning\WorkerDeploymentOptions; -use Temporal\Worker\Versioning\WorkerDeploymentVersion; +use Temporal\Common\Versioning\VersioningBehavior; +use Temporal\Common\Versioning\WorkerDeploymentVersion; +use Temporal\Worker\WorkerDeploymentOptions; use Temporal\Worker\WorkerOptions; use Temporal\Worker\WorkflowPanicPolicy; From d517b1a297390f797de463671645517caf78cb46 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 21 Aug 2025 20:58:48 +0400 Subject: [PATCH 13/15] chore: add phpdoc annotations --- src/Client/WorkflowOptions.php | 8 ++++++++ src/Common/Versioning/VersioningBehavior.php | 4 ++++ src/Common/Versioning/VersioningOverride.php | 4 +++- src/Common/Versioning/WorkerDeploymentVersion.php | 6 +++++- src/Worker/WorkerOptions.php | 4 ++++ 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Client/WorkflowOptions.php b/src/Client/WorkflowOptions.php index 94e2ab6b..95c2540f 100644 --- a/src/Client/WorkflowOptions.php +++ b/src/Client/WorkflowOptions.php @@ -185,6 +185,10 @@ final class WorkflowOptions extends Options /** * Override the version of the workflow. + * + * @since SDK 2.16.0 + * @since RoadRunner 2025.2.0 + * @internal Experimental */ #[Marshal(name: 'VersioningOverride')] public ?VersioningOverride $versioningOverride = null; @@ -530,6 +534,10 @@ public function withStaticDetails(string $details): self /** * Sets the versioning override to use when starting this workflow. + * + * @since SDK 2.16.0 + * @since RoadRunner 2025.2.0 + * @internal Experimental */ public function withVersioningOverride(?VersioningOverride $override): self { diff --git a/src/Common/Versioning/VersioningBehavior.php b/src/Common/Versioning/VersioningBehavior.php index 88a70c38..ca8573b1 100644 --- a/src/Common/Versioning/VersioningBehavior.php +++ b/src/Common/Versioning/VersioningBehavior.php @@ -14,6 +14,10 @@ * Experimental. Worker Deployments are experimental and might significantly change in the future. * * @see \Temporal\Api\Enums\V1\VersioningBehavior + * + * @since SDK 2.16.0 + * @since RoadRunner 2025.2.0 + * @internal Experimental */ enum VersioningBehavior: int { diff --git a/src/Common/Versioning/VersioningOverride.php b/src/Common/Versioning/VersioningOverride.php index 9a621b04..1b6b9503 100644 --- a/src/Common/Versioning/VersioningOverride.php +++ b/src/Common/Versioning/VersioningOverride.php @@ -14,7 +14,9 @@ /** * Represents the override of a worker's versioning behavior for a workflow execution. * - * @internal Experimental. + * @since SDK 2.16.0 + * @since RoadRunner 2025.2.0 + * @internal Experimental */ final class VersioningOverride { diff --git a/src/Common/Versioning/WorkerDeploymentVersion.php b/src/Common/Versioning/WorkerDeploymentVersion.php index 7e094b80..ead8acae 100644 --- a/src/Common/Versioning/WorkerDeploymentVersion.php +++ b/src/Common/Versioning/WorkerDeploymentVersion.php @@ -9,9 +9,13 @@ use Temporal\Internal\Traits\CloneWith; /** - * @internal Experimental. + * Represents the version of a specific worker deployment. * * @see \Temporal\Api\Deployment\V1\WorkerDeploymentVersion + * + * @since SDK 2.16.0 + * @since RoadRunner 2025.2.0 + * @internal Experimental */ class WorkerDeploymentVersion implements \Stringable { diff --git a/src/Worker/WorkerOptions.php b/src/Worker/WorkerOptions.php index 84ef6a10..8f9e2311 100644 --- a/src/Worker/WorkerOptions.php +++ b/src/Worker/WorkerOptions.php @@ -324,6 +324,8 @@ class WorkerOptions /** * Optional: If set it configures Worker Versioning for this worker. * + * @since SDK 2.16.0 + * @since RoadRunner 2025.2.0 * @internal Experimental. */ #[Marshal(name: 'DeploymentOptions')] @@ -849,6 +851,8 @@ public function withUseBuildIDForVersioning(bool $useBuildIDForVersioning = true /** * Set deployment options for the worker. * + * @since SDK 2.16.0 + * @since RoadRunner 2025.2.0 * @internal Experimental. */ #[Pure] From 5f0dc2660d4551f1efd03db2d92e018d19fcf5a9 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Fri, 22 Aug 2025 12:30:45 +0400 Subject: [PATCH 14/15] chore: update RoadRunner version references to 2025.1.3 --- dload.xml | 2 +- src/Client/WorkflowOptions.php | 4 ++-- src/Common/Versioning/VersioningBehavior.php | 12 +++++++++++- src/Common/Versioning/VersioningOverride.php | 2 +- src/Common/Versioning/WorkerDeploymentVersion.php | 2 +- src/Internal/Transport/Router/GetWorkerInfo.php | 8 ++++---- src/Worker/WorkerDeploymentOptions.php | 2 +- src/Worker/WorkerOptions.php | 4 ++-- 8 files changed, 23 insertions(+), 13 deletions(-) diff --git a/dload.xml b/dload.xml index 38ba76f6..23db489d 100644 --- a/dload.xml +++ b/dload.xml @@ -4,7 +4,7 @@ temp-dir="./runtime" > - + diff --git a/src/Client/WorkflowOptions.php b/src/Client/WorkflowOptions.php index 95c2540f..3873a498 100644 --- a/src/Client/WorkflowOptions.php +++ b/src/Client/WorkflowOptions.php @@ -187,7 +187,7 @@ final class WorkflowOptions extends Options * Override the version of the workflow. * * @since SDK 2.16.0 - * @since RoadRunner 2025.2.0 + * @since RoadRunner 2025.1.3 * @internal Experimental */ #[Marshal(name: 'VersioningOverride')] @@ -536,7 +536,7 @@ public function withStaticDetails(string $details): self * Sets the versioning override to use when starting this workflow. * * @since SDK 2.16.0 - * @since RoadRunner 2025.2.0 + * @since RoadRunner 2025.1.3 * @internal Experimental */ public function withVersioningOverride(?VersioningOverride $override): self diff --git a/src/Common/Versioning/VersioningBehavior.php b/src/Common/Versioning/VersioningBehavior.php index ca8573b1..da3080a3 100644 --- a/src/Common/Versioning/VersioningBehavior.php +++ b/src/Common/Versioning/VersioningBehavior.php @@ -16,7 +16,7 @@ * @see \Temporal\Api\Enums\V1\VersioningBehavior * * @since SDK 2.16.0 - * @since RoadRunner 2025.2.0 + * @since RoadRunner 2025.1.3 * @internal Experimental */ enum VersioningBehavior: int @@ -64,4 +64,14 @@ enum VersioningBehavior: int * complete on the old Version. */ case AutoUpgrade = 2; + + public static function tryFromName(?string $name): ?self + { + return match ($name) { + 'Unspecified', null => self::Unspecified, + 'Pinned' => self::Pinned, + 'AutoUpgrade' => self::AutoUpgrade, + default => null, + }; + } } diff --git a/src/Common/Versioning/VersioningOverride.php b/src/Common/Versioning/VersioningOverride.php index 1b6b9503..0073d595 100644 --- a/src/Common/Versioning/VersioningOverride.php +++ b/src/Common/Versioning/VersioningOverride.php @@ -15,7 +15,7 @@ * Represents the override of a worker's versioning behavior for a workflow execution. * * @since SDK 2.16.0 - * @since RoadRunner 2025.2.0 + * @since RoadRunner 2025.1.3 * @internal Experimental */ final class VersioningOverride diff --git a/src/Common/Versioning/WorkerDeploymentVersion.php b/src/Common/Versioning/WorkerDeploymentVersion.php index ead8acae..11154f2f 100644 --- a/src/Common/Versioning/WorkerDeploymentVersion.php +++ b/src/Common/Versioning/WorkerDeploymentVersion.php @@ -14,7 +14,7 @@ * @see \Temporal\Api\Deployment\V1\WorkerDeploymentVersion * * @since SDK 2.16.0 - * @since RoadRunner 2025.2.0 + * @since RoadRunner 2025.1.3 * @internal Experimental */ class WorkerDeploymentVersion implements \Stringable diff --git a/src/Internal/Transport/Router/GetWorkerInfo.php b/src/Internal/Transport/Router/GetWorkerInfo.php index 362392fd..915141de 100644 --- a/src/Internal/Transport/Router/GetWorkerInfo.php +++ b/src/Internal/Transport/Router/GetWorkerInfo.php @@ -44,10 +44,10 @@ public function handle(ServerRequestInterface $request, array $headers, Deferred private function workerToArray(WorkerInterface $worker): array { $workflowMap = static fn(WorkflowPrototype $workflow): array => [ - 'Name' => $workflow->getID(), - 'Queries' => \array_keys($workflow->getQueryHandlers()), - 'Signals' => \array_keys($workflow->getSignalHandlers()), - 'VersioningBehavior' => $workflow->getVersioningBehavior()->value, + 'name' => $workflow->getID(), + 'queries' => \array_keys($workflow->getQueryHandlers()), + 'signals' => \array_keys($workflow->getSignalHandlers()), + 'versioning_behavior' => $workflow->getVersioningBehavior()->value, ]; $activityMap = static fn(ActivityPrototype $activity): array => [ diff --git a/src/Worker/WorkerDeploymentOptions.php b/src/Worker/WorkerDeploymentOptions.php index 1f61c1e8..f8f2f7ef 100644 --- a/src/Worker/WorkerDeploymentOptions.php +++ b/src/Worker/WorkerDeploymentOptions.php @@ -26,7 +26,7 @@ class WorkerDeploymentOptions #[Marshal(name: 'UseVersioning')] private readonly bool $useVersioning; - #[Marshal(name: 'Version', nullable: true)] + #[Marshal(name: 'Version')] private readonly ?WorkerDeploymentVersion $version; #[Marshal(name: 'DefaultVersioningBehavior', type: EnumValueType::class)] diff --git a/src/Worker/WorkerOptions.php b/src/Worker/WorkerOptions.php index 8f9e2311..edf86c4b 100644 --- a/src/Worker/WorkerOptions.php +++ b/src/Worker/WorkerOptions.php @@ -325,7 +325,7 @@ class WorkerOptions * Optional: If set it configures Worker Versioning for this worker. * * @since SDK 2.16.0 - * @since RoadRunner 2025.2.0 + * @since RoadRunner 2025.1.3 * @internal Experimental. */ #[Marshal(name: 'DeploymentOptions')] @@ -852,7 +852,7 @@ public function withUseBuildIDForVersioning(bool $useBuildIDForVersioning = true * Set deployment options for the worker. * * @since SDK 2.16.0 - * @since RoadRunner 2025.2.0 + * @since RoadRunner 2025.1.3 * @internal Experimental. */ #[Pure] From bca65f2a14e82160b2e9de8076860aae74864d9d Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Fri, 22 Aug 2025 12:31:24 +0400 Subject: [PATCH 15/15] test: enhance versioning tests --- .../Extra/Versioning/DeploymentTest.php | 129 +++++++++++------- tests/Unit/DTO/WorkerOptionsTestCase.php | 2 +- tests/Unit/DTO/WorkflowOptionsTestCase.php | 1 + 3 files changed, 80 insertions(+), 52 deletions(-) diff --git a/tests/Acceptance/Extra/Versioning/DeploymentTest.php b/tests/Acceptance/Extra/Versioning/DeploymentTest.php index f26a0cc2..07365beb 100644 --- a/tests/Acceptance/Extra/Versioning/DeploymentTest.php +++ b/tests/Acceptance/Extra/Versioning/DeploymentTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\Attributes\Test; use Temporal\Client\WorkflowClientInterface; use Temporal\Client\WorkflowOptions; +use Temporal\Common\Uuid; use Temporal\Common\Versioning\VersioningBehavior; use Temporal\Common\Versioning\VersioningOverride; use Temporal\Common\Versioning\WorkerDeploymentVersion; @@ -23,53 +24,6 @@ #[Worker(options: [WorkerFactory::class, 'options'])] class DeploymentTest extends TestCase { - public static function executeWorkflow( - TemporalStarter $starter, - WorkflowClientInterface $client, - Feature $feature, - string $workflowType, - WorkflowOptions $options, - ): ?VersioningBehavior { - WorkerFactory::setCurrentDeployment($starter); - - try { - # Create a Workflow stub with an execution timeout 12 seconds - $stub = $client - ->withTimeout(10) - ->newUntypedWorkflowStub( - /** @see PinnedWorkflow */ - $workflowType, - $options - ->withTaskQueue($feature->taskQueue) - ->withWorkflowExecutionTimeout(20), - ); - - # Start the Workflow - $client->start($stub); - - # Wait for the Workflow to complete - $stub->getResult(timeout: 5); - - # Check the Workflow History - foreach ($client->getWorkflowHistory($stub->getExecution()) as $event) { - if ($event->hasWorkflowTaskCompletedEventAttributes()) { - $version = $event->getWorkflowTaskCompletedEventAttributes()?->getDeploymentVersion(); - self::assertNotNull($version); - self::assertSame(WorkerFactory::DEPLOYMENT_NAME, $version->getDeploymentName()); - self::assertSame(WorkerFactory::BUILD_ID, $version->getBuildId()); - - return VersioningBehavior::tryFrom( - $event->getWorkflowTaskCompletedEventAttributes()?->getVersioningBehavior(), - ); - } - } - - throw new \RuntimeException('The WorkflowTaskCompletedEventAttributes not found in the Workflow history.'); - } finally { - $starter->stop() and $starter->start(); - } - } - #[Test] public function defaultBehaviorAuto( TemporalStarter $starter, @@ -93,15 +47,26 @@ public function customBehaviorPinned( WorkflowClientInterface $client, Feature $feature, ): void { + $id = Uuid::v4(); $behavior = self::executeWorkflow( $starter, $client, $feature, /** @see PinnedWorkflow */ 'Extra_Versioning_Deployment_Pinned', - WorkflowOptions::new(), + WorkflowOptions::new() + ->withWorkflowId($id), ); + # Check worker registration self::assertSame(VersioningBehavior::Pinned, $behavior); + + # Check Override from Search Attributes + $sa = $client->newUntypedRunningWorkflowStub( + workflowID: $id, + workflowType: 'Extra_Versioning_Deployment_Pinned', + )->describe()->info->searchAttributes->getValues(); + self::assertSame('Pinned', $sa['TemporalWorkflowVersioningBehavior']); + self::assertSame('foo:baz', $sa['TemporalWorkerDeploymentVersion']); } #[Test] @@ -110,15 +75,28 @@ public function versionBehaviorOverrideAutoUpgrade( WorkflowClientInterface $client, Feature $feature, ): void { + $id = Uuid::v4(); $behavior = self::executeWorkflow( $starter, $client, $feature, /** @see PinnedWorkflow */ 'Extra_Versioning_Deployment_Pinned', - WorkflowOptions::new()->withVersioningOverride(VersioningOverride::autoUpgrade()), + WorkflowOptions::new() + ->withWorkflowId($id) + ->withVersioningOverride(VersioningOverride::autoUpgrade()), ); - self::assertSame(VersioningBehavior::AutoUpgrade, $behavior); + + # Check worker registration + self::assertSame(VersioningBehavior::Pinned, $behavior); + + # Check Override from Search Attributes + $sa = $client->newUntypedRunningWorkflowStub( + workflowID: $id, + workflowType: 'Extra_Versioning_Deployment_Pinned', + )->describe()->info->searchAttributes->getValues(); + self::assertSame('AutoUpgrade', $sa['TemporalWorkflowVersioningBehavior']); + self::assertSame('foo:baz', $sa['TemporalWorkerDeploymentVersion']); } #[Test] @@ -140,7 +118,56 @@ public function versionBehaviorOverridePinned( ), )), ); - self::assertSame(VersioningBehavior::Pinned, $behavior); + + # Check worker registration + self::assertSame(VersioningBehavior::AutoUpgrade, $behavior); + } + + private static function executeWorkflow( + TemporalStarter $starter, + WorkflowClientInterface $client, + Feature $feature, + string $workflowType, + WorkflowOptions $options, + ): ?VersioningBehavior { + WorkerFactory::setCurrentDeployment($starter); + + try { + # Create a Workflow stub with an execution timeout 12 seconds + $stub = $client + ->withTimeout(10) + ->newUntypedWorkflowStub( + /** @see PinnedWorkflow */ + $workflowType, + $options + ->withTaskQueue($feature->taskQueue) + ->withWorkflowExecutionTimeout(20), + ); + + # Start the Workflow + $client->start($stub); + + # Wait for the Workflow to complete + $stub->getResult(timeout: 5); + + # Check the Workflow History + foreach ($client->getWorkflowHistory($stub->getExecution()) as $event) { + if ($event->hasWorkflowTaskCompletedEventAttributes()) { + $version = $event->getWorkflowTaskCompletedEventAttributes()?->getDeploymentVersion(); + self::assertNotNull($version); + self::assertSame(WorkerFactory::DEPLOYMENT_NAME, $version->getDeploymentName()); + self::assertSame(WorkerFactory::BUILD_ID, $version->getBuildId()); + + return VersioningBehavior::tryFrom( + $event->getWorkflowTaskCompletedEventAttributes()?->getVersioningBehavior(), + ); + } + } + + throw new \RuntimeException('The WorkflowTaskCompletedEventAttributes not found in the Workflow history.'); + } finally { + $starter->stop() and $starter->start(); + } } } diff --git a/tests/Unit/DTO/WorkerOptionsTestCase.php b/tests/Unit/DTO/WorkerOptionsTestCase.php index 6add9842..ad004b50 100644 --- a/tests/Unit/DTO/WorkerOptionsTestCase.php +++ b/tests/Unit/DTO/WorkerOptionsTestCase.php @@ -91,7 +91,7 @@ public function testDeploymentOptionsUseVersion(): void self::assertTrue($options['UseVersioning']); self::assertSame(VersioningBehavior::AutoUpgrade->value, $options['DefaultVersioningBehavior']); - self::assertSame('foo.bar', $options['Version']); + self::assertSame(['DeploymentName' => 'foo', 'BuildId' => 'bar'], $options['Version']); } public function testMaxConcurrentActivityExecutionSize(): void diff --git a/tests/Unit/DTO/WorkflowOptionsTestCase.php b/tests/Unit/DTO/WorkflowOptionsTestCase.php index b71054d1..074ea08c 100644 --- a/tests/Unit/DTO/WorkflowOptionsTestCase.php +++ b/tests/Unit/DTO/WorkflowOptionsTestCase.php @@ -51,6 +51,7 @@ public function testMarshalling(): void 'Priority' => [ 'priority_key' => 0, ], + 'VersioningOverride' => null, ]; $result = $this->marshal($dto);