Skip to content

Add endpoint-aware serialization for Nexus operations - #2644

Open
JoshuaFrenchwood wants to merge 4 commits into
mainfrom
feature/endpoint-aware-nexus-serialization
Open

Add endpoint-aware serialization for Nexus operations#2644
JoshuaFrenchwood wants to merge 4 commits into
mainfrom
feature/endpoint-aware-nexus-serialization

Conversation

@JoshuaFrenchwood

Copy link
Copy Markdown

What was changed

Added caller side converter.NexusSerializationContext support for workflow-scheduled Nexus operations.
This context gives the Nexus Endpoint, Service, and operation name to the data/failure converters

Why?

This enables codecs to select serialization behavior or encryption keys by Nexus endpoint, service, or operation.

For example, workflows calling two Nexus endpoints can encrypt each endpoint’s payloads with a different key while ensuring that inputs, results, and failures are decoded with the converter selected for the corresponding operation.

Checklist

  1. Closes

  2. How was this tested:

Added unit/functional tests to verify that NexusSerializationContext works as expected.

  1. Any docs updates needed?

@JoshuaFrenchwood
JoshuaFrenchwood requested a review from a team as a code owner September 1, 2026 14:59
@JoshuaFrenchwood
JoshuaFrenchwood force-pushed the feature/endpoint-aware-nexus-serialization branch from 6d95b6a to e31beb5 Compare September 1, 2026 19:36

@chrsmith chrsmith left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Lots of nitpicks and some questions. But this all looks right. (Though I'd defer approval to someone with a little more background on the SDKs and/or stronger opinions about how any newly exported types/methods should look.)


// SerializationContext provides metadata about where serialization is occurring.
// Implementations include [WorkflowSerializationContext] for workflow-level
// payloads, and [ActivitySerializationContext] for activity-level payloads.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor nit: since there are 3x different things, maybe we should use a list rather than a really long sentence? e.g.

// SerializationContext provides metadata about where serialization is occurring,
// with the concrete type depending on the context.
// - [WorkflowSerializationContext] for workflow-level payloads
// - [ActivitySerializationContext] for activity-level payloads
// - [NexusSerializationContext] for caller-side Nexus operation payloads

Also, what is a "caller-side Nexus operation payload"? Would it be more accurate or clearer to say "Nexus handlers" instead?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, I updated this to be more clear. By caller side, I meant workflows that are calling nexus operations.

Comment thread converter/serialization_context.go Outdated

// NexusSerializationContext is the serialization context for caller-side Nexus
// operation input, successful result, and failure decoding. Operation summaries
// and user metadata use the workflow serialization context instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is the typical reader of this code supposed to know what "Operation summaries" or "user metadata" are referring to? It's probably benign, but if we expect different fields of the same proto to be ran through different serializers, that sounds like a bad thing?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I removed that last sentence and made this more readable.

Comment thread internal/internal_event_handlers.go Outdated
@@ -2088,7 +2094,7 @@ func (weh *workflowExecutionEventHandlerImpl) handleNexusOperationCompleted(even
state := command.getData().(*scheduledNexusOperation)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit: Is there a better name we can use here than state? We are essentially casting the command's data to the underlying "event type", right? Would something like event or eventSource be clearer?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I renamed it scheduleNexusOperation

Comment thread internal/internal_workflow_testsuite.go Outdated
callback func(*commonpb.Payload, error),
startedHandler func(opID string, e error),
) int64 {
failureConverter := params.failureConverter

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit: This is perfectly fine as-is, but you can use cmp.Or(params.failureConverter, env.failureConverter) to simplify this type of initialization.
https://pkg.go.dev/cmp#Or

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Nice! used that instead thanks

Comment thread internal/workflow.go
return ExecuteNexusOperationParams{}, fmt.Errorf("invalid 'operation' parameter, must be an OperationReference or a string")
}

nsc := converter.NexusSerializationContext{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit: For readability, maybe add a comment to group these things? What do you think of:

// Nexus context and data converters.

Comment thread test/serialization_context_test.go Outdated

const intTestNexusHMACEncoding = "binary/nexus-hmac-test"

func intTestNexusContextKey(endpoint, service, operation string) string {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit: Is "intTest" short for integration test? If so, maybe its all fine as-is. But IMHO, something like nexusContextToCodecKey might be clearer?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yeah intTest is integration test, I renamed it to "intTestNexusCodecKey"

Comment thread test/serialization_context_test.go Outdated
ts.NoError(err)
var result string
ts.NoError(run.Get(ctx, &result))
ts.ElementsMatch([]string{"hmac", "zlib"}, strings.Split(result, "|"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

For readability, could we have the workflow expose a proper type rather than a string?

We want to verify that the decoded results match, but It would be great if we could also look at the raw, encoded results between the two codecs and confirm that they do NOT match. (i.e. the hmac and zlib encoders are using two different encodings for the same data.)

Is there any way we can easily verify that? Or would we need to have some sort of state smuggled out of the intTestNexusCodecSelector?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Great point. I updated this workflow to return a struct, and ts.Equal for the final result. Also at the end of the test I look in the History to verify that the encoded data does not match.

Comment thread CHANGELOG.md
### Added

- Added caller-side `converter.NexusSerializationContext` support for Nexus operation inputs,
results, and failures. Each scheduled operation retains an isolated contextual data and failure

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

How is this supposed to work in the long run?

  • This is wired up for Nexus invocations made from workflows, but what about SANOs?
  • If the handler-side is supposed to use the same converter, how will this actually work?

I'm just trying to understand the next few steps in getting this all working end-to-end.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes the long term is to have SANO support. This PR starts with Nexus calls from in workflows because the Nexus Context (service, endpoint, operation) are present. Adding SANO support and handler side will require server changes, because that context is not currently returned when a SANO result or failure is polled, or consistently propagated to Temporal entities started by Nexus handlers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants