Workflow versioning

Hey all,

I am seeking some help on how to go about versioning a workflow that is running. I have read the docs and they mostly point to information about wrapping activities in the getVersion flow. But my question is more about an actual change in the workflow.

ie we have a workflow that creates a record in a database, then it sits in a loop waiting on signals and performs status changes to the record and if a specific status happens we end the workflow and its completed.

We was currently only checking one status but some changes happened and we need to change this to two status’. But as we already have these workflow’s running we want to check the status and end the workflow that is already being executed, so changing the flow of the workflow.

My question is based on this very lose and rough example when a replay happens, would the proxy loop run through with any previous requests?

func (w *WorkflowParams) Workflow(ctx workflow.Context, args *WorkflowInput) (string, error) {
	logger := workflow.GetLogger(ctx)
	var item model.Item

	workflow.ExecuteActivity(ctx, createRecord, args).Get(ctx, &item)
	
	workflow.ExecuteActivity(ctx, addData, item).Get(ctx, &item)

	for {
		callingWorkflowId, statusRequest := proxy.ReceiveRequest(ctx)

		workflow.ExecuteActivity(ctx, updateStatus, item, statusRequest).Get(ctx, &item)

		if item.Status == "completed" {
			break
		}
	}
	return item
}

If we added the extra check for the status change, would this run the existing workflows and end them?

func (w *WorkflowParams) Workflow(ctx workflow.Context, args *WorkflowInput) (string, error) {
	logger := workflow.GetLogger(ctx)
	var item model.Item

	workflow.ExecuteActivity(ctx, createRecord, args).Get(ctx, &item)
	
	workflow.ExecuteActivity(ctx, addData, item).Get(ctx, &item)

	for {
		callingWorkflowId, statusRequest := proxy.ReceiveRequest(ctx)

		workflow.ExecuteActivity(ctx, updateStatus, item, statusRequest).Get(ctx, &item)

		if item.Status == "completed" || item.Status == "failed" {
			break
		}
	}
	return item
}

If we added an “if block” above the for loop to check the status, we would only get the status at that current point in time, correct?

How could we get fork the flow moving forwards having previously running and new workflows use this new check to end?

Yes, versioning loops is a tricky situation. You either version the whole loop, but then the already running workflows will execute the whole loop using the old version of the code. Or you version each loop iteration, this way you have to use a different “changeID” for every iteration. Something like:

func (w *WorkflowParams) Workflow(ctx workflow.Context, args *WorkflowInput) (string, error) {
	logger := workflow.GetLogger(ctx)
	var item model.Item

	workflow.ExecuteActivity(ctx, createRecord, args).Get(ctx, &item)

	workflow.ExecuteActivity(ctx, addData, item).Get(ctx, &item)

	for i := 0; ; i++ {
		callingWorkflowId, statusRequest := proxy.ReceiveRequest(ctx)

		workflow.ExecuteActivity(ctx, updateStatus, item, statusRequest).Get(ctx, &item)
		iterationChangeID := fmt.Sprintf("fooChange-fix2-%d", i)
		v := workflow.GetVersion(ctx, iterationChangeID, workflow.DefaultVersion, 1)
		if v == workflow.DefaultVersion {
			if item.Status == "completed" {
				break
			}
		} else {
			if item.Status == "completed" || item.Status == "failed" {
				break
			}
		}
	}
	return item
}

Hey Maxim! Thanks for the prompt reply!

Hmm I see, my question would be that the existing running versions would still only run the status == "complete" check and as such would never get to the end. They have already ran to an end state as such but the check is not correct so we need to change the check for the existing ones.
We would also want any new ones to execute this new check also.

Otherwise we are left with the workflows stuck in “pending” state rather than “complete”.

I don’t understand your follow-up comment. If you version every iteration of the loop separately then each iteration is evaluated separately. So already executed iterations would use the old code (which is required for replay to work) and the new iterations are going to execute the new code breaking out of the loop.

Sorry, let me try to explain. It might be me just not understanding how these iterations work

We have a workflow, it has run as far as the for loop, then it gets one proxy / signal request to change status and that status is “failed”.

If we change the code to what you have with the getVersion code. When this is replayed, the check that will happen will only check that item.Status == "completed" correct? which means that the loop will still be running and the workflow is still “pending”.

In order for it to execute the item.Status == "completed" || item.Status == "failed" we would need to make a proxy / signal request with a status update for it to be a new version / iteration?

I see. I’ve got confused because you mentioned versioning in the original post. Versioning is used to change workflow implementation for all the future workflows and the workflows that didn’t reach the place of the change. So it is about changing the future. It looks like you are asking to change the past. Your workflow has already processed the signal and now you want to revert that part of the workflow to execute the new code path. In this case, you want to use versioning to update the workflow and then use reset to roll back the workflow state to the past.