docs: added advanced workflows documentation (#7252)

This commit is contained in:
Shahed Nasser
2024-05-07 10:32:36 +03:00
committed by GitHub
parent 39c3f6d92a
commit 14d15df866
14 changed files with 678 additions and 22 deletions

View File

@@ -0,0 +1,52 @@
export const metadata = {
title: `${pageNumber} Access Workflow Errors`,
}
# {metadata.title}
In this document, youll learn how to access errors that occur during a workflows execution.
## How to Access Workflow Errors
By default, when an error occurs in a workflow, it throws that error, and the execution stops.
You can configure the workflow to return the errors instead so that you can access and handle them differently.
For example:
export const highlights = [
["11", "errors", "`errors` is an array of errors that occur during the workflow's execution."],
["14", "throwOnError", "Specify that errors occuring during the workflow's execution should be returned, not thrown."],
]
```ts title="src/api/store/workflows/route.ts" highlights={highlights}
import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/medusa"
import myWorkflow from "../../../workflows/hello-world"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
const { result, errors } = await myWorkflow(req.scope)
.run({
// ...
throwOnError: false,
})
if (errors.length) {
return res.send({
errors: errors.map((error) => error.error),
})
}
res.send(result)
}
```
The object passed to the `run` method accepts a `throwOnError` property. When disabled, the errors are returned in the `errors` property of the `run`'s output. The value of `errors` is an array of error objects.
Then, you can check the items in the `errors` array and handle them accordingly. Each error object has an `error` property, which holds the name or the text of the thrown error.

View File

@@ -8,13 +8,19 @@ In this chapter, you'll learn how to add a compensation function to a step.
## Compensation Function
Errors can occur in a workflow. To avoid data inconsistency, you can pass a compensation function as a third parameter to the `createStep` function.
Errors can occur in a workflow. To avoid data inconsistency, define a function to run when an error occurs in a step. This function is called the compensation function.
The compensation function only runs if an error occurs throughout the Workflow. Its useful to undo or roll back actions youve performed in a step.
Each step can have a compensation function. The compensation function only runs if an error occurs throughout the Workflow. Its useful to undo or roll back actions youve performed in a step.
For example, change step one to add a compensation function and step two to throw an error:
```ts title="src/workflows/hello-world.ts" highlights={[["10"], ["11"], ["12"]]}
```ts title="src/workflows/hello-world.ts" highlights={[["16"], ["17"], ["18"]]}
// other imports...
import {
createStep,
StepResponse,
} from "@medusajs/workflows-sdk"
const step1 = createStep(
"step-1",
async () => {
@@ -31,7 +37,7 @@ const step1 = createStep(
const step2 = createStep(
"step-2",
async ({ name }: WorkflowInput) => {
async () => {
throw new Error("Throwing an error...")
}
)
@@ -54,12 +60,11 @@ type WorkflowOutput = {
}
const myWorkflow = createWorkflow<
WorkflowInput,
{},
WorkflowOutput
>("hello-world", function (input) {
const str1 = step1()
// to pass input
const str2 = step2(input)
step2()
return {
message: str1,
@@ -83,11 +88,7 @@ export async function GET(
res: MedusaResponse
) {
const { result } = await myWorkflow(req.scope)
.run({
input: {
name: req.query.name as string,
},
})
.run()
res.send(result)
}

View File

@@ -0,0 +1,156 @@
import { TypeList } from "docs-ui"
export const metadata = {
title: `${pageNumber} Long-Running Workflows`,
}
# {metadata.title}
In this chapter, youll learn what a long-running workflow is and how to configure it.
## What is a Long-Running Workflow?
By default, when you execute a workflow, you wait until the workflow finishes execution. Once you receive the workflows output, the rest of the code is executed.
A long-running workflow is a workflow that continues its execution in the background. You dont 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.
---
## Configure Long-Running Workflows
A workflow is considered long-running if one or more of its steps have their `async` configuration set to `true`.
For example, consider the following workflow and steps:
```ts title="src/workflows/hello-world.ts" highlights={[["13"]]}
import {
createStep,
createWorkflow,
} from "@medusajs/workflows-sdk"
const step1 = createStep("step-1", async () => {
// ...
})
const step2 = createStep(
{
name: "step-2",
async: true,
},
async () => {
// ...
}
)
const step3 = createStep("step-3", async () => {
// ...
})
type WorkflowOutput = {
message: string
}
const myWorkflow = createWorkflow<
{},
WorkflowOutput
>({
name: "hello-world",
}, function () {
step1()
step2()
step3()
})
export default myWorkflow
```
The second step has in its configuration object `async` set to true. 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.
---
## Access Long-Running Workflow Status and Result
<Note type="check">
- [A workflow engine module installed](!resources!/architectural-modules/workflow-engine/in-memory).
</Note>
To access the status and result of a long-running workflow, use the workflow engine registered in the Medusa Container. The workflow engine provides methods to access and subscribe to workflow executions.
For example:
export const highlights = [
["18", "", "Resolve the workflow engine from the Medusa container."],
["24", "subscribe", "Subscribe to status changes of the workflow execution."],
]
```ts title="src/api/store/workflows/route.ts" highlights={highlights}
import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/medusa"
import myWorkflow from "../../../workflows/hello-world"
import {
IWorkflowEngineService,
} from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
const { transaction, result } = await myWorkflow(req.scope)
.run()
const workflowEngine = req.scope.resolve<
IWorkflowEngineService
>(
ModuleRegistrationName.WORKFLOW_ENGINE
)
await workflowEngine.subscribe({
workflowId: "hello-world",
transactionId: transaction.transactionId,
subscriber: (data) => {
if (data.eventType === "onFinish") {
console.log("Finished execution", data.result)
} else if (data.eventType === "onStepFailure") {
console.log("Workflow failed", data.step)
}
},
})
res.send(result)
}
```
In the above example, you execute the long-running workflow `hello-world`. You then resolve the workflow engine from the Medusa container and use its `subscribe` method to listen to changes in the workflow executions status.
The `subscribe` method accepts an object having three properties:
<TypeList
types={[
{
name: "workflowId",
type: "`string`",
description: "The name of the workflow."
},
{
name: "transactionId",
type: "`string`",
description: "The ID of the workflow exection's transaction. The transaction's details are returned in the response of the workflow execution."
},
{
name: "subscriber",
type: "`string`",
description: "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."
}
]}
sectionTitle="Access Long-Running Workflow Status and Result"
/>
Once the workflow execution finishes, the subscriber function is executed with the `eventType` of the received parameter set to `onFinish`. The workflows output is set in the `result` property of the parameter.

View File

@@ -1,5 +1,5 @@
export const metadata = {
title: `${pageNumber} Running Workflow Steps in Parallel`,
title: `${pageNumber} Run Workflow Steps in Parallel`,
}
# {metadata.title}

View File

@@ -0,0 +1,87 @@
export const metadata = {
title: `${pageNumber} Retry Failed Steps`,
}
# {metadata.title}
In this chapter, youll learn how to configure steps to allow retrial on failure.
## Configure a Steps Retrial
By default, when an error occurs in a step, the step and the workflow fail, and the execution stops.
You can configure the step to retry on failure. The `createStep` function can accept a configuration object instead of the steps name as a first parameter:
```ts title="src/workflows/hello-world.ts" highlights={[["10"]]}
import {
createStep,
StepResponse,
createWorkflow,
} from "@medusajs/workflows-sdk"
const step1 = createStep(
{
name: "step-1",
maxRetries: 2,
},
async () => {
console.log("Executing step 1")
throw new Error("Oops! Something happened.")
}
)
type WorkflowOutput = {
message: string
}
const myWorkflow = createWorkflow<
{},
WorkflowOutput
>("hello-world", function () {
const str1 = step1()
return {
message: str1,
}
})
export default myWorkflow
```
The steps configuration object accepts a `maxRetries` property, which is a number indicating the number of times a step can be retried when it fails.
When you execute the above workflow, youll see the following result in the terminal:
```bash
Executing step 1
Executing step 1
Executing step 1
error: Oops! Something happened.
Error: Oops! Something happened.
```
The first line indicates the first time the step was executed, and the next two lines indicate the times the step was retried. After that, the step and workflow fail.
---
## Step Retry Intervals
By default, a step is retried immediately after it fails.
To specify a wait time before a step is retried, pass a `retryInterval` property to the step's configuration object. Its value is a number of seconds to wait before retrying the step.
For example:
```ts title="src/workflows/hello-world.ts" highlights={[["5"]]}
const step1 = createStep(
{
name: "step-1",
maxRetries: 2,
retryInterval: 2, // 2 seconds
},
async () => {
// ...
}
)
```

View File

@@ -0,0 +1,86 @@
export const metadata = {
title: `${pageNumber} Workflow Timeout`,
}
# {metadata.title}
In this chapter, youll learn how to set a timeout for workflows and steps.
## Configure Workflow Timeout
By default, a workflow doesnt have a timeout. It continues execution until its finished or an error occurs.
You can configure a workflows timeout to indicate how long the workflow can run. Once the specified time is passed and the workflow is still running, the workflow is considered failed and an error is thrown.
For example:
```ts title="src/workflows/hello-world.ts" highlights={[["22"]]}
import {
createStep,
createWorkflow,
} from "@medusajs/workflows-sdk"
const step1 = createStep(
"step-1",
async () => {
// ...
}
)
type WorkflowOutput = {
message: string
}
const myWorkflow = createWorkflow<
{},
WorkflowOutput
>({
name: "hello-world",
timeout: 2, // 2 seconds
}, function () {
const str1 = step1()
return {
message: str1,
}
})
export default myWorkflow
```
The `createWorkflow` function can accept a configuration object instead of the workflows name. In the configuration object, you pass a `timeout` property, whose value is a number indicating the timeout in seconds.
<Note title="Tip">
A workflows timeout error is returned in the `errors` property of the workflows execution, as explained in [this chapter](../access-workflow-errors/page.mdx). The errors name is `TransactionTimeoutError`.
</Note>
---
## Configure Step Timeout
Alternatively, you can configure timeout for a step rather than the entire workflow.
For example:
```tsx
const step1 = createStep(
{
name: "step-1",
timeout: 2, // 2 seconds
},
async () => {
// ...
}
)
```
The steps configuration object accepts a `timeout` property, whose value is a number indicating the timeout in seconds.
<Note title="Tip">
A steps timeout error is returned in the `errors` property of the workflows execution, as explained in [this chapter](../access-workflow-errors/page.mdx). The errors name is `TransactionStepTimeoutError`.
</Note>

View File

@@ -194,9 +194,25 @@ export const sidebar = sidebarAttachHrefCommonOptions(
path: "/advanced-development/workflows/advanced-example",
title: "Example: Advanced Workflow",
},
{
path: "/advanced-development/workflows/access-workflow-errors",
title: "Access Workflow Errors",
},
{
path: "/advanced-development/workflows/retry-failed-steps",
title: "Retry Failed Steps",
},
{
path: "/advanced-development/workflows/parallel-steps",
title: "Running Steps in Parallel",
title: "Run Steps in Parallel",
},
{
path: "/advanced-development/workflows/workflow-timeout",
title: "Workflow Timeout",
},
{
path: "/advanced-development/workflows/long-running-workflow",
title: "Long-Running Workflow",
},
],
},

View File

@@ -6,6 +6,6 @@ export const metadata = {
# {metadata.title}
This section includes documentation for official Medusa architectural plugins.
This section includes documentation for official Medusa architectural modules.
<ChildDocs />

View File

@@ -0,0 +1,39 @@
import { Table } from "docs-ui"
export const metadata = {
title: `In-Memory Workflow Engine Module`,
}
# {metadata.title}
The In-Memory Workflow Engine Module uses a plain JavaScript Map object to store the workflow executions.
This module is helpful for development or when youre testing out Medusa, but its not recommended to be used in production.
For production, its recommended to use modules like [Redis Workflow Engine Module](../redis/page.mdx).
---
## Install the In-Memory Workflow Engine Module
To install the In-Memory Workflow Engine Module, run the following command in the directory of your Medusa application:
```bash npm2yarn
npm install @medusajs/workflow-engine-inmemory
```
Next, add the module into the `modules` property of the exported object in `medusa-config.js`:
```js title="medusa-config.js"
const { Modules } = require("@medusajs/modules-sdk")
// ...
module.exports = {
// ...
modules: {
// ...
[Modules.WORKFLOW_ENGINE]: true,
},
}
```

View File

@@ -0,0 +1,182 @@
import { Table } from "docs-ui"
export const metadata = {
title: `Redis Workflow Engine Module`,
}
# {metadata.title}
The Redis Workflow Engine Module uses Redis to track workflow executions and handle their subscribers. In production, it's recommended to use this module.
---
## Install the Redis Workflow Engine Module
<Note type="check">
- [Redis installed and Redis server running](https://redis.io/docs/getting-started/installation/).
</Note>
To install Redis Workflow Engine Module, run the following command in the directory of your Medusa application:
```bash npm2yarn
npm install @medusajs/workflow-engine-redis
```
Next, add the module into the `modules` property of the exported object in `medusa-config.js`:
export const highlights = [
["8", "redisUrl", "The Redis connection URL."]
]
```js title="medusa-config.js" highlights={highlights}
const { Modules } = require("@medusajs/modules-sdk")
// ...
module.exports = {
// ...
modules: {
// ...
[Modules.WORKFLOW_ENGINE]: {
resolve: "@medusajs/workflow-engine-redis",
options: {
redis: {
url: process.emv.WE_REDIS_URL,
},
},
},
},
}
```
### Redis Workflow Engine Module Options
<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Option</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
<Table.HeaderCell>Required</Table.HeaderCell>
<Table.HeaderCell>Default</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
`url`
</Table.Cell>
<Table.Cell>
A string indicating the Redis connection URL.
</Table.Cell>
<Table.Cell>
No. If not provided, you must provide the `pubsub` option.
</Table.Cell>
<Table.Cell>
\-
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`options`
</Table.Cell>
<Table.Cell>
An object of Redis options. Refer to the [Redis API Reference](https://redis.github.io/ioredis/index.html#RedisOptions) for details on accepted properties.
</Table.Cell>
<Table.Cell>
No
</Table.Cell>
<Table.Cell>
\-
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`queueName`
</Table.Cell>
<Table.Cell>
The name of the queue used to keep track of retries and timeouts.
</Table.Cell>
<Table.Cell>
No
</Table.Cell>
<Table.Cell>
`medusa-workflows`
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`pubsub`
</Table.Cell>
<Table.Cell>
A connection object having the following properties:
- `url`: A required string indicating the Redis connection URL.
- `options`: An optional object of Redis options. Refer to the [Redis API Reference](https://redis.github.io/ioredis/index.html#RedisOptions) for details on accepted properties.
</Table.Cell>
<Table.Cell>
No. If not provided, you must provide the `url` option.
</Table.Cell>
<Table.Cell>
\-
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
### Environment Variables
Make sure to add the following environment variables:
```bash
WE_REDIS_URL=<YOUR_REDIS_URL>
```
---
## Test the Module
To test the module, start the Medusa application:
```bash npm2yarn
npm run dev
```
You'll see the following message in the terminal's logs:
```bash noCopy noReport
Connection to Redis in module 'workflow-engine-redis' established
```

View File

@@ -1,8 +1,4 @@
export const filesMap = [
{
"filePath": "/www/apps/resources/app/.DS_Store",
"pathname": "/"
},
{
"filePath": "/www/apps/resources/app/admin-widget-injection-zones/page.mdx",
"pathname": "/admin-widget-injection-zones"
@@ -28,8 +24,12 @@ export const filesMap = [
"pathname": "/architectural-modules"
},
{
"filePath": "/www/apps/resources/app/commerce-modules/.DS_Store",
"pathname": "/commerce-modules"
"filePath": "/www/apps/resources/app/architectural-modules/workflow-engine/in-memory/page.mdx",
"pathname": "/architectural-modules/workflow-engine/in-memory"
},
{
"filePath": "/www/apps/resources/app/architectural-modules/workflow-engine/redis/page.mdx",
"pathname": "/architectural-modules/workflow-engine/redis"
},
{
"filePath": "/www/apps/resources/app/commerce-modules/api-key/examples/page.mdx",

View File

@@ -6844,6 +6844,28 @@ export const generatedSidebar = [
"children": []
}
]
},
{
"loaded": true,
"isPathHref": true,
"title": "Workflow Engine Modules",
"hasTitleStyling": true,
"children": [
{
"loaded": true,
"isPathHref": true,
"path": "/architectural-modules/workflow-engine/in-memory",
"title": "In-Memory",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"path": "/architectural-modules/workflow-engine/redis",
"title": "Redis",
"children": []
}
]
}
]
},

View File

@@ -1619,6 +1619,20 @@ export const sidebar = sidebarAttachHrefCommonOptions([
},
],
},
{
title: "Workflow Engine Modules",
hasTitleStyling: true,
children: [
{
path: "/architectural-modules/workflow-engine/in-memory",
title: "In-Memory",
},
{
path: "/architectural-modules/workflow-engine/redis",
title: "Redis",
},
],
},
],
},
{

View File

@@ -110,7 +110,8 @@ module.exports = {
"@typescript-eslint/no-var-requires": "off",
"prefer-rest-params": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-non-null-asserted-optional-chain": "off"
"@typescript-eslint/no-non-null-asserted-optional-chain": "off",
"@typescript-eslint/ban-types": "off"
},
},
],