chore: reorganize docs apps (#7228)
* reorganize docs apps * add README * fix directory * add condition for old docs
This commit is contained in:
1 parent
224ebb2154
commit
4fe28f5a95
6187 files changed
+601447
-598226
No files matched your search
@@ -0,0 +1,253 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Advanced Workflow Example`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll create an advanced workflow.
|
||||
|
||||
Workflows are most useful when performing tasks across services and integrations:
|
||||
|
||||
- You simplify complex flows or processes by splitting them into a series of steps.
|
||||
- You avoid data inconsistency and loss through retry mechanisms and rollbacks.
|
||||
- You execute the workflow from anywhere in your Medusa application.
|
||||
|
||||
The workflow you’ll build is an update-product workflow. You'll use an example `ErpService` that has methods to manage the product in a third-party ERP system, and the `ProductService`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Create Workflow File
|
||||
|
||||
Start by creating the file `src/workflows/update-product-erp/index.ts` that will hold the constructed workflow.
|
||||
|
||||
In the file, add the type of the expected workflow input:
|
||||
|
||||
```ts title="src/workflows/update-product-erp/index.ts"
|
||||
import { UpdateProductDTO } from "@medusajs/types"
|
||||
|
||||
export type UpdateProductAndErpWorkflowInput = UpdateProductDTO
|
||||
|
||||
```
|
||||
|
||||
The expected input is the data to update in the product along with the product’s ID.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create Update Product Step
|
||||
|
||||
The first step in the workflow receives the product’s ID and the data to update, then updates the product.
|
||||
|
||||
Create the file `src/workflows/update-product-erp/steps/update-product.ts` with the following content:
|
||||
|
||||
export const updateProductHighlights = [
|
||||
["13", "resolve", "Resolve the `ProductService` from the Medusa container."],
|
||||
["16", "previousProductData", "Retrieve the `previousProductData` to pass it to the compensation function."],
|
||||
["19", "", "Update the product."],
|
||||
["33", "", "Revert the product’s data using the `previousProductData` passed from the step to the compensation function."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/update-product-erp/steps/update-product.ts" highlights={updateProductHighlights}
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { UpdateProductAndErpWorkflowInput } from ".."
|
||||
|
||||
const updateProduct = createStep(
|
||||
"update-product",
|
||||
async (input: UpdateProductAndErpWorkflowInput, context) => {
|
||||
const productModuleService: IProductModuleService =
|
||||
context.container.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const { id } = input
|
||||
const previousProductData =
|
||||
await productModuleService.retrieve(id)
|
||||
|
||||
const product = await productModuleService.update([input])
|
||||
|
||||
return new StepResponse(product, {
|
||||
// pass to compensation function
|
||||
previousProductData,
|
||||
})
|
||||
},
|
||||
// compensation function
|
||||
async ({ previousProductData }, context) => {
|
||||
const productModuleService: IProductModuleService =
|
||||
context.container.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const { type, ...previousData } = previousProductData
|
||||
|
||||
await productModuleService.update([
|
||||
{
|
||||
...previousData,
|
||||
},
|
||||
])
|
||||
|
||||
await productModuleService.updateTypes(type)
|
||||
}
|
||||
)
|
||||
|
||||
export default updateProduct
|
||||
```
|
||||
|
||||
In the step:
|
||||
|
||||
- You resolve the `ProductService` from the Medusa container.
|
||||
- You retrieve the `previousProductData` to pass it to the compensation function.
|
||||
- You update and return the product.
|
||||
|
||||
You also pass a compensation function as a second parameter to `createStep`. The compensation function runs if an error occurs during the workflow execution.
|
||||
|
||||
In the compensation function, you revert the product’s data using the `previousProductData` passed from the step to the compensation function.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
The compensation functions of each step ensure data consistency across Medusa and external services and systems when errors occur.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## 3. Create Step 2: Update ERP
|
||||
|
||||
The second step in the workflow receives the same input. It updates the product’s details in the ERP system using the `ErpService`.
|
||||
|
||||
Create the file `src/workflows/update-product-erp/steps/update-erp.ts` with the following content:
|
||||
|
||||
export const updateErpHighlights = [
|
||||
["12", "resolve", "Resolve the `ErpService` from the Medusa container."],
|
||||
["17", "previousErpData", "Retrieve the `previousErpData` to pass it to the compensation function."],
|
||||
["21", "updateProductErpData", "Update the product’s ERP data and return the data from the ERP system."],
|
||||
["34", "updateProductErpData", "Revert the product's data in the ERP system to its previous state using the `previousErpData`."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/update-product-erp/steps/update-erp.ts" highlights={updateErpHighlights}
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { UpdateProductAndErpWorkflowInput } from ".."
|
||||
import ErpService from "../../../services/erp"
|
||||
|
||||
const updateErp = createStep(
|
||||
"update-erp",
|
||||
async (input: UpdateProductAndErpWorkflowInput, context) => {
|
||||
const erpService: ErpService =
|
||||
context.container.resolve("erpService")
|
||||
|
||||
const { id, ...updatedData } = input
|
||||
|
||||
// get previous ERP data
|
||||
const previousErpData =
|
||||
await erpService.retrieveProductErpDetails(id)
|
||||
|
||||
const updatedErpData =
|
||||
await erpService.updateProductErpData(id, updatedData)
|
||||
|
||||
return new StepResponse(updatedErpData, {
|
||||
// pass to compensation function
|
||||
previousErpData,
|
||||
productId: id,
|
||||
})
|
||||
},
|
||||
// compensation function
|
||||
async ({ previousErpData, productId }, context) => {
|
||||
const erpService: ErpService =
|
||||
context.container.resolve("erpService")
|
||||
|
||||
await erpService.updateProductErpData(
|
||||
productId,
|
||||
previousErpData
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default updateErp
|
||||
```
|
||||
|
||||
In the step:
|
||||
|
||||
- You resolve the `ErpService` from the Medusa container.
|
||||
- You retrieve the `previousErpData` to pass it to the compensation function.
|
||||
- You update the product’s ERP data and return the data from the ERP system.
|
||||
|
||||
You also pass a compensation function as a second parameter to `createStep`. In the compensation function, you revert the product's data in the ERP system to its previous state.
|
||||
|
||||
---
|
||||
|
||||
## 4. Create Workflow
|
||||
|
||||
With the steps ready, you'll create the workflow that runs these steps to update the product’s data both in Medusa and the external ERP system.
|
||||
|
||||
Change the content of `src/workflows/update-product-erp/index.ts` to the following:
|
||||
|
||||
```ts title="src/workflows/update-product-erp/index.ts"
|
||||
import { createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { UpdateProductDTO, ProductDTO } from "@medusajs/types"
|
||||
import updateProduct from "./steps/update-product"
|
||||
import updateErp from "./steps/update-erp"
|
||||
|
||||
export type UpdateProductAndErpWorkflowInput = UpdateProductDTO
|
||||
|
||||
type WorkflowOutput = {
|
||||
product: ProductDTO
|
||||
erpData: Record<string, unknown>
|
||||
}
|
||||
|
||||
const updateProductAndErpWorkflow = createWorkflow<
|
||||
UpdateProductAndErpWorkflowInput,
|
||||
WorkflowOutput
|
||||
>("update-product-and-erp", function (input) {
|
||||
const product = updateProduct(input)
|
||||
const erpData = updateErp(input)
|
||||
|
||||
return {
|
||||
product,
|
||||
erpData,
|
||||
}
|
||||
})
|
||||
|
||||
export default updateProductAndErpWorkflow
|
||||
```
|
||||
|
||||
In the workflow construction function, you first run the `updateProduct` step, then the `updateErp` step. You return as the workflow’s result an object holding the updated product and ERP data.
|
||||
|
||||
---
|
||||
|
||||
## 5. Execute Workflow
|
||||
|
||||
You can now use and execute your workflow in your Medusa application.
|
||||
|
||||
To execute the workflow in an API route, create the file `src/api/store/products/[id]/erp/route.ts` with the following content:
|
||||
|
||||
```ts title="src/api/store/products/[id]/erp/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import updateProductAndErpWorkflow, {
|
||||
UpdateProductAndErpWorkflowInput,
|
||||
} from "../../../../../workflows/update-product-erp"
|
||||
|
||||
export const POST = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
// skipping validation for simplicity
|
||||
const productData: UpdateProductAndErpWorkflowInput = {
|
||||
id: req.params.id,
|
||||
...req.body,
|
||||
}
|
||||
|
||||
const { result } = await updateProductAndErpWorkflow(
|
||||
req.scope
|
||||
).run({
|
||||
input: productData,
|
||||
})
|
||||
|
||||
res.json(result)
|
||||
}
|
||||
```
|
||||
|
||||
In this `POST` API route, you retrieve the product’s ID from the path parameter and the data to update from the request body. You then execute the workflow by passing it the retrieved data as an input.
|
||||
|
||||
The route returns the result of the workflow, which is an object holding both the update product’s details and the ERP details.
|
||||
@@ -0,0 +1,111 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Compensation Function`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
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.
|
||||
|
||||
The compensation function only runs if an error occurs throughout the Workflow. It’s useful to undo or roll back actions you’ve 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"]]}
|
||||
const step1 = createStep(
|
||||
"step-1",
|
||||
async () => {
|
||||
const message = `Hello from step one!`
|
||||
|
||||
console.log(message)
|
||||
|
||||
return new StepResponse(message)
|
||||
},
|
||||
async () => {
|
||||
console.log("Oops! Rolling back my changes...")
|
||||
}
|
||||
)
|
||||
|
||||
const step2 = createStep(
|
||||
"step-2",
|
||||
async ({ name }: WorkflowInput) => {
|
||||
throw new Error("Throwing an error...")
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Test Compensation Function
|
||||
|
||||
1. Use the steps in a workflow. For example:
|
||||
|
||||
```ts title="src/workflows/hello-world.ts"
|
||||
import {
|
||||
// other imports...
|
||||
createWorkflow,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
// ...
|
||||
|
||||
type WorkflowOutput = {
|
||||
message: string
|
||||
}
|
||||
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
WorkflowOutput
|
||||
>("hello-world", function (input) {
|
||||
const str1 = step1()
|
||||
// to pass input
|
||||
const str2 = step2(input)
|
||||
|
||||
return {
|
||||
message: str1,
|
||||
}
|
||||
})
|
||||
|
||||
export default myWorkflow
|
||||
```
|
||||
|
||||
2. Execute the workflow from a resource, such as an API route:
|
||||
|
||||
```ts title="src/api/store/workflow/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import myWorkflow from "../../../workflows/hello-world"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const { result } = await myWorkflow(req.scope)
|
||||
.run({
|
||||
input: {
|
||||
name: req.query.name as string,
|
||||
},
|
||||
})
|
||||
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
3. Run the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
4. Send a `GET` request to `/store/workflow`:
|
||||
|
||||
```bash apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/workflows"
|
||||
curl http://localhost:9000/store/workflow
|
||||
```
|
||||
|
||||
In the console, you'll see:
|
||||
|
||||
- `Hello from step one!` logged in the terminal, indicating that the first step ran successfully.
|
||||
- `Oops! Rolling back my changes...` logged in the terminal, indicating that the second step failed and the compensation function of the first step ran consequently.
|
||||
@@ -0,0 +1,96 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Constraints on Workflow Constructor Function`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
This chapter lists some constraints to keep in mind when defining Workflow constructor functions.
|
||||
|
||||
## No Arrow Functions
|
||||
|
||||
The function passed to the `createWorkflow` function can’t be an arrow function:
|
||||
|
||||
```ts highlights={[["5", "=>", "Function can't be an arrow function"], ["13", "", "Correct way of defining the function."]]}
|
||||
// Don't
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
WorkflowOutput
|
||||
>("hello-world", (input) => {
|
||||
// ...
|
||||
})
|
||||
|
||||
// Do
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
WorkflowOutput
|
||||
>("hello-world", function (input) {
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
## No Async Functions
|
||||
|
||||
The function passed to the `createWorkflow` can’t be an async function:
|
||||
|
||||
```ts highlights={[["5", "async", "Function can't be async."], ["13", "", "Correct way of defining the function."]]}
|
||||
// Don't
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
WorkflowOutput
|
||||
>("hello-world", async function (input) {
|
||||
// ...
|
||||
})
|
||||
|
||||
// Do
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
WorkflowOutput
|
||||
>("hello-world", function (input) {
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
## No Direct Data Manipulation
|
||||
|
||||
Since the constructor function only defines how the workflow works, you can’t directly manipulate data within the function. Instead, use the `transform` function:
|
||||
|
||||
export const highlights = [
|
||||
["10", "", "Don't manipulate data directly."],
|
||||
["22", "transform", "Use the `transform` function to manipulate data."]
|
||||
]
|
||||
|
||||
```ts highlights={highlights}
|
||||
// Don't
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
WorkflowOutput
|
||||
>("hello-world", function (input) {
|
||||
const str1 = step1(input)
|
||||
const str2 = step2(input)
|
||||
|
||||
return {
|
||||
message: `${input.str1}${input.str2}`,
|
||||
}
|
||||
})
|
||||
|
||||
// Do
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
WorkflowOutput
|
||||
>("hello-world", function (input) {
|
||||
const str1 = step1(input)
|
||||
const str2 = step2(input)
|
||||
|
||||
const result = transform(
|
||||
{
|
||||
str1,
|
||||
str2,
|
||||
},
|
||||
(input) => ({
|
||||
message: `${input.str1}${input.str2}`,
|
||||
})
|
||||
)
|
||||
|
||||
return result
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Running Workflow Steps in Parallel`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to run workflow steps in parallel.
|
||||
|
||||
## parallelize Utility Function
|
||||
|
||||
If your workflow has steps that don’t rely on one another’s results, you can run them in parallel. The workflow will wait until all specified steps are finished before continuing with the rest of its implementation.
|
||||
|
||||
The `parallelize` utility function imported from the `@medusajs/workflows-sdk` package allows you to run the steps in parallel.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["23", "[prices, productSalesChannel]", "The result of the steps. `prices` is the result of `createPricesStep`, and `productSalesChannel` is the result of `attachProductToSalesChannelStep`."],
|
||||
["23", "parallelize", "Run the steps passed as parameters in parallel."],
|
||||
]
|
||||
|
||||
```ts highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
parallelize,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { ProductDTO } from "@medusajs/types"
|
||||
import {
|
||||
createProductStep,
|
||||
getProductStep,
|
||||
createPricesStep,
|
||||
attachProductToSalesChannelStep,
|
||||
} from "./steps"
|
||||
|
||||
interface WorkflowInput {
|
||||
title: string
|
||||
}
|
||||
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
ProductDTO
|
||||
>("my-workflow", (input) => {
|
||||
const product = createProductStep(input)
|
||||
|
||||
const [prices, productSalesChannel] = parallelize(
|
||||
createPricesStep(product),
|
||||
attachProductToSalesChannelStep(product)
|
||||
)
|
||||
|
||||
const id = product.id
|
||||
return getProductStep(product.id)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The `parallelize` function accepts the steps to run in parallel as a parameter.
|
||||
|
||||
It returns an array of each step’s result. The results are ordered in the result array by the order they're passed in the function's parameter.
|
||||
Reference in new issue
Block a user