Skip to content
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

New Update-With-Start API #1731

Merged
merged 23 commits into from
Dec 6, 2024
Merged

New Update-With-Start API #1731

merged 23 commits into from
Dec 6, 2024

Conversation

dandavison
Copy link
Contributor

@dandavison dandavison commented Nov 28, 2024

New Update-With-Start API.

  • Instead of using ExecuteWorkflow, update-with-start is invoked via a new method UpdateWithStartWorkflow on Client which returns an UpdateHandle
  • The method receives a WithStartWorkflowOperation struct which defines the start operation, and exposes a method to obtain the workflow run:

The snippet below is from the accompanying sample: temporalio/samples-go#370:

tx := earlyreturn.Transaction{ID: uuid.New(), SourceAccount: "Bob", TargetAccount: "Alice", Amount: 100}

startWorkflowOp := c.NewWithStartWorkflowOperation(client.StartWorkflowOptions{
	ID:                       "early-return-workflow-ID-" + tx.ID,
	WorkflowIDConflictPolicy: enumspb.WORKFLOW_ID_CONFLICT_POLICY_FAIL,
	TaskQueue:                earlyreturn.TaskQueueName,
}, earlyreturn.Workflow, tx)

updateHandle, err := c.UpdateWithStartWorkflow(ctxWithTimeout, client.UpdateWithStartWorkflowOptions{
	UpdateOptions: client.UpdateWorkflowOptions{
		UpdateName:   earlyreturn.UpdateName,
		WaitForStage: client.WorkflowUpdateStageCompleted,
	},
	StartOperation: startWorkflowOp,
})
if err != nil {
	log.Fatalln("Error issuing update-with-start:", err)
}
var earlyReturnResult any
err = updateHandle.Get(ctxWithTimeout, &earlyReturnResult)
if err != nil {
	// The workflow will continue running, cancelling the transaction.

	// NOTE: If the error is retryable, a retry attempt must use a unique workflow ID.
	log.Fatalln("Error obtaining update result:", err)
}

workflowRun, err := startWorkflowOp.Get(ctxWithTimeout)

@dandavison dandavison force-pushed the uws branch 9 times, most recently from 7b0f0cc to c787087 Compare November 29, 2024 21:45
@dandavison dandavison marked this pull request as ready for review November 29, 2024 21:45
@dandavison dandavison requested a review from a team as a code owner November 29, 2024 21:45
client/client.go Outdated Show resolved Hide resolved
client/client.go Outdated Show resolved Hide resolved
client/client.go Outdated Show resolved Hide resolved
client/client.go Outdated Show resolved Hide resolved
// NewWithStartWorkflowOperation returns a WithStartWorkflowOperation to perform Update-with-Start.
// Returns an error if the WorkflowIDConflictPolicy is not set, or if the workflow or arguments are invalid.
// NOTE: Experimental
NewWithStartWorkflowOperation(options StartWorkflowOptions, workflow interface{}, args ...interface{}) WithStartWorkflowOperation
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
NewWithStartWorkflowOperation(options StartWorkflowOptions, workflow interface{}, args ...interface{}) WithStartWorkflowOperation
NewWithStartWorkflowOperation(options WithStartWorkflowOperationOptions) WithStartWorkflowOperation

Arguably this is more future proof, but understood if we don't want it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seems to be going too far to me: it doesn't seem right to have to create a WithStartWorkflowOperationOptions struct in order to create a WithStartWorkflowOperation given that the latter essentially is just a stateful version of the former.

Copy link
Member

Choose a reason for hiding this comment

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

(unresolved since I wanted to add a comment but it was resolved which disables the ability to discuss further)

it doesn't seem right to have to create a WithStartWorkflowOperationOptions struct in order to create a WithStartWorkflowOperation given that the latter essentially is just a stateful version of the former.

Creation methods of stateful things often accept options to create them and it helps us if later we need to add another option. I am ok if we all agree this isn't needed, but accepting an options object when creating something does save us from having a potential differently-named overload or adding mutation methods on the result later. I fear we will add some server-side option that only applies to multiop start workflow and we won't be able to represent it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

which disables the ability to discuss further

Apparently not :)

I am ok if we all agree this isn't needed

Indeed, the consensus with others was that it wasn't needed.

I fear we will add some server-side option that only applies to multiop start workflow and we won't be able to represent it here.

We would be able to add such an option to UpdateWithStartWorkflowOptions.

Copy link
Member

Choose a reason for hiding this comment

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

We would be able to add such an option to UpdateWithStartWorkflowOptions.

Until we use this WithStartWorkflowOperation for more than just update with start, which is why I figured it was named to not be related to update. But it's not a big deal.

Copy link
Contributor Author

@dandavison dandavison Dec 5, 2024

Choose a reason for hiding this comment

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

I might not be getting what you're saying but if we

add some server-side option that only applies to multiop start workflow

we would at least be able to add as a top-level field here:

type UpdateWithStartWorkflowOptions struct {
	StartWorkflowOperation WithStartWorkflowOperation
	UpdateOptions          UpdateWorkflowOptions
}

client/client.go Outdated Show resolved Hide resolved
@dandavison dandavison force-pushed the uws branch 5 times, most recently from 56d5d61 to 5e6ea5c Compare December 4, 2024 01:33
Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

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

Mostly little stuff

// NewWithStartWorkflowOperation returns a WithStartWorkflowOperation to perform Update-with-Start.
// Returns an error if the WorkflowIDConflictPolicy is not set, or if the workflow or arguments are invalid.
// NOTE: Experimental
NewWithStartWorkflowOperation(options StartWorkflowOptions, workflow interface{}, args ...interface{}) WithStartWorkflowOperation
Copy link
Member

Choose a reason for hiding this comment

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

(unresolved since I wanted to add a comment but it was resolved which disables the ability to discuss further)

it doesn't seem right to have to create a WithStartWorkflowOperationOptions struct in order to create a WithStartWorkflowOperation given that the latter essentially is just a stateful version of the former.

Creation methods of stateful things often accept options to create them and it helps us if later we need to add another option. I am ok if we all agree this isn't needed, but accepting an options object when creating something does save us from having a potential differently-named overload or adding mutation methods on the result later. I fear we will add some server-side option that only applies to multiop start workflow and we won't be able to represent it here.

client/client.go Show resolved Hide resolved
internal/client.go Outdated Show resolved Hide resolved
Operation: "UpdateWithStartWorkflow",
Name: in.UpdateName,
Tags: map[string]string{workflowIDTagKey: in.WorkflowID, updateIDTagKey: in.UpdateID},
ToHeader: true,
Copy link
Member

Choose a reason for hiding this comment

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

Just out of curiosity, which header does this span get put on? Is it both the start request and the update request or just one?

internal/interceptor.go Outdated Show resolved Hide resolved
internal/internal_workflow_client.go Outdated Show resolved Hide resolved
internal/internal_workflow_client.go Outdated Show resolved Hide resolved
internal/internal_workflow_client.go Outdated Show resolved Hide resolved
@@ -416,6 +421,11 @@ type ClientUpdateWorkflowInput struct {
WaitForStage WorkflowUpdateStage
}

type ClientUpdateWithStartWorkflowInput struct {
UpdateInput *ClientUpdateWorkflowInput
StartWorkflowOperation WithStartWorkflowOperation
Copy link
Member

Choose a reason for hiding this comment

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

May be worth noting that the interceptor can't do much with this. They can't even know what workflow it applies to. We don't have to do anything now, but I would expect at some point for interceptor authors to ask how to get details of the start the same way they can get details of the update.

@@ -416,6 +421,11 @@ type ClientUpdateWorkflowInput struct {
WaitForStage WorkflowUpdateStage
}

type ClientUpdateWithStartWorkflowInput struct {
UpdateInput *ClientUpdateWorkflowInput
Copy link
Member

@cretz cretz Dec 5, 2024

Choose a reason for hiding this comment

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

This is input for another interceptor call, I don't think we should reuse it for this one. The interceptor input really just needs to be the values passed in by the user. Maybe something like:

type ClientUpdateWithStartWorkflowInput struct {
    StartOptions  *StartWorkflowOptions
    StartWorkflowType string
    StartWorkflowArgs []any
    UpdateID string
    UpdateName string
    UpdateArgs []any
    UpdateWaitForStage WorkflowUpdateStage
}

Or something like:

type ClientUpdateWithStartWorkflowInput struct {
    StartOptions  *StartWorkflowOptions
    StartWorkflowType string
    StartWorkflowArgs []any
    UpdateOptions *UpdateWorkflowOptions
}

Or something like:

type ClientUpdateWithStartWorkflowInput struct {
    UpdateOptions *UpdateWorkflowOptions
    StartOperation WithStartWorkflowOperation
}

and add Options() StartWorkflowOptions and WorkflowType() string and WorkflowArgs() []any to WithStartWorkflowOperation interface.

(this is my only remaining discussion before marking approved, everything else LGTM or is non-blocking)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What problem are you trying to solve? This is how it's done in Java:

    public WorkflowUpdateWithStartInput(
        WorkflowStartInput workflowStartInput, StartUpdateInput<R> workflowUpdateInput) {

Copy link
Member

@cretz cretz Dec 5, 2024

Choose a reason for hiding this comment

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

Each input type is only for the outer call being made, it is not reused in places where it may contain non-applicable fields (e.g. ClientUpdateWorkflowInput.RunID which makes no sense here). The whole point is so that we can grow the interceptor as needed for just this call. This is different than our stance on reusing option types for callers.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah right I see, thank you @Quinn-With-Two-Ns and @cretz for the suggested schemas. I've switched to

type ClientUpdateWithStartWorkflowInput struct {
	UpdateOptions          *UpdateWorkflowOptions
	StartWorkflowOperation WithStartWorkflowOperation
}

9562e0e

// NewWithStartWorkflowOperation returns a WithStartWorkflowOperation to perform Update-with-Start.
// Returns an error if the WorkflowIDConflictPolicy is not set, or if the workflow or arguments are invalid.
// NOTE: Experimental
NewWithStartWorkflowOperation(options StartWorkflowOptions, workflow interface{}, args ...interface{}) WithStartWorkflowOperation
Copy link
Member

Choose a reason for hiding this comment

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

We would be able to add such an option to UpdateWithStartWorkflowOptions.

Until we use this WithStartWorkflowOperation for more than just update with start, which is why I figured it was named to not be related to update. But it's not a big deal.

@dandavison dandavison enabled auto-merge (squash) December 6, 2024 00:16
@dandavison dandavison disabled auto-merge December 6, 2024 00:40
@dandavison dandavison merged commit 9d59447 into master Dec 6, 2024
14 checks passed
@dandavison dandavison deleted the uws branch December 6, 2024 01:29
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.

3 participants