322 lines
10 KiB
Plaintext
322 lines
10 KiB
Plaintext
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.
|