docs: add routing page (#9550)
- Add a new homepage to `book` project for the routing page - Move all main doc pages to be under `/v2/learn` (and added redirects + fixed links across docs) - Other: add admin components to resources dropdown + fixes to search on mobile. Closes DX-955 Preview: https://docs-v2-git-docs-router-page-medusajs.vercel.app/v2
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
import { Prerequisites } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Schedule Syncing Brands from Third-Party System`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<Note title="Example Chapter">
|
||||
|
||||
This chapter covers how to use workflows and scheduled jobs to sync brands from the third-party system as the last step of the ["Integrate Systems" chapter](../page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## 1. Implement Syncing Workflow
|
||||
|
||||
<Prerequisites
|
||||
items={[
|
||||
{
|
||||
text: "Brand Module",
|
||||
link: "/customization/custom-features/module"
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
Start by defining the workflow that syncs the brand from the third-party system.
|
||||
|
||||
The workflow has the following steps:
|
||||
|
||||
1. Retrieve brands from the third-party system.
|
||||
2. Create new brands in Medusa.
|
||||
3. Update existing brands in Medusa.
|
||||
|
||||
### Retrieve Brands Step
|
||||
|
||||
To create the step that retrieves the brands from the third-party service, create the file `src/workflows/sync-brands-from-system/steps/retrieve-brands-from-system.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/sync-brands-from-system/steps/retrieve-brands-from-system.ts" collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import BrandModuleService from "../../../modules/brand/service"
|
||||
import { BRAND_MODULE } from "../../../modules/brand"
|
||||
|
||||
export const retrieveBrandsFromSystemStep = createStep(
|
||||
"retrieve-brands-from-system",
|
||||
async (_, { container }) => {
|
||||
const brandModuleService: BrandModuleService = container.resolve(
|
||||
BRAND_MODULE
|
||||
)
|
||||
|
||||
const brands = await brandModuleService.client.retrieveBrands()
|
||||
|
||||
return new StepResponse(brands)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In this step, you resolve the Brand Module's main service from the container, and use its client service to retrieve the brands from the third-party system.
|
||||
|
||||
The step returns the retrieved brands.
|
||||
|
||||
### Create Brands Step
|
||||
|
||||
Next, create the step that creates new brands in Medusa in the file `src/workflows/sync-brands-from-system/steps/create-brands.ts`:
|
||||
|
||||
export const createBrandsHighlights = [
|
||||
["21", "createBrands", "Create the brands in Medusa"],
|
||||
["30", "deleteBrands", "Delete the brands from Medusa"]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/sync-brands-from-system/steps/create-brands.ts" highlights={createBrandsHighlights} collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { InferTypeOf } from "@medusajs/framework/types"
|
||||
import BrandModuleService from "../../../modules/brand/service"
|
||||
import { BRAND_MODULE } from "../../../modules/brand"
|
||||
import { Brand } from "../../../modules/brand/models/brand"
|
||||
|
||||
type CreateBrandsInput = {
|
||||
brands: InferTypeOf<typeof Brand>[]
|
||||
}
|
||||
|
||||
export const createBrandsStep = createStep(
|
||||
"create-brand-step",
|
||||
async (input: CreateBrandsInput, { container }) => {
|
||||
const brandModuleService: BrandModuleService = container.resolve(
|
||||
BRAND_MODULE
|
||||
)
|
||||
|
||||
const brands = await brandModuleService.createBrands(input.brands)
|
||||
|
||||
return new StepResponse(brands, brands.map((brand) => brand.id))
|
||||
},
|
||||
async (ids: string[], { container }) => {
|
||||
const brandModuleService: BrandModuleService = container.resolve(
|
||||
BRAND_MODULE
|
||||
)
|
||||
|
||||
await brandModuleService.deleteBrands(ids)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
This step receives the brands to create as input.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Since a data model is a variable, use the `InferTypeOf` utility imported from `@medusajs/framework/types` to infer its type.
|
||||
|
||||
</Note>
|
||||
|
||||
In the step, you resolve the Brand Module's main service and uses its `createBrands` method to create the brands.
|
||||
|
||||
You return the created brands and pass their IDs to the compensation function, which deletes the brands if an error occurs.
|
||||
|
||||
### Update Brands Step
|
||||
|
||||
To create the step that updates existing brands in Medusa, create the file `src/workflows/sync-brands-from-system/steps/update-brands.ts` with the following content:
|
||||
|
||||
export const updateBrandsHighlights = [
|
||||
["21", "prevUpdatedBrands", "Retrieve the data of the brands before the update."],
|
||||
["25", "updateBrands", "Update the brands in Medusa."],
|
||||
["34", "updateBrands", "Revert the update by reverting the brands' to before the update."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/sync-brands-from-system/steps/update-brands.ts" highlights={updateBrandsHighlights} collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { InferTypeOf } from "@medusajs/framework/types"
|
||||
import BrandModuleService from "../../../modules/brand/service"
|
||||
import { BRAND_MODULE } from "../../../modules/brand"
|
||||
import { Brand } from "../../../modules/brand/models/brand"
|
||||
|
||||
type UpdateBrandsInput = {
|
||||
brands: InferTypeOf<typeof Brand>[]
|
||||
}
|
||||
|
||||
export const updateBrandsStep = createStep(
|
||||
"update-brand-step",
|
||||
async ({ brands }: UpdateBrandsInput, { container }) => {
|
||||
const brandModuleService: BrandModuleService = container.resolve(
|
||||
BRAND_MODULE
|
||||
)
|
||||
|
||||
const prevUpdatedBrands = await brandModuleService.listBrands({
|
||||
id: brands.map((brand) => brand.id),
|
||||
})
|
||||
|
||||
const updatedBrands = await brandModuleService.updateBrands(brands)
|
||||
|
||||
return new StepResponse(updatedBrands, prevUpdatedBrands)
|
||||
},
|
||||
async (prevUpdatedBrands, { container }) => {
|
||||
const brandModuleService: BrandModuleService = container.resolve(
|
||||
BRAND_MODULE
|
||||
)
|
||||
|
||||
await brandModuleService.updateBrands(prevUpdatedBrands)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
This step receives the brands to update as input.
|
||||
|
||||
In the step, you retrieve the brands first to pass them later to the compensation function, then update and return the brands.
|
||||
|
||||
In the compensation function, you update the brands are again but to their data before the update made by the step.
|
||||
|
||||
### Create Workflow
|
||||
|
||||
Finally, create the workflow in the file `src/workflows/sync-brands-from-system/index.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/sync-brands-from-system/index.ts"
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
transform,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { InferTypeOf } from "@medusajs/framework/types"
|
||||
import { retrieveBrandsFromSystemStep } from "./steps/retrieve-brands-from-system"
|
||||
import { createBrandsStep } from "./steps/create-brands"
|
||||
import { updateBrandsStep } from "./steps/update-brands"
|
||||
import { Brand } from "../../modules/brand/models/brand"
|
||||
|
||||
export const syncBrandsFromSystemWorkflow = createWorkflow(
|
||||
"sync-brands-from-system",
|
||||
() => {
|
||||
const brands = retrieveBrandsFromSystemStep()
|
||||
|
||||
// TODO create and update brands
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
For now, you only add the `retrieveBrandsFromSystemStep` to the workflow that retrieves the brands from the third-party system.
|
||||
|
||||
### Identify Brands to Create or Update in Workflow
|
||||
|
||||
Next, you need to identify which brands must be created or updated.
|
||||
|
||||
Since workflows are constructed internally and are only evaluated during execution, you can't access any data's value to perform data manipulation or checks.
|
||||
|
||||
Instead, use the `transform` utility function imported from `@medusajs/framework/workflows-sdk`, which gives you access to the real-time values of the data to perform actions on them.
|
||||
|
||||
So, replace the `TODO` with the following:
|
||||
|
||||
```ts title="src/workflows/sync-brands-from-system/index.ts"
|
||||
const { toCreate, toUpdate } = transform(
|
||||
{
|
||||
brands,
|
||||
},
|
||||
(data) => {
|
||||
const toCreate: InferTypeOf<typeof Brand>[] = []
|
||||
const toUpdate: InferTypeOf<typeof Brand>[] = []
|
||||
|
||||
data.brands.forEach((brand) => {
|
||||
if (brand.external_id) {
|
||||
toUpdate.push({
|
||||
...brand,
|
||||
id: brand.external_id,
|
||||
})
|
||||
} else {
|
||||
toCreate.push(brand)
|
||||
}
|
||||
})
|
||||
|
||||
return { toCreate, toUpdate }
|
||||
}
|
||||
)
|
||||
|
||||
// TODO create and update the brands
|
||||
```
|
||||
|
||||
`transform` accepts two parameters:
|
||||
|
||||
1. The data to be passed to the function in the second parameter.
|
||||
2. A function to execute only when the workflow is executed. Its return value can be consumed by the rest of the workflow.
|
||||
|
||||
In the function, you sort the brands as to be created or to be updated based on whether they have an `external_id` property.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
This approach assumes that the third-party system stores the ID of the brand in Medusa in `external_id`.
|
||||
|
||||
</Note>
|
||||
|
||||
### Create and Update the Brands
|
||||
|
||||
Finally, replace the new `TODO` with the following:
|
||||
|
||||
```ts title="src/workflows/sync-brands-from-system/index.ts"
|
||||
const created = createBrandsStep({ brands: toCreate })
|
||||
const updated = updateBrandsStep({ brands: toUpdate })
|
||||
|
||||
return new WorkflowResponse({
|
||||
created,
|
||||
updated,
|
||||
})
|
||||
```
|
||||
|
||||
You pass the brands to be created to the `createBrandsStep`, and the brands to be updated to the `updateBrandsStep`.
|
||||
|
||||
Then, you return the created and updated brands.
|
||||
|
||||
---
|
||||
|
||||
## 2. Schedule Syncing Task
|
||||
|
||||
To schedule a task that syncs brands from the third-party system, create a scheduled job at `src/jobs/sync-brands-from-system.ts`:
|
||||
|
||||
```ts title="src/jobs/sync-brands-from-system.ts"
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { syncBrandsFromSystemWorkflow } from "../workflows/sync-brands-from-system"
|
||||
|
||||
export default async function (container: MedusaContainer) {
|
||||
const logger = container.resolve("logger")
|
||||
|
||||
const { result } = await syncBrandsFromSystemWorkflow(container).run()
|
||||
|
||||
logger.info(
|
||||
`Synced brands from third-party system: ${
|
||||
result.created.length
|
||||
} brands created and ${result.updated.length} brands updated.`)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "sync-brands-from-system",
|
||||
schedule: "* * * * *",
|
||||
}
|
||||
```
|
||||
|
||||
This defines a scheduled job that runs every minute (for testing purposes).
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about scheduled jobs [in this guide](../../../basics/scheduled-jobs/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
The scheduled job executes the `syncBrandsFromSystemWorkflow` and prints how many brands were created and updated.
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
To test it out, start the Medusa application. In a minute, the scheduled job will run and you'll see a logged message indicating how many brands were created or updated.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
In the previous chapters, you:
|
||||
|
||||
- Created a service that acts as a client integrating a third-party system.
|
||||
- Implemented two-way sync of brands between the third-party system and Medusa using a subscriber and a scheduled job.
|
||||
Reference in New Issue
Block a user