docs: added "Integrate Your Ecommerce Stack" recipe (#5774)

This commit is contained in:
Shahed Nasser
2023-11-30 11:27:09 +00:00
committed by GitHub
parent 4e9d954549
commit c808317044
4 changed files with 387 additions and 9 deletions

View File

@@ -0,0 +1,307 @@
---
addHowToData: true
---
import DocCard from '@theme/DocCard';
import Icons from '@theme/Icon';
import LearningPath from '@site/src/components/LearningPath';
# Integrate Ecommerce Stack Recipe
This document guides you through integrating systems in your ecommerce stack with Medusa.
## Overview
Integrating third-party systems, such as ERP or a CMS, into your ecommerce stack can be challenging. It requires:
- Establishing connections with the different systems based on each of their APIs.
- Building flows that span across multiple systems.
- Maintaining data consistency and syncing between your systems.
Medusas architecture and functionalities allow you to integrate third-party systems and build flows around them. It also provides error-handling mechanisms and webhook capabilities that prevent data inconsistency between your systems.
<LearningPath pathName="integrate-ecommerce-stack" />
---
## Connect to External Systems with Services
Medusas Services let you implement a client that connects and performs functionalities with your third-party system. You can then use the service to connect to your third-party system in other resources, such as a Workflow or an API Route.
This increases the maintainability of your integration as its all implemented within the service. If the external system makes any changes to its APIs, you only need to make changes within the service.
<DocCard item={{
type: 'link',
href: '/development/services/create-service',
label: 'Create a Service',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create a service in Medusa.',
}
}} />
<Details>
<Summary>Example: Create a service for an ERP system</Summary>
Heres an example of a service created for an external ERP system:
```ts title="src/services/erp.ts"
import { ProductService, TransactionBaseService } from "@medusajs/medusa";
import { MedusaError } from "@medusajs/utils"
import axios, { AxiosInstance } from "axios"
type InjectedDependencies = {
productService: ProductService
}
class ErpService extends TransactionBaseService {
private client_: AxiosInstance
private productService_: ProductService
constructor(container: InjectedDependencies) {
super(container)
this.productService_ = container.productService
this.client_ = axios.create({
baseURL: `https://api.erp-example.com`,
headers: {
Authorization: `Bearer ${process.env.ERP_TOKEN}`
}
})
}
async getProductERPDetails (id: string) {
const product = await this.productService_.retrieve(id, {
select: ["external_id"]
})
if (!product) {
throw new MedusaError(MedusaError.Types.NOT_FOUND, `Product with id ${id} was not found.`)
}
const erpProduct = await this.client_.get(`/product/${product.external_id}`)
return erpProduct
}
async updateProduct(product: any) {
// do stuff
}
}
export default ErpService
```
In this example, you create an `ErpService` that is used to interact with the third-party system. In the service, you create a client using [axios](https://axios-http.com/) to send requests to the ERP system. If the system youre integrating provides a JavaScript SDK, you can use that instead.
Then, in the service, you create methods for each ERP functionality. For example, the `getProductErpDetails` method retrieves the details of a product in the ERP system.
:::tip
Products have an `external_id` attribute that can be used to store the product's ID in an external system.
:::
</Details>
---
## Build Flows Across Systems
With Medusas workflows, you can build flows with steps that may perform actions on different systems. For example, you can create a workflow that updates the products details in integrated systems like ERPs, WMSs, and CMSs.
Workflows can be executed from anywhere. So, taking the workflow described in the above example, you can listen to the `product.updated` event using a [Subscriber](../development/events/subscribers.mdx) and execute the workflow whenever the event is triggered.
![A flowchart of how the workflow is executed when the product.updated event is triggered](https://res.cloudinary.com/dza7lstvk/image/upload/v1701340569/Medusa%20Docs/Diagrams/workflow-recipe-example_wmwmo5.jpg)
Workflows guarantee data consistency through their compensation feature. You can provide a compensation function to steps that roll back the actions of that step. Then, if an error occurs in any step, the actions of previous steps are rolled back using the compensation function.
<DocCard item={{
type: 'link',
href: '/development/workflows',
label: 'Workflows',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn more about Workflows and how to create them.',
}
}} />
<Details>
<Summary>Example: Update products across systems with workflows</Summary>
For example, create the following workflow in `src/workflows/update-product.ts`:
```ts title="src/workflows/update-product.ts"
import { Product, ProductService } from "@medusajs/medusa"
import {
createStep,
StepResponse,
createWorkflow
} from "@medusajs/workflows-sdk"
import ErpService from "../services/erp"
type WorkflowInput = {
productId: string
}
const updateInErpStep = createStep("update-in-erp", async ({ productId }: WorkflowInput, context) => {
const erpService: ErpService = context.container.resolve("erpService")
const productService: ProductService = context.container.resolve("productService")
const updatedProductData = await productService.retrieve(productId)
const oldProductData = await erpService.getProductERPDetails(productId)
await erpService.updateProduct(updatedProductData)
return new StepResponse({}, {
// provide the old product data as a parameter
// to the compensation function in case an error
// occurs.
oldProductData
})
}, async ({
oldProductData
}, context) => {
const erpService: ErpService = context.container.resolve("erpService")
await erpService.updateProduct(oldProductData)
})
const updateInCmsStep = createStep("update-in-cms", async({ productId }: WorkflowInput, context) => {
// update product in CMS...
})
const updateInWmsStep = createStep("update-in-wms", async({ productId }: WorkflowInput, context) => {
// update product in WMS...
})
const updateProductWorkflow = createWorkflow<
WorkflowInput, void
>("update-product-in-systems",
function (input) {
updateInErpStep(input)
updateInCmsStep(input)
updateInWmsStep(input)
})
export default updateProductWorkflow
```
In this workflow, you create three steps: one to update the product in an ERP, another in a CMS, and another in a WMS.
In the `update-in-erp` step, you resolve the ERPs service that you created and use it to update the product in the ERP. You also provide a compensation function, which is passed as a third parameter to the `createStep` function. The compensation function reverts the update in the ERP system in case an error occurs.
You then create the workflow `update-product-in-systems` which uses the three steps in its constructor function. The workflows constructor function doesnt run until its executed.
Then, create the subscriber at `src/subscribers/update-product.ts`:
```ts title="src/subscribers/update-product.ts"
import {
type SubscriberConfig,
type SubscriberArgs,
ProductService,
} from "@medusajs/medusa"
import updateProductWorkflow from "../workflows/update-product"
export default async function handleProductUpdate({
data, eventName, container, pluginOptions
}: SubscriberArgs<Record<string, any>>) {
updateProductWorkflow(container)
.run({
input: {
productId: data.id
}
})
.then(() => {
console.log("Updated product across systems.")
})
}
export const config: SubscriberConfig = {
event: ProductService.Events.UPDATED,
context: {
subscriberId: "product-update"
}
}
```
The subscriber executes the workflow whenever the `product.updated` event is triggered, passing it the ID of the updated product.
</Details>
---
## Create Webhook Listeners
You can provide webhook listeners that your external systems call when their data is updated. This lets you synchronize data between your systems. Webhook listeners can be created in Medusa using API Routes.
For example, suppose an administrator changes the product data in the ERP system. In that case, the system sends a request to the webhook you define in Medusa, which updates the product data in Medusa.
<DocCard item={{
type: 'link',
href: '/development/api-routes/create',
label: 'Create an API Route',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create an API Route in Medusa.',
}
}} />
<Details>
<Summary>Example: Create a webhook listener for ERP changes</Summary>
For example, create the file `src/api/webhooks/erp/update/route.ts` with the following content:
```ts title="src/api/webhooks/erp/update/route.ts"
import {
MedusaRequest,
MedusaResponse,
ProductService
} from "@medusajs/medusa"
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
const { id, updatedData} = req.body
const productService: ProductService = req.scope.resolve(
"productService"
)
await productService.update(id, updatedData)
res.status(200)
}
```
This creates a webhook listener for an ERP system. It receives the ID of a product and its updated data, assuming thats how your ERP system sends the data.
Then, create the file `src/api/middlewares.ts` with the following content:
```ts title="src/api/middlewares.ts"
import { MiddlewaresConfig } from "@medusajs/medusa"
import { raw } from "body-parser"
export const config: MiddlewaresConfig = {
routes: [
{
method: ["POST", "PUT"],
matcher: "/webhooks/*",
bodyParser: false,
middlewares: [raw({ type: "application/json" })],
},
],
}
```
This replaces the default JSON middleware with the raw middleware, which is useful for webhook routes.
</Details>
---
## Additional Development
You can find other resources for your ecommerce development in the [Medusa Development section](../development/overview.mdx) of this documentation.

View File

@@ -73,6 +73,14 @@ module.exports = {
iconName: "credit-card-solid",
},
},
{
type: "doc",
id: "recipes/integrate-ecommerce-stack",
label: "Integrate Ecommerce Stack",
customProps: {
iconName: "puzzle-solid",
},
},
{
type: "doc",
id: "recipes/commerce-automation",

View File

@@ -78,7 +78,7 @@ const LearningPathSteps: React.FC<LearningPathStepsProps> = ({ ...rest }) => {
)}
key={index}
>
<div className={clsx("flex items-center gap-1")}>
<div className={clsx("flex items-center gap-1 relative")}>
<div className="w-2 flex-none flex items-center justify-center">
{index === currentStep && (
<IconCircleDottedLine
@@ -102,6 +102,14 @@ const LearningPathSteps: React.FC<LearningPathStepsProps> = ({ ...rest }) => {
>
{step.title}
</span>
<Link
href={step.path}
className={clsx("absolute top-0 left-0 w-full h-full")}
onClick={(e) => {
e.preventDefault()
goToStep(index)
}}
/>
</div>
{index === currentStep && (
<div className={clsx("flex items-center gap-1")}>
@@ -113,14 +121,6 @@ const LearningPathSteps: React.FC<LearningPathStepsProps> = ({ ...rest }) => {
</div>
</div>
)}
<Link
href={step.path}
className={clsx("absolute top-0 left-0 w-full h-full")}
onClick={(e) => {
e.preventDefault()
goToStep(index)
}}
/>
</div>
))}
</div>

View File

@@ -449,6 +449,69 @@ const paths: LearningPathType[] = [
},
},
},
{
name: "integrate-ecommerce-stack",
label: "Integrate Ecommerce Stack",
description:
"Use Medusas architecture and functionalities to integrate third-party systems and build flows around them.",
steps: [
{
title: "Connect to External Systems with Services",
path: "/development/services/create-service",
descriptionJSX: (
<>
Medusas Services let you implement a client that connects and
performs functionalities with your third-party system.
<br />
<br />
You can then use the service to connect to your third-party system
in other resources, such as a Workflow or an API Route.
</>
),
},
{
title: "Build Flows Across Systems",
path: "/development/workflows",
descriptionJSX: (
<>
With Medusas workflows, you can build flows with steps that may
perform actions on different systems. Workflows can be executed from
anywhere.
<br />
<br />
For example, you can create a workflow that updates the products
details in integrated systems like ERPs, WMSs, and CMSs. Then, you
can listen to the
<code>product.updated</code> event using a{" "}
<Link to="/development/events/create-subscriber">Subscriber</Link>{" "}
and execute the workflow whenever the event is triggered.
</>
),
},
{
title: "Create Webhook Listeners",
path: "/development/api-routes/create",
descriptionJSX: (
<>
You can provide webhook listeners that your external systems call
when their data is updated. This lets you synchronize data between
your systems.
<br />
<br />
Webhook listeners can be created in Medusa using API Routes.
</>
),
},
],
finish: {
type: "rating",
step: {
title: "Congratulations on integrating your ecommerce stack!",
description: "Please rate your experience using this recipe.",
eventName: "rating_path_integrate-ecommerce-stack",
},
},
},
// TODO: Eventually remove these learning paths
{
name: "rbac",