docs: improve module isolation chapter (#12565)

* docs: improve module isolation chapter

* add note about loaders running with migrations
This commit is contained in:
Shahed Nasser
2025-05-21 18:57:28 +03:00
committed by GitHub
parent 499381d119
commit bc965eb6aa
5 changed files with 15758 additions and 15430 deletions

View File

@@ -6,9 +6,9 @@ export const metadata = {
In this chapter, you'll learn about the module's container and how to resolve resources in that container.
Since modules are isolated, each module has a local container only used by the resources of that module.
Since modules are [isolated](../isolation/page.mdx), each module has a local container only used by the resources of that module.
So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container.
So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container, and some Framework tools that the Medusa application registers in the module's container.
### List of Registered Resources

View File

@@ -9,7 +9,8 @@ In this chapter, you'll learn how modules are isolated, and what that means for
<Note title="Summary">
- Modules can't access resources, such as services or data models, from other modules.
- Use Medusa's linking concepts, as explained in the [Module Links chapters](../../../fundamentals/module-links/page.mdx), to extend a module's data models and retrieve data across modules.
- Use [Module Links](../../../fundamentals/module-links/page.mdx) to extend an existing module's data models, and [Query](../../module-links/query/page.mdx) to retrieve data across modules.
- Use [workflows](../../workflows/page.mdx) to build features that depend on functionalities from different modules.
</Note>
@@ -19,6 +20,14 @@ A module is unaware of any resources other than its own, such as services or dat
For example, your custom module can't resolve the Product Module's main service or have direct relationships from its data model to the Product Module's data models.
A module has its own container, as explained in the [Module Container](../container/page.mdx) chapter. This container includes the module's resources, such as services and data models, and some Framework resources that the Medusa application provides.
<Note>
Refer to the [Module Container Resources](!resources!/medusa-container-resources) for a list of resources registered in a module's container.
</Note>
---
## Why are Modules Isolated
@@ -26,20 +35,24 @@ For example, your custom module can't resolve the Product Module's main service
Some of the module isolation's benefits include:
- Integrate your module into any Medusa application without side-effects to your setup.
- Replace existing modules with your custom implementation, if your use case is drastically different.
- Replace existing modules with your custom implementation if your use case is drastically different.
- Use modules in other environments, such as Edge functions and Next.js apps.
---
## How to Extend Data Model of Another Module?
To extend the data model of another module, such as the `product` data model of the Product Module, use Medusa's linking concepts as explained in the [Module Links chapters](../../../fundamentals/module-links/page.mdx).
To extend the data model of another module, such as the `Product` data model of the Product Module, use [Module Links](../../../fundamentals/module-links/page.mdx). Module Links allow you to build associations between data models of different modules without breaking the module isolation.
Then, you can retrieve data across modules using [Query](../../module-links/query/page.mdx).
---
## How to Use Services of Other Modules?
If you're building a feature that uses functionalities from different modules, use a workflow whose steps resolve the modules' services to perform these functionalities.
You'll often build feature that uses functionalities from different modules. For example, if you may need to retrieve brands, then sync them to a third-party service.
To build functionalities spanning across modules and systems, create a [workflow](../../workflows/page.mdx) whose steps resolve the modules' services to perform these functionalities.
Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output.
@@ -63,7 +76,7 @@ const retrieveBrandsStep = createStep(
"retrieve-brands",
async (_, { container }) => {
const brandModuleService = container.resolve(
"brandModuleService"
"brand"
)
const brands = await brandModuleService.listBrands()
@@ -76,7 +89,7 @@ const createBrandsInCmsStep = createStep(
"create-brands-in-cms",
async ({ brands }, { container }) => {
const cmsModuleService = container.resolve(
"cmsModuleService"
"cms"
)
const cmsBrands = await cmsModuleService.createBrands(brands)
@@ -85,7 +98,7 @@ const createBrandsInCmsStep = createStep(
},
async (brands, { container }) => {
const cmsModuleService = container.resolve(
"cmsModuleService"
"cms"
)
await cmsModuleService.deleteBrands(
@@ -95,7 +108,7 @@ const createBrandsInCmsStep = createStep(
)
```
The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module.
The `retrieveBrandsStep` retrieves the brands from a Brand Module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS Module.
Then, create the following workflow that uses these steps:
@@ -111,3 +124,154 @@ export const syncBrandsWorkflow = createWorkflow(
```
You can then use this workflow in an API route, scheduled job, or other resources that use this functionality.
---
## How to Use Framework APIs and Tools in Module?
### Framework Tools in Module Container
A module has in its container some Framework APIs and tools, such as [Logger](../../../debugging-and-testing/logging/page.mdx). You can refer to the [Module Container Resources](!resources!/medusa-container-resources) for a list of resources registered in a module's container.
You can resolve those resources in the module's services and loaders.
For example:
```ts title="Example Service"
import { Logger } from "@medusajs/framework/types"
type InjectedDependencies = {
logger: Logger
}
export default class BlogModuleService {
protected logger_: Logger
constructor({ logger }: InjectedDependencies) {
this.logger_ = logger
this.logger_.info("[BlogModuleService]: Hello World!")
}
// ...
}
```
In this example, the `BlogModuleService` class resolves the `Logger` service from the module's container and uses it to log a message.
### Using Framework Tools in Workflows
Some Framework APIs and tools are not registered in the module's container. For example, [Query](../../module-links/query/page.mdx) is only registered in the Medusa container.
You should, instead, build workflows that use these APIs and tools along with your module's service.
For example, you can create a workflow that retrieves data using Query, then pass the data to your module's service to perform some action.
```ts title="Example Workflow"
import { createWorkflow, createStep } from "@medusajs/framework/workflows-sdk"
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
const createBrandsInCmsStep = createStep(
"create-brands-in-cms",
async ({ brands }, { container }) => {
const cmsModuleService = container.resolve(
"cms"
)
const cmsBrands = await cmsModuleService.createBrands(brands)
return new StepResponse(cmsBrands, cmsBrands)
},
async (brands, { container }) => {
const cmsModuleService = container.resolve(
"cms"
)
await cmsModuleService.deleteBrands(
brands.map((brand) => brand.id)
)
}
)
const syncBrandsWorkflow = createWorkflow(
"sync-brands",
() => {
const { data: brands } = useQueryGraphStep({
entity: "brand",
fields: [
"*",
"products.*",
],
})
createBrandsInCmsStep({ brands })
}
)
```
In this example, you use the `useQueryGraphStep` to retrieve brands with their products, then pass the brands to the `createBrandsInCmsStep` step.
In the `createBrandsInCmsStep`, you resolve the CMS Module's service from the module's container and use it to create the brands in the third-party system. You pass the brands you retrieved using Query to the module's service.
### Injecting Dependencies to Module
Some cases still require you to access external resources, mainly [Infrastructure Modules](!resources!/infrastructure-modules) or Framework tools, in your module.
For example, you may need the [Event Module](!resources!/infrastructure-modules/event) to emit events from your module's service.
In those cases, you can inject the dependencies to your module's service in `medusa-config.ts` using the `dependencies` property of the module's configuration.
<Note type="warning">
Use this approach only when absolutely necessary, where workflows aren't sufficient for your use case. By injecting dependencies, you risk breaking your module if the dependency isn't provided, or if the dependency's API changes.
</Note>
For example:
```ts title="medusa-config.ts"
import { Modules } from "@medusajs/framework/utils"
module.exports = defineConfig({
// ...
modules: [
{
resolve: "./src/modules/blog",
dependencies: [
Modules.EVENT_BUS,
],
},
],
})
```
In this example, you inject the Event Module's service to your module's container.
<Note>
Only the main service will be injected into the module's container.
</Note>
You can then use the Event Module's service in your module's service:
```ts title="Example Service"
class BlogModuleService {
protected eventBusService_: AbstractEventBusModuleService
constructor({ event_bus }) {
this.eventBusService_ = event_bus
}
performAction() {
// TODO perform action
this.eventBusService_.emit({
name: "custom.event",
data: {
id: "123",
// other data payload
},
})
}
}
```

View File

@@ -102,12 +102,18 @@ This indicates that the loader in the `hello` module ran and logged this message
## When are Loaders Executed?
### Loaders Executed on Application Startup
When you start the Medusa application, it executes the loaders of all modules in their registration order.
A loader is executed before the module's main service is instantiated. So, you can use loaders to register in the module's container resources that you want to use in the module's service. For example, you can register a database connection.
Loaders are also useful to only load a module if a certain condition is met. For example, if you try to connect to a database in a loader but the connection fails, you can throw an error in the loader to prevent the module from being loaded. This is useful if your module depends on an external service to work.
### Loaders Executed with Migrations
Loaders are also executed when you run [migrations](../../data-models/write-migration/page.mdx). This can be useful if you need to run some task before the migrations, or you want to migrate some data to an integrated third-party system as part of the migration process.
---
## Example: Register Custom MongoDB Connection

View File

@@ -16,9 +16,9 @@ export const generatedEditDates = {
"app/learn/fundamentals/api-routes/page.mdx": "2024-12-04T11:02:57.134Z",
"app/learn/fundamentals/modules/modules-directory-structure/page.mdx": "2024-12-09T10:32:46.839Z",
"app/learn/fundamentals/events-and-subscribers/page.mdx": "2025-05-16T13:40:16.111Z",
"app/learn/fundamentals/modules/container/page.mdx": "2025-03-18T15:10:03.574Z",
"app/learn/fundamentals/modules/container/page.mdx": "2025-05-21T15:07:12.059Z",
"app/learn/fundamentals/workflows/execute-another-workflow/page.mdx": "2024-12-09T15:56:22.895Z",
"app/learn/fundamentals/modules/loaders/page.mdx": "2025-04-22T15:32:00.430Z",
"app/learn/fundamentals/modules/loaders/page.mdx": "2025-05-21T15:15:35.271Z",
"app/learn/fundamentals/admin/widgets/page.mdx": "2024-12-09T16:43:24.260Z",
"app/learn/fundamentals/data-models/page.mdx": "2025-03-18T07:55:56.252Z",
"app/learn/fundamentals/modules/remote-link/page.mdx": "2024-09-30T08:43:53.127Z",
@@ -47,7 +47,7 @@ export const generatedEditDates = {
"app/learn/fundamentals/api-routes/cors/page.mdx": "2025-03-11T08:54:26.281Z",
"app/learn/fundamentals/admin/ui-routes/page.mdx": "2025-02-24T09:35:11.752Z",
"app/learn/fundamentals/api-routes/middlewares/page.mdx": "2025-05-09T07:56:04.125Z",
"app/learn/fundamentals/modules/isolation/page.mdx": "2024-12-09T11:02:38.087Z",
"app/learn/fundamentals/modules/isolation/page.mdx": "2025-05-21T15:10:15.499Z",
"app/learn/fundamentals/data-models/index/page.mdx": "2025-03-18T07:59:07.798Z",
"app/learn/fundamentals/custom-cli-scripts/page.mdx": "2024-10-23T07:08:55.898Z",
"app/learn/debugging-and-testing/testing-tools/integration-tests/api-routes/page.mdx": "2025-03-18T15:06:27.864Z",

File diff suppressed because it is too large Load Diff