-
Notifications
You must be signed in to change notification settings - Fork 212
Add Temporal Nexus Operation Handler #2842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Quinn-With-Two-Ns
wants to merge
7
commits into
temporalio:master
Choose a base branch
from
Quinn-With-Two-Ns:temporal-nexus-operation-handler
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
5863dba
Add Temporal Nexus Operation Handler
Quinn-With-Two-Ns 6641b14
Update
Quinn-With-Two-Ns 05a2002
Update some docs
Quinn-With-Two-Ns acfa2e8
Update calling convention
Quinn-With-Two-Ns d1cd542
Code review
Quinn-With-Two-Ns ee62ae7
Address feedback
Quinn-With-Two-Ns a33ff01
Add check for duplicate handler start
Quinn-With-Two-Ns File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
80 changes: 80 additions & 0 deletions
80
temporal-sdk/src/main/java/io/temporal/internal/nexus/NexusStartWorkflowHelper.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| package io.temporal.internal.nexus; | ||
|
|
||
| import static io.temporal.internal.common.LinkConverter.workflowEventToNexusLink; | ||
| import static io.temporal.internal.common.NexusUtil.nexusProtoLinkToLink; | ||
|
|
||
| import io.nexusrpc.handler.HandlerException; | ||
| import io.nexusrpc.handler.OperationContext; | ||
| import io.nexusrpc.handler.OperationStartDetails; | ||
| import io.temporal.api.common.v1.Link; | ||
| import io.temporal.api.common.v1.WorkflowExecution; | ||
| import io.temporal.api.enums.v1.EventType; | ||
| import io.temporal.internal.client.NexusStartWorkflowRequest; | ||
| import io.temporal.internal.client.NexusStartWorkflowResponse; | ||
| import java.net.URISyntaxException; | ||
| import java.util.function.Function; | ||
|
|
||
| /** | ||
| * Shared helper for starting a workflow from a Nexus operation and attaching workflow links to the | ||
| * operation context. Used by both {@code WorkflowRunOperationImpl} and {@code TemporalNexusClient}. | ||
| */ | ||
| public class NexusStartWorkflowHelper { | ||
|
|
||
| /** | ||
| * Starts a workflow via the provided invoker function, attaches workflow links to the operation | ||
| * context, and returns the response. | ||
| * | ||
| * @param ctx the operation context (links will be attached as a side-effect) | ||
| * @param details the operation start details containing requestId, callback, links | ||
| * @param invoker function that starts the workflow given a {@link NexusStartWorkflowRequest} | ||
| * @return the {@link NexusStartWorkflowResponse} containing the operation token and workflow | ||
| * execution | ||
| */ | ||
| public static NexusStartWorkflowResponse startWorkflowAndAttachLinks( | ||
| OperationContext ctx, | ||
| OperationStartDetails details, | ||
| Function<NexusStartWorkflowRequest, NexusStartWorkflowResponse> invoker) { | ||
| InternalNexusOperationContext nexusCtx = CurrentNexusOperationContext.get(); | ||
|
|
||
| NexusStartWorkflowRequest nexusRequest = | ||
| new NexusStartWorkflowRequest( | ||
| details.getRequestId(), | ||
| details.getCallbackUrl(), | ||
| details.getCallbackHeaders(), | ||
| nexusCtx.getTaskQueue(), | ||
| details.getLinks()); | ||
|
|
||
| NexusStartWorkflowResponse response = invoker.apply(nexusRequest); | ||
| WorkflowExecution workflowExec = response.getWorkflowExecution(); | ||
|
|
||
| // If the start workflow response returned a link use it, otherwise | ||
| // create the link information about the new workflow and return to the caller. | ||
| Link.WorkflowEvent workflowEventLink = | ||
| nexusCtx.getStartWorkflowResponseLink().hasWorkflowEvent() | ||
| ? nexusCtx.getStartWorkflowResponseLink().getWorkflowEvent() | ||
| : null; | ||
| if (workflowEventLink == null) { | ||
| workflowEventLink = | ||
| Link.WorkflowEvent.newBuilder() | ||
| .setNamespace(nexusCtx.getNamespace()) | ||
| .setWorkflowId(workflowExec.getWorkflowId()) | ||
| .setRunId(workflowExec.getRunId()) | ||
| .setEventRef( | ||
| Link.WorkflowEvent.EventReference.newBuilder() | ||
| .setEventType(EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED)) | ||
| .build(); | ||
| } | ||
| io.temporal.api.nexus.v1.Link nexusLink = workflowEventToNexusLink(workflowEventLink); | ||
| if (nexusLink != null) { | ||
| try { | ||
| ctx.addLinks(nexusProtoLinkToLink(nexusLink)); | ||
| } catch (URISyntaxException e) { | ||
| throw new HandlerException(HandlerException.ErrorType.INTERNAL, "failed to parse URI", e); | ||
| } | ||
| } | ||
|
|
||
| return response; | ||
| } | ||
|
|
||
| private NexusStartWorkflowHelper() {} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
130 changes: 130 additions & 0 deletions
130
temporal-sdk/src/main/java/io/temporal/nexus/TemporalNexusClient.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| package io.temporal.nexus; | ||
|
|
||
| import io.nexusrpc.handler.OperationContext; | ||
| import io.nexusrpc.handler.OperationStartDetails; | ||
| import io.temporal.client.WorkflowClient; | ||
| import io.temporal.client.WorkflowOptions; | ||
| import io.temporal.client.WorkflowStub; | ||
| import io.temporal.common.Experimental; | ||
| import io.temporal.internal.client.NexusStartWorkflowResponse; | ||
| import io.temporal.internal.nexus.NexusStartWorkflowHelper; | ||
| import io.temporal.workflow.Functions; | ||
| import java.util.Objects; | ||
|
|
||
| /** | ||
| * Nexus-aware client wrapping {@link WorkflowClient}. Provides methods for interacting with | ||
| * Temporal workflows from within a Nexus operation handler. | ||
| * | ||
| * <p>Obtained via the {@link TemporalOperationHandler.StartFunction} parameter. The client creates | ||
| * workflow stubs internally — users pass the workflow class, a lambda that calls the workflow | ||
| * method, and workflow options. | ||
| * | ||
| * <p>Usage example: | ||
| * | ||
| * <pre>{@code | ||
| * @OperationImpl | ||
| * public OperationHandler<OrderInput, OrderResult> createOrder() { | ||
| * return TemporalOperationHandler.from((context, client, input) -> { | ||
| * return client.startWorkflow( | ||
| * OrderWorkflow.class, | ||
| * wf -> wf.processOrder(input), | ||
| * WorkflowOptions.newBuilder() | ||
| * .setWorkflowId("order-" + context.getRequestId()) | ||
| * .build()); | ||
| * }); | ||
| * } | ||
| * }</pre> | ||
| * | ||
| * <p>For advanced use cases, the underlying {@link WorkflowClient} can be accessed via {@link | ||
| * #getWorkflowClient()}. For example, to send a signal and return a synchronous result: | ||
| * | ||
| * <pre>{@code | ||
| * @OperationImpl | ||
| * public OperationHandler<CancelOrderInput, Void> cancelOrder() { | ||
| * return TemporalOperationHandler.from((context, client, input) -> { | ||
| * client.getWorkflowClient() | ||
| * .newUntypedWorkflowStub("order-" + input.getOrderId()) | ||
| * .signal("requestCancellation", input); | ||
| * return TemporalOperationResult.sync(null); | ||
| * }); | ||
| * } | ||
| * }</pre> | ||
| */ | ||
| @Experimental | ||
| public final class TemporalNexusClient { | ||
|
|
||
| private final WorkflowClient client; | ||
| private final OperationContext operationContext; | ||
| private final OperationStartDetails operationStartDetails; | ||
|
|
||
| TemporalNexusClient( | ||
| WorkflowClient client, | ||
| OperationContext operationContext, | ||
| OperationStartDetails operationStartDetails) { | ||
| this.client = Objects.requireNonNull(client); | ||
| this.operationContext = Objects.requireNonNull(operationContext); | ||
| this.operationStartDetails = Objects.requireNonNull(operationStartDetails); | ||
| } | ||
|
|
||
| /** Returns the underlying {@link WorkflowClient} for advanced use cases. */ | ||
| public WorkflowClient getWorkflowClient() { | ||
| return client; | ||
| } | ||
|
|
||
| /** | ||
| * Starts a workflow by invoking a returning method on a workflow stub. The client creates the | ||
| * stub from the given class and options, then invokes the workflow method via the provided | ||
| * function. | ||
| * | ||
| * <p>Example: | ||
| * | ||
| * <pre>{@code | ||
| * client.startWorkflow(MyWorkflow.class, wf -> wf.run(input), options) | ||
| * }</pre> | ||
| * | ||
| * <p>For void-returning workflow methods, use a block lambda that returns null: | ||
| * | ||
| * <pre>{@code | ||
| * client.startWorkflow(MyWorkflow.class, wf -> { wf.execute(input); return null; }, options) | ||
| * }</pre> | ||
| * | ||
| * @param workflowClass the workflow interface class | ||
| * @param workflowMethod receives the workflow stub and calls exactly one workflow method | ||
| * @param options workflow start options (must include workflowId) | ||
| * @param <T> the workflow interface type | ||
| * @param <R> the workflow return type | ||
| * @return an async {@link TemporalOperationResult} with the workflow-run operation token | ||
| */ | ||
| public <T, R> TemporalOperationResult<R> startWorkflow( | ||
| Class<T> workflowClass, Functions.Func1<T, R> workflowMethod, WorkflowOptions options) { | ||
| T stub = client.newWorkflowStub(workflowClass, options); | ||
| Functions.Func<R> bound = () -> workflowMethod.apply(stub); | ||
| return invokeAndReturn(WorkflowHandle.fromWorkflowMethod(bound)); | ||
| } | ||
|
|
||
| /** | ||
| * Starts a workflow using an untyped workflow type name. | ||
| * | ||
| * @param workflowType the workflow type name string | ||
| * @param resultClass the expected result class | ||
| * @param args workflow arguments | ||
| * @param options workflow start options (must include workflowId) | ||
| * @param <R> the workflow return type | ||
| * @return an async {@link TemporalOperationResult} with the workflow-run operation token | ||
| */ | ||
| public <R> TemporalOperationResult<R> startWorkflow( | ||
| String workflowType, Class<R> resultClass, Object[] args, WorkflowOptions options) { | ||
| WorkflowStub stub = client.newUntypedWorkflowStub(workflowType, options); | ||
| WorkflowHandle<R> handle = WorkflowHandle.fromWorkflowStub(stub, resultClass, args); | ||
| return invokeAndReturn(handle); | ||
| } | ||
|
|
||
| private <R> TemporalOperationResult<R> invokeAndReturn(WorkflowHandle<R> handle) { | ||
| NexusStartWorkflowResponse response = | ||
| NexusStartWorkflowHelper.startWorkflowAndAttachLinks( | ||
| operationContext, | ||
| operationStartDetails, | ||
| request -> handle.getInvoker().invoke(request)); | ||
| return TemporalOperationResult.async(response.getOperationToken()); | ||
| } | ||
| } |
49 changes: 49 additions & 0 deletions
49
temporal-sdk/src/main/java/io/temporal/nexus/TemporalOperationCancelContext.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package io.temporal.nexus; | ||
|
|
||
| import io.nexusrpc.handler.OperationCancelDetails; | ||
| import io.nexusrpc.handler.OperationContext; | ||
| import io.temporal.common.Experimental; | ||
| import java.util.Objects; | ||
|
|
||
| /** | ||
| * Context for a Nexus cancel operation. Combines the {@link OperationContext} and {@link | ||
| * OperationCancelDetails} into a single object passed to cancel methods on {@link | ||
| * TemporalOperationHandler}. | ||
| */ | ||
| @Experimental | ||
| public final class TemporalOperationCancelContext { | ||
|
|
||
| private final OperationContext operationContext; | ||
| private final OperationCancelDetails operationCancelDetails; | ||
|
|
||
| TemporalOperationCancelContext( | ||
| OperationContext operationContext, OperationCancelDetails operationCancelDetails) { | ||
| this.operationContext = Objects.requireNonNull(operationContext); | ||
| this.operationCancelDetails = Objects.requireNonNull(operationCancelDetails); | ||
| } | ||
|
|
||
| /** Returns the service name for this operation. */ | ||
| public String getService() { | ||
| return operationContext.getService(); | ||
| } | ||
|
|
||
| /** Returns the operation name. */ | ||
| public String getOperation() { | ||
| return operationContext.getOperation(); | ||
| } | ||
|
|
||
| /** Returns the operation token identifying the operation to cancel. */ | ||
| public String getOperationToken() { | ||
| return operationCancelDetails.getOperationToken(); | ||
| } | ||
|
|
||
| /** Returns the underlying {@link OperationContext} for advanced use cases. */ | ||
| public OperationContext getOperationContext() { | ||
| return operationContext; | ||
| } | ||
|
|
||
| /** Returns the underlying {@link OperationCancelDetails} for advanced use cases. */ | ||
| public OperationCancelDetails getOperationCancelDetails() { | ||
| return operationCancelDetails; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.