Skip to content

Commit

Permalink
docs: revise last chapters of customizations (medusajs#10480)
Browse files Browse the repository at this point in the history
  • Loading branch information
shahednasser authored Dec 6, 2024
1 parent 21b0e0c commit a76b533
Show file tree
Hide file tree
Showing 8 changed files with 447 additions and 336 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ You can now create the workflow that runs the `createBrandStep`. A workflow is c

Add the following content in the same `src/workflows/create-brand.ts` file:

```ts
```ts title="src/workflows/create-brand.ts"
// other imports...
import {
// ...
Expand Down

Large diffs are not rendered by default.

25 changes: 11 additions & 14 deletions www/apps/book/app/learn/customization/integrate-systems/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,22 @@ export const metadata = {

# {metadata.title}

In this chapter, you'll learn how to integrate a third-party system into Medusa.
Commerce applications often connect to third-party systems that provide additional or specialized features. For example, you may integrate a Content-Management System (CMS) for rich content features, a payment provider to process credit-card payments, and a notification service to send emails.

## How to Integrate a Third-Party System?
Medusa's framework facilitates integrating these systems and orchestrating operations across them, saving you the effort of managing them yourself. You won't find those capabilities in other commerce platforms that in these scenarios become a bottleneck to building customizations and iterating quickly.

To integrate a third-party system into Medusa, you:
In Medusa, you integrate a third-party system by:

1. Implement the methods to interact with the system in a service. It can either be the main module's service, or an internal service in the module that's used by the main one.
2. Implement in workflows custom features around the integration, such as sending data to the third-party system.
- Workflows roll-back mechanism ensures data consistency. This is essential as you integrate multiple systems into your application.
3. Use the workflow in other resources to expose or utilize the custom functionality.
1. Creating a module whose service provides the methods to connect to and perform operations in the third-party system.
2. Building workflows that complete tasks spanning across systems. You use the module that integrates a third-party system in the workflow's steps.
3. Executing the workflows you built in an [API route](../../basics/api-routes/page.mdx), at a scheduled time, or when an event is emitted.

---

## Next Chapters: Syncing Brands Example
## Next Chapters: Sync Brands Example

In the next chapters, you'll implement an example of syncing brands with a third-party system, such as a Content Management System (CMS).
In the previous chapters, you've [added brands](../custom-features/module/page.mdx) to your Medusa application. In the next chapters, you will:

That requires:

1. Implementing the service that integrates the third-party system.
2. Creating a brand in the third-party system when a brand is created in Medusa.
2. Retrieving the brands from the third-party system to sync them with Medusa's brands at a scheduled interval.
1. Integrate a dummy third-party CMS in the Brand Module.
2. Sync brands to the CMS when a brand is created.
2. Sync brands from the CMS at a daily schedule.

Large diffs are not rendered by default.

178 changes: 77 additions & 101 deletions www/apps/book/app/learn/customization/integrate-systems/service/page.mdx
Original file line number Diff line number Diff line change
@@ -1,41 +1,46 @@
import { Prerequisites } from "docs-ui"

export const metadata = {
title: `${pageNumber} Integrate Third-Party Brand System in a Service`,
title: `${pageNumber} Guide: Integrate CMS Brand System`,
}

# {metadata.title}

<Note title="Example Chapter">
In the previous chapters, you've created a [Brand Module](../../custom-features/module/page.mdx) that adds brands to your application. In this chapter, you'll integrate a dummy Content-Management System (CMS) in a new module. The module's service will provide methods to retrieve and manage brands in the CMS. You'll later use this service to sync data from and to the CMS.

This chapter covers how to integrate a dummy third-party system in a service as a step of the ["Integrate Systems" chapter](../page.mdx).
<Note>

Learn more about modules in [this chapter](../../../basics/modules/page.mdx).

</Note>

## 1. Create Service
## 1. Create Module Directory

<Prerequisites
items={[
{
text: "Brand Module",
link: "/learn/customization/custom-features/module"
}
]}
/>
You'll integrate the third-party system in a new CMS Module. So, create the directory `src/modules/cms` that will hold the module's resources.

![Directory structure after adding the directory for the CMS Module](https://res.cloudinary.com/dza7lstvk/image/upload/v1733492447/Medusa%20Book/cms-dir-overview-1_gasguk.jpg)

---

Start by creating the file `src/modules/brand/services/client.ts` with the following content:
## 2. Create Module Service

Next, you'll create the module's service. It will provide methods to connect and perform actions with the third-party system.

Create the CMS Module's service at `src/modules/cms/service.ts` with the following content:

![Directory structure after adding the CMS Module's service](https://res.cloudinary.com/dza7lstvk/image/upload/v1733492583/Medusa%20Book/cms-dir-overview-2_zwcwh3.jpg)

export const serviceHighlights = [
["4", "BrandClientOptions", "Define the options that the Brand Module receives necessary for the integration."],
["8", "InjectedDependencies", "Define the dependencies injected into the service."],
["20", "moduleDef", "Retrieve the module's configuration."]
["3", "ModuleOptions", "The options that the CMS Module receives."],
["7", "InjectedDependencies", "The dependencies injected into the service from the module's container."],
["16", "logger", "Dependencies injected from the module's container"],
["16", "options", "Options passed to the module in the configurations."]
]

```ts title="src/modules/brand/services/client.ts" highlights={serviceHighlights}
```ts title="src/modules/cms/service.ts" highlights={serviceHighlights}
import { Logger, ConfigModule } from "@medusajs/framework/types"
import { BRAND_MODULE } from ".."

export type BrandClientOptions = {
export type ModuleOptions = {
apiKey: string
}

Expand All @@ -44,162 +49,133 @@ type InjectedDependencies = {
configModule: ConfigModule
}

export class BrandClient {
private options_: BrandClientOptions
class CmsModuleService {
private options_: ModuleOptions
private logger_: Logger

constructor({ logger, configModule }: InjectedDependencies) {
constructor({ logger }: InjectedDependencies, options: ModuleOptions) {
this.logger_ = logger
this.options_ = options

const moduleDef = configModule.modules[BRAND_MODULE]
if (typeof moduleDef !== "boolean") {
this.options_ = moduleDef.options as BrandClientOptions
}
// TODO initialize SDK
}
}
```

This creates a `BrandClient` service. Using dependency injection, you resolve the `logger` and `configModule` from the Module's container.

`logger` is useful to log messages, and `configModule` has configurations exported in `medusa-config.ts`.

You also define an `options_` property in your service to store the module's options.

The `configModule`'s `modules` property is an object whose keys are registered module names and values are the module's configuration.
export default CmsModuleService
```

If the module's configuration isn't a boolean, it has an `options` property that holds the module's options. You use it to set the `options_` property's value.
You create a `CmsModuleService` that will hold the methods to connect to the third-party CMS. A service's constructor accepts two parameters:

<Note title="Tip">
1. The module's container. Since a module is [isolated](../../../advanced-development/modules/isolation/page.mdx), it has a [local container](../../../advanced-development/modules/container/page.mdx) different than the Medusa container you use in other customizations. This container holds framework tools like the [Logger utility](../../../debugging-and-testing/logging/page.mdx) and resources within the module.
2. Options passed to the module when it's later added in Medusa's configurations. These options are useful to pass secret keys or configurations that ensure your module is re-usable across applications. For the CMS Module, you accept the API key to connect to the dummy CMS as an option.

If the service integrating the third-party system was a main service, it receives the module's options as a second parameter.

</Note>
When integrating a third-party system that has a Node.js SDK or client, you can initialize that client in the constructor to be used in the service's methods.

### Integration Methods

Next, add the following methods to simulate sending requests to the third-party system:
Next, you'll add methods that simulate sending requests to a third-party CMS. You'll use these methods later to sync brands from and to the CMS.

Add the following methods in the `CmsModuleService`:

export const methodsHighlights = [
["10", "sendRequest", "Since the third-party system isn't real, this method only logs a message."],
["19", "createBrand", "A method that creates a brand in the third-party system."],
["23", "deleteBrand", "A method that deletes a brand in the third-party system."],
["27", "retrieveBrands", "A method that retrieves a brand from a third-party system."]
["6", "sendRequest", "Since the third-party system isn't real, this method only logs a message."],
["12", "createBrand", "A method that creates a brand in the third-party system."],
["16", "deleteBrand", "A method that deletes a brand in the third-party system."],
["20", "retrieveBrands", "A method that retrieves a brand from a third-party system."]
]

```ts title="src/modules/brand/services/client.ts" highlights={methodsHighlights}
// other imports...
import { InferTypeOf } from "@medusajs/framework/types"
import { Brand } from "../models/brand"

export class BrandClient {
// ...

// a dummy method to simulate sending a request,
// in a realistic scenario, you'd use an SDK, fetch, or axios clients
private async sendRequest(url: string, method: string, data?: any) {
this.logger_.info(`Sending a ${
method
} request to ${url}. data: ${JSON.stringify(data, null, 2)}`)
this.logger_.info(`Client Options: ${
JSON.stringify(this.options_, null, 2)
}`)
this.logger_.info(`Sending a ${method} request to ${url}.`)
this.logger_.info(`Request Data: ${JSON.stringify(data, null, 2)}`)
this.logger_.info(`API Key: ${JSON.stringify(this.options_.apiKey, null, 2)}`)
}

async createBrand(brand: InferTypeOf<typeof Brand>) {
async createBrand(brand: Record<string, unknown>) {
await this.sendRequest("/brands", "POST", brand)
}

async deleteBrand(id: string) {
await this.sendRequest(`/brands/${id}`, "DELETE")
}

async retrieveBrands() {
async retrieveBrands(): Promise<Record<string, unknown>[]> {
await this.sendRequest("/brands", "GET")

return []
}
}
```

The `sendRequest` method is a dummy method to simulate sending a request to a third-party system.
The `sendRequest` method sends requests to the third-party CMS. Since this guide isn't using a real CMS, it only simulates the sending by logging messages in the terminal.

You also add three methods that use the `sendRequest` method:

- `createBrand` that creates a brand in the third-party system. To reference a brand's type, you use the `InferTypeOf` utility imported from `@medusajs/framework/types`. This transforms a data model, which is a variable, to its equivalent type.
- `createBrand` that creates a brand in the third-party system.
- `deleteBrand` that deletes the brand in the third-party system.
- `retrieveBrands` to retrieve a brand from the third-party system.

---

## 2. Export Service
## 3. Export Module Definition

If the service integrating the third-party system is the module's main service, you only need to export it in the module definition.
After creating the module's service, you'll export the module definition indicating the module's name and service.

However, since this service is an internal service in the Brand Module, you must export it in a `src/modules/brand/services/index.ts` file:
Create the file `src/modules/cms/index.ts` with the following content:

```ts title="src/modules/brand/services/index.ts"
export * from "./client"
```
![Directory structure of the Medusa application after adding the module definition file](https://res.cloudinary.com/dza7lstvk/image/upload/v1733492991/Medusa%20Book/cms-dir-overview-3_b0byks.jpg)

This registers the service in the module's container, allowing you to access it in the module's main service.
```ts title="src/modules/cms/index.ts"
import { Module } from "@medusajs/framework/utils"
import CmsModuleService from "./service"

---

## 3. Add Internal Service in Main Service

In the main service at `src/modules/brand/service.ts`, add the following imports and types at the top of the file:

```ts title="src/modules/brand/service.ts"
// other imports...
import { BrandClient, BrandClientOptions } from "./services"

type InjectedDependencies = {
brandClient: BrandClient
}
```
Then, add the following in the `BrandModuleService` class:
export const CMS_MODULE = "cms"

```ts title="src/modules/brand/service.ts"
class BrandModuleService extends MedusaService({
Brand,
}) {
public client: BrandClient

constructor({ brandClient }: InjectedDependencies) {
super(...arguments)

this.client = brandClient
}
}
export default Module(CMS_MODULE, {
service: CmsModuleService,
})
```

In the main module service, you first resolve through dependency injection the `brandClient` from the container and set it in a public property `client`.
You use `Module` from the Modules SDK to export the module's defintion, indicating that the module's name is `cms` and its service is `CmsModuleService`.

---

## 4. Pass Options to the Module
## 4. Add Module to Medusa's Configurations

To pass options in the module, change its configurations in `medusa-config.ts`:
Finally, add the module to the Medusa configurations at `medusa-config.ts`:

```ts title="medusa-config.ts"
module.exports = defineConfig({
// ...
modules: [
// ...
{
resolve: "./src/modules/brand",
resolve: "./src/modules/cms",
options: {
apiKey: process.env.BRAND_API_KEY || "temp",
apiKey: process.env.CMS_API_KEY,
},
},
],
})
```

A module's configuration accepts an `options` property, which can hold any options to pass to the module.
The object passed in `modules` accept an `options` property, whose value is an object of options to pass to the module. These are the options you receive in the `CmsModuleService`'s constructor.

You can add the `CMS_API_KEY` environment variable to `.env`:

```bash
CMS_API_KEY=123
```

---

## Next Steps: Sync Brand From Medusa to Third-Party System
## Next Steps: Sync Brand From Medusa to CMS

You can now use the CMS Module's service to perform actions on the third-party CMS.

In the next chapter, you'll learn how to sync brands created in Medusa to the third-party system using a workflow and a scheduled job.
In the next chapter, you'll learn how to emit an event when a brand is created, then handle that event to sync the brand from Medusa to the third-party service.
24 changes: 6 additions & 18 deletions www/apps/book/app/learn/customization/next-steps/page.mdx
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
export const metadata = {
title: `${pageNumber} Customizations Next Steps`,
title: `${pageNumber} Customizations Next Steps: Learn the Fundamentals`,
}

# {metadata.title}

The previous examples under the Customization chapter explained more about customizing Medusa for a realistic use case.
The previous guides introduced Medusa's different concepts and how you can use them to customize Medusa for a realistic use case, You added brands to your application, linked them to products, customized the admin dashboard, and integrated a third-party CMS.

Your learning journey doesn't end here, and this only presents some of Medusa's powerful features.

This chapter guides you into how to continue your learning journey, and what resources will be helpful for you during your development.

## Follow the Rest of this Documentation

The next chapters of the documentation provide more in-depth uses of the different concepts you learned about.

While you can start playing around with Medusa and customize it, it's highly recommended to continue the rest of this documentation to learn about what more you can do with each concept.

---
The next chapters will cover each of these concepts in depth, with the different ways you can use them, their options or configurations, and more advanced features that weren't covered in the previous guides. While you can start building with Medusa, it's highly recommended to follow the next chapters for a better understanding of Medusa's fundamentals.

## Helpful Resources Guides

The [Development Resources](!resources!) documentation provides more helpful guides and references for your development journey.

Some of these guides and references are:
The [Development Resources](!resources!) documentation provides more helpful guides and references for your development journey. Some of these guides and references include:

1. [Service Factory Reference](!resources!/service-factory-reference): Learn about the methods generated by the service factory with examples.
2. [Workflows Reference](!resources!/medusa-workflows-reference): Browse the list of workflows and their hooks.
3. [Commerce Modules](!resources!/commerce-modules): Browse the list of commerce modules in Medusa and their references to learn how to use them.
1. [Service Factory Reference](!resources!/service-factory-reference): Learn about the methods generated by `MedusaService` with examples.
2. [Workflows Reference](!resources!/medusa-workflows-reference): Browse the list of core workflows and their hooks that are useful for your customizations.
4. [Admin Injection Zones](!resources!/admin-widget-injection-zones): Browse the injection zones in the Medusa Admin to learn where you can inject widgets.

---
Expand Down
12 changes: 6 additions & 6 deletions www/apps/book/generated/edit-dates.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export const generatedEditDates = {
"app/learn/advanced-development/workflows/variable-manipulation/page.mdx": "2024-11-14T16:11:24.538Z",
"app/learn/customization/custom-features/api-route/page.mdx": "2024-11-28T13:12:10.521Z",
"app/learn/customization/custom-features/module/page.mdx": "2024-11-28T09:25:29.098Z",
"app/learn/customization/custom-features/workflow/page.mdx": "2024-11-28T10:47:28.084Z",
"app/learn/customization/custom-features/workflow/page.mdx": "2024-12-06T14:34:53.354Z",
"app/learn/customization/extend-features/extend-create-product/page.mdx": "2024-12-05T09:26:15.796Z",
"app/learn/customization/custom-features/page.mdx": "2024-11-28T08:21:55.207Z",
"app/learn/customization/customize-admin/page.mdx": "2024-12-06T07:21:02.303Z",
Expand All @@ -100,11 +100,11 @@ export const generatedEditDates = {
"app/learn/customization/extend-features/define-link/page.mdx": "2024-12-04T17:15:16.004Z",
"app/learn/customization/extend-features/page.mdx": "2024-09-12T12:38:57.394Z",
"app/learn/customization/extend-features/query-linked-records/page.mdx": "2024-12-05T10:36:32.357Z",
"app/learn/customization/integrate-systems/handle-event/page.mdx": "2024-09-30T08:43:53.135Z",
"app/learn/customization/integrate-systems/page.mdx": "2024-09-12T12:33:29.827Z",
"app/learn/customization/integrate-systems/schedule-task/page.mdx": "2024-09-30T08:43:53.135Z",
"app/learn/customization/integrate-systems/service/page.mdx": "2024-10-16T08:49:50.899Z",
"app/learn/customization/next-steps/page.mdx": "2024-09-12T10:50:04.873Z",
"app/learn/customization/integrate-systems/handle-event/page.mdx": "2024-12-06T14:34:53.355Z",
"app/learn/customization/integrate-systems/page.mdx": "2024-12-06T14:34:53.355Z",
"app/learn/customization/integrate-systems/schedule-task/page.mdx": "2024-12-06T14:34:53.355Z",
"app/learn/customization/integrate-systems/service/page.mdx": "2024-12-06T14:34:53.356Z",
"app/learn/customization/next-steps/page.mdx": "2024-12-06T14:34:53.356Z",
"app/learn/customization/page.mdx": "2024-09-12T11:16:18.504Z",
"app/learn/more-resources/cheatsheet/page.mdx": "2024-07-11T16:11:26.480Z",
"app/learn/architecture/architectural-modules/page.mdx": "2024-09-23T12:51:04.520Z",
Expand Down
Loading

0 comments on commit a76b533

Please sign in to comment.