4.8.11. Long-Running Workflows

In this chapter, you’ll learn what a long-running workflow is and how to configure it.

What is a Long-Running Workflow?#

When you execute a workflow, you wait until the workflow finishes execution to receive the output.

A long-running workflow is a workflow that continues its execution in the background. You don’t receive its output immediately. Instead, you subscribe to the workflow execution to listen to status changes and receive its result once the execution is finished.

Why use Long-Running Workflows?#

Long-running workflows are useful if:

  • A task takes too long. For example, you're importing data from a CSV file.
  • The workflow's steps wait for an external action to finish before resuming execution. For example, before you import the data from the CSV file, you wait until the import is confirmed by the user.

Configure Long-Running Workflows#

A workflow is considered long-running if at least one step has its async configuration set to true and doesn't return a step response.

For example, consider the following workflow and steps:

src/workflows/hello-world.ts
10})11
12const step2 = createStep(13  {14    name: "step-2",15    async: true,16  },17  async () => {18    console.log("Waiting to be successful...")19  }20)21
22const step3 = createStep("step-3", async () => {23  return new StepResponse("Finished three steps")24})25
26const myWorkflow = createWorkflow(27  "hello-world", 28  function () {29  step1()30  step2()31  const message = step3()32
33  return new WorkflowResponse({34    message,35  })36})37
38export default myWorkflow

The second step has in its configuration object async set to true and it doesn't return a step response. This indicates that this step is an asynchronous step.

So, when you execute the hello-world workflow, it continues its execution in the background once it reaches the second step.

NoteA workflow is also considered long-running if one of its steps has their retryInterval option set as explained in this chapter .

Change Step Status#

Once the workflow's execution reaches an async step, it'll wait in the background for the step to succeed or fail before it moves to the next step.

To fail or succeed a step, use the Workflow Engine Module's main service that is registered in the Medusa Container under the Modules.WORKFLOW_ENGINE (or workflowsModuleService) key.

Retrieve Transaction ID#

Before changing the status of a workflow execution's async step, you must have the execution's transaction ID.

When you execute the workflow, the object returned has a transaction property, which is an object that holds the details of the workflow execution's transaction. Use its transactionId to later change async steps' statuses:

Code
1const { transaction } = await myWorkflow(req.scope)2  .run()3
4// use transaction.transactionId later

Change Step Status to Successful#

The Workflow Engine Module's main service has a setStepSuccess method to set a step's status to successful. If you use it on a workflow execution's async step, the workflow continues execution to the next step.

For example, consider the following step:

Code
8} from "@medusajs/framework/workflows-sdk"9
10type SetStepSuccessStepInput = {11  transactionId: string12};13
14export const setStepSuccessStep = createStep(15  "set-step-success-step",16  async function (17    { transactionId }: SetStepSuccessStepInput,18    { container }19  ) {20    const workflowEngineService = container.resolve(21      Modules.WORKFLOW_ENGINE22    )23
24    await workflowEngineService.setStepSuccess({25      idempotencyKey: {26        action: TransactionHandlerType.INVOKE,27        transactionId,28        stepId: "step-2",29        workflowId: "hello-world",30      },31      stepResponse: new StepResponse("Done!"),32      options: {33        container,34      },35    })36  }37)

In this step (which you use in a workflow other than the long-running workflow), you resolve the Workflow Engine Module's main service and set step-2 of the previous workflow as successful.

The setStepSuccess method of the workflow engine's main service accepts as a parameter an object having the following properties:

idempotencyKeyobject
The details of the workflow execution.
stepResponseStepResponse
Set the response of the step. This is similar to the response you return in a step's definition, but since the async step doesn't have a response, you set its response when changing its status.
optionsRecord<string, any>Optional
Options to pass to the step.

Change Step Status to Failed#

The Workflow Engine Module's main service also has a setStepFailure method that changes a step's status to failed. It accepts the same parameter as setStepSuccess.

After changing the async step's status to failed, the workflow execution fails and the compensation functions of previous steps are executed.

For example:

Code
8} from "@medusajs/framework/workflows-sdk"9
10type SetStepFailureStepInput = {11  transactionId: string12};13
14export const setStepFailureStep = createStep(15  "set-step-success-step",16  async function (17    { transactionId }: SetStepFailureStepInput,18    { container }19  ) {20    const workflowEngineService = container.resolve(21      Modules.WORKFLOW_ENGINE22    )23
24    await workflowEngineService.setStepFailure({25      idempotencyKey: {26        action: TransactionHandlerType.INVOKE,27        transactionId,28        stepId: "step-2",29        workflowId: "hello-world",30      },31      stepResponse: new StepResponse("Failed!"),32      options: {33        container,34      },35    })36  }37)

You use this step in another workflow that changes the status of an async step in a long-running workflow's execution to failed.


Access Long-Running Workflow Status and Result#

To access the status and result of a long-running workflow execution, use the subscribe and unsubscribe methods of the Workflow Engine Module's main service.

For example:

src/api/workflows/route.ts
10
11  const workflowEngineService = req.scope.resolve<12    IWorkflowEngineService13  >(14    Modules.WORKFLOW_ENGINE15  )16
17  const subscriptionOptions = {18    workflowId: "hello-world",19    transactionId: transaction.transactionId,20    subscriberId: "hello-world-subscriber",21  }22
23  await workflowEngineService.subscribe({24    ...subscriptionOptions,25    subscriber: async (data) => {26      if (data.eventType === "onFinish") {27        console.log("Finished execution", data.result)28        // unsubscribe29        await workflowEngineService.unsubscribe({30          ...subscriptionOptions,31          subscriberOrId: subscriptionOptions.subscriberId,32        })33      } else if (data.eventType === "onStepFailure") {34        console.log("Workflow failed", data.step)35      }36    },37  })38
39  res.send(result)40}

In the above example, you execute the long-running workflow hello-world and resolve the Workflow Engine Module's main service from the Medusa container.

subscribe Method#

The main service's subscribe method allows you to listen to changes in the workflow execution’s status. It accepts an object having three properties:

workflowIdstring
The name of the workflow.
transactionIdstring
The ID of the workflow exection's transaction. The transaction's details are returned in the response of the workflow execution.
subscriberIdstring
The ID of the subscriber.
subscriber(data: { eventType: string, result?: any }) => Promise<void>
The function executed when the workflow execution's status changes. The function receives a data object. It has an eventType property, which you use to check the status of the workflow execution.

If the value of eventType in the subscriber function's first parameter is onFinish, the workflow finished executing. The first parameter then also has a result property holding the workflow's output.

unsubscribe Method#

You can unsubscribe from the workflow using the workflow engine's unsubscribe method, which requires the same object parameter as the subscribe method.

However, instead of the subscriber property, it requires a subscriberOrId property whose value is the same subscriberId passed to the subscribe method.


Example: Restaurant-Delivery Recipe#

To find a full example of a long-running workflow, refer to the restaurant-delivery recipe.

In the recipe, you use a long-running workflow that moves an order from placed to completed. The workflow waits for the restaurant to accept the order, the driver to pick up the order, and other external actions.

Was this chapter helpful?
Edit this page