Skip to content

Commit 44248d1

Browse files
committed
feat(nexus): integrate NexusEnvironment and NexusTaskHandler into ServiceContainer; streamline task handling logic
1 parent 44916ac commit 44248d1

9 files changed

Lines changed: 58 additions & 71 deletions

File tree

src/Internal/Nexus/NexusContext.php

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020
/**
2121
* Composite per-dispatch Nexus context, returned by {@see \Temporal\Nexus\Nexus::getCurrentContext()}.
2222
*
23-
* Carries the public Temporal-side {@see $operation} info and the handler-side
24-
* {@see $current} ({@see OperationContext} with links/headers/deadline). The
25-
* {@see $environment} (WorkflowClient) and {@see $outboundPipeline} are internal
26-
* plumbing and should not be consumed by user code.
23+
* {@see $current} (the handler-side {@see OperationContext} with links/headers/deadline) is the
24+
* one invariant of a dispatch — it is always present. The remaining fields are conditional:
25+
* {@see $operation}/{@see $environment} are null until the worker environment is bound;
26+
* {@see $startDetails} and {@see $cancelDetails} are mutually exclusive (a dispatch is a start
27+
* XOR a cancel); {@see $outboundPipeline} is null when no interceptor pipeline applies. The
28+
* {@see $environment} (WorkflowClient) and {@see $outboundPipeline} are internal plumbing and
29+
* should not be consumed by user code.
2730
*/
2831
final class NexusContext
2932
{
@@ -32,36 +35,11 @@ final class NexusContext
3235
* @internal $environment and $outboundPipeline are framework plumbing.
3336
*/
3437
public function __construct(
38+
public readonly OperationContext $current,
3539
public readonly ?NexusOperationContext $operation = null,
3640
public readonly ?NexusEnvironment $environment = null,
37-
public readonly ?OperationContext $current = null,
3841
public readonly ?OperationStartDetails $startDetails = null,
3942
public readonly ?OperationCancelDetails $cancelDetails = null,
4043
public readonly ?Pipeline $outboundPipeline = null,
4144
) {}
42-
43-
public function withOperation(?NexusOperationContext $operation): self
44-
{
45-
return new self($operation, $this->environment, $this->current, $this->startDetails, $this->cancelDetails, $this->outboundPipeline);
46-
}
47-
48-
public function withEnvironment(?NexusEnvironment $environment): self
49-
{
50-
return new self($this->operation, $environment, $this->current, $this->startDetails, $this->cancelDetails, $this->outboundPipeline);
51-
}
52-
53-
public function withCurrent(?OperationContext $current): self
54-
{
55-
return new self($this->operation, $this->environment, $current, $this->startDetails, $this->cancelDetails, $this->outboundPipeline);
56-
}
57-
58-
public function withStartDetails(?OperationStartDetails $details): self
59-
{
60-
return new self($this->operation, $this->environment, $this->current, $details, $this->cancelDetails, $this->outboundPipeline);
61-
}
62-
63-
public function withCancelDetails(?OperationCancelDetails $details): self
64-
{
65-
return new self($this->operation, $this->environment, $this->current, $this->startDetails, $details, $this->outboundPipeline);
66-
}
6745
}

src/Internal/Nexus/NexusTaskHandler.php

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
final class NexusTaskHandler
4848
{
4949
private ?ServiceHandler $serviceHandler = null;
50-
private ?NexusEnvironment $environment = null;
5150

5251
/**
5352
* @param bool $includeTracebackInFailure Disable in cross-trust-boundary
@@ -58,6 +57,7 @@ public function __construct(
5857
private readonly DataConverterInterface $dataConverter,
5958
private readonly bool $includeTracebackInFailure = true,
6059
private readonly PipelineProvider $interceptorProvider = new SimplePipelineProvider(),
60+
private readonly ?NexusEnvironment $environment = null,
6161
) {}
6262

6363
/**
@@ -85,15 +85,6 @@ public static function deadlineFromHeaders(array $headers): ?\DateTimeImmutable
8585
}
8686
}
8787

88-
/**
89-
* Bind worker environment for {@see \Temporal\Nexus\Nexus::getOperationContext()}. Idempotent.
90-
*/
91-
public function withNexusEnvironment(NexusEnvironment $environment): self
92-
{
93-
$this->environment = $environment;
94-
return $this;
95-
}
96-
9788
public function handleStartOperation(Request $request, ?MethodCanceller $methodCanceller = null): Response
9889
{
9990
$startRequest = $request->getStartOperation();

src/Internal/ServiceContainer.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
use Temporal\Internal\Declaration\Reader\ActivityReader;
2424
use Temporal\Internal\Declaration\Reader\NexusServiceReader;
2525
use Temporal\Internal\Declaration\Reader\WorkflowReader;
26+
use Temporal\Internal\Nexus\NexusEnvironment;
2627
use Temporal\Internal\Nexus\NexusInvocationRegistry;
28+
use Temporal\Internal\Nexus\NexusTaskHandler;
2729
use Temporal\Internal\Marshaller\MarshallerInterface;
2830
use Temporal\Internal\Queue\QueueInterface;
2931
use Temporal\Internal\Repository\RepositoryInterface;
@@ -42,6 +44,7 @@ final class ServiceContainer
4244
public readonly ActivityCollection $activities;
4345
public readonly NexusServiceCollection $nexusServices;
4446
public readonly NexusInvocationRegistry $nexusInvocations;
47+
public readonly NexusTaskHandler $nexusTaskHandler;
4548
public readonly WorkflowReader $workflowsReader;
4649
public readonly ActivityReader $activitiesReader;
4750
public readonly NexusServiceReader $nexusServicesReader;
@@ -60,11 +63,18 @@ public function __construct(
6063
public readonly ExceptionInterceptorInterface $exceptionInterceptor,
6164
public readonly PipelineProvider $interceptorProvider,
6265
public readonly LoggerInterface $logger,
66+
public readonly ?NexusEnvironment $nexusEnvironment = null,
6367
) {
6468
$this->workflows = new WorkflowCollection();
6569
$this->activities = new ActivityCollection();
6670
$this->nexusServices = new NexusServiceCollection();
6771
$this->nexusInvocations = new NexusInvocationRegistry();
72+
$this->nexusTaskHandler = new NexusTaskHandler(
73+
$this->nexusServices,
74+
$this->dataConverter,
75+
interceptorProvider: $this->interceptorProvider,
76+
environment: $nexusEnvironment,
77+
);
6878
$this->running = new ProcessCollection();
6979
$this->workflowsReader = new WorkflowReader($this->reader);
7080
$this->activitiesReader = new ActivityReader($this->reader);
@@ -76,6 +86,7 @@ public static function fromWorkerFactory(
7686
ExceptionInterceptorInterface $exceptionInterceptor,
7787
PipelineProvider $interceptorProvider,
7888
LoggerInterface $logger,
89+
?NexusEnvironment $nexusEnvironment = null,
7990
): self {
8091
return new self(
8192
$worker,
@@ -88,6 +99,7 @@ public static function fromWorkerFactory(
8899
$exceptionInterceptor,
89100
$interceptorProvider,
90101
$logger,
102+
$nexusEnvironment,
91103
);
92104
}
93105
}

src/Nexus/Nexus.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,7 @@ public static function getCurrentContext(): NexusContext
5353
*/
5454
public static function getCurrentOperationContext(): OperationContext
5555
{
56-
return self::getCurrentContext()->current ?? throw new \LogicException(
57-
'Nexus::getCurrentOperationContext() called outside a Nexus operation dispatch.',
58-
);
56+
return self::getCurrentContext()->current;
5957
}
6058

6159
/**

src/Worker/Worker.php

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
use Temporal\Internal\Declaration\EntityNameValidator;
1616
use Temporal\Internal\Events\EventEmitterTrait;
1717
use Temporal\Internal\Events\EventListenerInterface;
18-
use Temporal\Internal\Nexus\NexusEnvironment;
19-
use Temporal\Internal\Nexus\NexusTaskHandler;
2018
use Temporal\Internal\Repository\RepositoryInterface;
2119
use Temporal\Internal\ServiceContainer;
2220
use Temporal\Internal\Transport\Router;
@@ -37,21 +35,18 @@ class Worker implements WorkerInterface, EventListenerInterface, DispatcherInter
3735
private RouterInterface $router;
3836
private ServiceContainer $services;
3937
private RPCConnectionInterface $rpc;
40-
private ?NexusEnvironment $nexusEnvironment;
4138

4239
public function __construct(
4340
string $taskQueue,
4441
WorkerOptions $options,
4542
ServiceContainer $serviceContainer,
4643
RPCConnectionInterface $rpc,
47-
?NexusEnvironment $nexusEnvironment = null,
4844
) {
4945
EntityNameValidator::validateTaskQueue($taskQueue);
5046

5147
$this->rpc = $rpc;
5248
$this->name = $taskQueue;
5349
$this->options = $options;
54-
$this->nexusEnvironment = $nexusEnvironment;
5550

5651
$this->services = $serviceContainer;
5752
$this->router = $this->createRouter();
@@ -122,6 +117,14 @@ public function getActivities(): RepositoryInterface
122117

123118
public function registerNexusServiceImplementation(object ...$services): WorkerInterface
124119
{
120+
if ($this->services->nexusEnvironment === null && $services !== []) {
121+
throw new \LogicException(
122+
'Cannot register Nexus service implementations on a worker without a WorkflowClient. ' .
123+
'Pass a WorkflowClient to the WorkerFactory (e.g. WorkerFactory::create(client: $workflowClient)) ' .
124+
'— Nexus operations require cluster access.',
125+
);
126+
}
127+
125128
foreach ($services as $service) {
126129
$prototype = $this->services->nexusServicesReader->fromClass(\get_class($service));
127130
$this->services->nexusServices->add($prototype->withInstance($service), false);
@@ -153,27 +156,10 @@ protected function createRouter(): RouterInterface
153156
$router->add(new Router\StackTrace($this->services->running));
154157

155158
// Nexus routes
156-
$nexusHandler = $this->createNexusTaskHandler();
157-
$invocations = $this->services->nexusInvocations;
158-
$router->add(new Router\InvokeNexusOperation($nexusHandler, $invocations, $this->services->dataConverter));
159-
$router->add(new Router\CancelNexusOperation($nexusHandler));
160-
$router->add(new Router\CancelNexusOperationMethod($invocations));
159+
$router->add(new Router\InvokeNexusOperation($this->services->nexusTaskHandler, $this->services->nexusInvocations, $this->services->dataConverter));
160+
$router->add(new Router\CancelNexusOperation($this->services->nexusTaskHandler));
161+
$router->add(new Router\CancelNexusOperationMethod($this->services->nexusInvocations));
161162

162163
return $router;
163164
}
164-
165-
private function createNexusTaskHandler(): NexusTaskHandler
166-
{
167-
$handler = new NexusTaskHandler(
168-
$this->services->nexusServices,
169-
$this->services->dataConverter,
170-
interceptorProvider: $this->services->interceptorProvider,
171-
);
172-
173-
if ($this->nexusEnvironment !== null) {
174-
$handler->withNexusEnvironment($this->nexusEnvironment);
175-
}
176-
177-
return $handler;
178-
}
179165
}

src/WorkerFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,9 @@ public function newWorker(
214214
$options->enableLoggingInReplay,
215215
$taskQueue,
216216
),
217+
$nexusEnvironment,
217218
),
218219
$this->rpc,
219-
$nexusEnvironment,
220220
);
221221

222222
// Call initializeWorker hooks (forward order)

testing/src/WorkerFactory.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Temporal\Interceptor\PipelineProvider;
1515
use Temporal\Interceptor\SimplePipelineProvider;
1616
use Temporal\Internal\Interceptor\Pipeline;
17+
use Temporal\Internal\Nexus\NexusEnvironment;
1718
use Temporal\Internal\ServiceContainer;
1819
use Temporal\Internal\Workflow\Logger;
1920
use Temporal\Plugin\CompositePipelineProvider;
@@ -94,6 +95,15 @@ public function newWorker(
9495
$interceptorProvider ?? new SimplePipelineProvider(),
9596
);
9697

98+
/** @psalm-suppress ArgumentTypeCoercion */
99+
$nexusEnvironment = $this->workflowClient !== null
100+
? new NexusEnvironment(
101+
$this->workflowClient->getClientOptions()->namespace,
102+
$taskQueue,
103+
$this->workflowClient,
104+
)
105+
: null;
106+
97107
$worker = new WorkerMock(
98108
new Worker(
99109
$taskQueue,
@@ -107,6 +117,7 @@ public function newWorker(
107117
$options->enableLoggingInReplay,
108118
$taskQueue,
109119
),
120+
$nexusEnvironment,
110121
),
111122
$this->rpc,
112123
),

tests/Nexus/Unit/NexusOutboundInterceptorTest.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Temporal\Interceptor\Trait\NexusOperationOutboundCallsInterceptorTrait;
1919
use Temporal\Internal\Interceptor\Pipeline;
2020
use Temporal\Internal\Nexus\NexusContext;
21+
use Temporal\Nexus\Handler\OperationContext;
2122
use Temporal\Nexus\Nexus;
2223
use Temporal\Nexus\NexusOperationContext;
2324

@@ -56,6 +57,7 @@ public function getInfo(GetInfoInput $input, callable $next): NexusOperationCont
5657

5758
$info = new NexusOperationContext('ns', 'tq');
5859
Nexus::setCurrentContext(new NexusContext(
60+
current: new OperationContext(service: 'svc', operation: 'op'),
5961
operation: $info,
6062
outboundPipeline: Pipeline::prepare([$record('A'), $record('B')]),
6163
));
@@ -79,6 +81,7 @@ public function getInfo(GetInfoInput $input, callable $next): NexusOperationCont
7981
};
8082

8183
Nexus::setCurrentContext(new NexusContext(
84+
current: new OperationContext(service: 'svc', operation: 'op'),
8285
operation: new NexusOperationContext('ns', 'tq'),
8386
outboundPipeline: Pipeline::prepare([$rewriting]),
8487
));
@@ -92,7 +95,10 @@ public function getInfo(GetInfoInput $input, callable $next): NexusOperationCont
9295
public function testGetInfoReturnsInfoDirectlyWithoutPipeline(): void
9396
{
9497
$info = new NexusOperationContext('ns', 'tq');
95-
Nexus::setCurrentContext(new NexusContext(operation: $info));
98+
Nexus::setCurrentContext(new NexusContext(
99+
current: new OperationContext(service: 'svc', operation: 'op'),
100+
operation: $info,
101+
));
96102

97103
self::assertSame($info, Nexus::getOperationContext());
98104
}

tests/Unit/Nexus/NexusContextAccessorTestCase.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Temporal\Client\WorkflowClientInterface;
99
use Temporal\Internal\Nexus\NexusContext;
1010
use Temporal\Internal\Nexus\NexusEnvironment;
11+
use Temporal\Nexus\Handler\OperationContext;
1112
use Temporal\Nexus\Nexus;
1213
use PHPUnit\Framework\Attributes\CoversClass;
1314
use Temporal\Nexus\NexusOperationContext;
@@ -41,6 +42,7 @@ public function testReturnsCurrentContext(): void
4142
$ctx = new NexusOperationContext('ns', 'tq');
4243

4344
Nexus::setCurrentContext(new NexusContext(
45+
current: new OperationContext(service: 'svc', operation: 'op'),
4446
operation: $ctx,
4547
environment: new NexusEnvironment('ns', 'tq', $client),
4648
));
@@ -62,7 +64,10 @@ public function testPublicContextDoesNotExposeWorkflowClient(): void
6264

6365
public function testClearingRestoresOutsideBehavior(): void
6466
{
65-
Nexus::setCurrentContext(new NexusContext(operation: new NexusOperationContext('ns', 'tq')));
67+
Nexus::setCurrentContext(new NexusContext(
68+
current: new OperationContext(service: 'svc', operation: 'op'),
69+
operation: new NexusOperationContext('ns', 'tq'),
70+
));
6671
Nexus::setCurrentContext(null);
6772

6873
$this->expectException(\LogicException::class);

0 commit comments

Comments
 (0)