3.3.3. Module Isolation
In this chapter, you'll learn how modules are isolated, and what that means for your custom development.
- Modules can't access resources, such as services or data models, from other modules.
- Use Module Links to extend an existing module's data models, and Query to retrieve data across modules.
- Use workflows to build features that depend on functionalities from different modules.
How are Modules Isolated?#
A module is unaware of any resources other than its own, such as services or data models. This means it can't access these resources if they're implemented in another module.
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 chapter. This container includes the module's resources, such as services and data models, and some Framework resources that the Medusa application provides.
Why are Modules Isolated#
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.
- 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 Module Links. 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.
How to Use Services of Other Modules?#
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 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.
Example#
For example, consider you have two modules:
- A module that stores and manages brands in your application.
- A module that integrates a third-party Content Management System (CMS).
To sync brands from your application to the third-party system, create the following steps:
1const retrieveBrandsStep = createStep(2 "retrieve-brands",3 async (_, { container }) => {4 const brandModuleService = container.resolve(5 "brand"6 )7 8 const brands = await brandModuleService.listBrands()9 10 return new StepResponse(brands)11 }12)13 14const createBrandsInCmsStep = createStep(15 "create-brands-in-cms",16 async ({ brands }, { container }) => {17 const cmsModuleService = container.resolve(18 "cms"19 )20 21 const cmsBrands = await cmsModuleService.createBrands(brands)22 23 return new StepResponse(cmsBrands, cmsBrands)24 },25 async (brands, { container }) => {26 const cmsModuleService = container.resolve(27 "cms"28 )29 30 await cmsModuleService.deleteBrands(31 brands.map((brand) => brand.id)32 )33 }34)
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:
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. You can refer to the Module 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:
1import { Logger } from "@medusajs/framework/types"2 3type InjectedDependencies = {4 logger: Logger5}6 7export default class BlogModuleService {8 protected logger_: Logger9 10 constructor({ logger }: InjectedDependencies) {11 this.logger_ = logger12 13 this.logger_.info("[BlogModuleService]: Hello World!")14 }15 16 // ...17}
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 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.
1import { createWorkflow, createStep } from "@medusajs/framework/workflows-sdk"2import { useQueryGraphStep } from "@medusajs/medusa/core-flows"3 4const createBrandsInCmsStep = createStep(5 "create-brands-in-cms",6 async ({ brands }, { container }) => {7 const cmsModuleService = container.resolve(8 "cms"9 )10 11 const cmsBrands = await cmsModuleService.createBrands(brands)12 13 return new StepResponse(cmsBrands, cmsBrands)14 },15 async (brands, { container }) => {16 const cmsModuleService = container.resolve(17 "cms"18 )19 20 await cmsModuleService.deleteBrands(21 brands.map((brand) => brand.id)22 )23 }24)25 26const syncBrandsWorkflow = createWorkflow(27 "sync-brands",28 () => {29 const { data: brands } = useQueryGraphStep({30 entity: "brand",31 fields: [32 "*",33 "products.*",34 ],35 })36 37 createBrandsInCmsStep({ brands })38 }39)
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 or Framework tools, in your module. For example, you may need the Event Module 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.
For example:
In this example, you inject the Event Module's service to your module's container.
You can then use the Event Module's service in your module's service:
1class BlogModuleService {2 protected eventBusService_: AbstractEventBusModuleService3 4 constructor({ event_bus }) {5 this.eventBusService_ = event_bus6 }7 8 performAction() {9 // TODO perform action10 11 this.eventBusService_.emit({12 name: "custom.event",13 data: {14 id: "123",15 // other data payload16 },17 })18 }19}