docs: rename Architectural Modules to Infrastructure Modules (#12212)

* docs: rename Architectural Modules to Infrastructure Modules

* generate again
This commit is contained in:
Shahed Nasser
2025-04-17 13:20:43 +03:00
committed by GitHub
parent 8618e6ee38
commit eb73bdb478
149 changed files with 12165 additions and 12145 deletions
@@ -0,0 +1,262 @@
---
sidebar_label: "Create Event Module"
tags:
- event
- how to
- server
---
import { TypeList } from "docs-ui"
export const metadata = {
title: `How to Create an Event Module`,
}
# {metadata.title}
In this guide, youll learn how to create an Event Module.
## 1. Create Module Directory
Start by creating a new directory for your module. For example, `src/modules/my-event`.
---
## 2. Create the Event Service
Create the file `src/modules/my-event/service.ts` that holds the implementation of the event service.
The Event Module's main service must extend the `AbstractEventBusModuleService` class from the Medusa Framework:
```ts title="src/modules/my-event/service.ts"
import { AbstractEventBusModuleService } from "@medusajs/framework/utils"
import { Message } from "@medusajs/types"
class MyEventService extends AbstractEventBusModuleService {
async emit<T>(data: Message<T> | Message<T>[], options: Record<string, unknown>): Promise<void> {
throw new Error("Method not implemented.")
}
async releaseGroupedEvents(eventGroupId: string): Promise<void> {
throw new Error("Method not implemented.")
}
async clearGroupedEvents(eventGroupId: string): Promise<void> {
throw new Error("Method not implemented.")
}
}
export default MyEventService
```
The service implements the required methods based on the desired publish/subscribe logic.
### eventToSubscribersMap_ Property
The `AbstractEventBusModuleService` has a field `eventToSubscribersMap_`, which is a JavaScript Map. The map's keys are the event names, whereas the value of each key is an array of subscribed handler functions.
In your custom implementation, you can use this property to manage the subscribed handler functions:
```ts
const eventSubscribers =
this.eventToSubscribersMap_.get(eventName) || []
```
### emit Method
The `emit` method is used to push an event from the Medusa application into your messaging system. The subscribers to that event would then pick up the message and execute their asynchronous tasks.
An example implementation:
```ts title="src/modules/my-event/service.ts"
class MyEventService extends AbstractEventBusModuleService {
async emit<T>(data: Message<T> | Message<T>[], options: Record<string, unknown>): Promise<void> {
const events = Array.isArray(data) ? data : [data]
for (const event of events) {
console.log(`Received the event ${event.name} with data ${event.data}`)
// TODO push the event somewhere
}
}
// ...
}
```
The `emit` method receives the following parameters:
<TypeList
types={[
{
name: "data",
type: "`object or array of objects`",
description: "The emitted event(s).",
optional: false,
children: [
{
name: "name",
type: "`string`",
description: "The name of the emitted event.",
optional: false
},
{
name: "data",
type: "`object`",
description: "The data payload of the event.",
optional: false
},
{
name: "metadata",
type: "`object`",
description: "Additional details of the emitted event.",
optional: false,
children: [
{
name: "eventGroupId",
type: "string",
description: "A group ID that the event belongs to.",
optional: true
}
]
},
{
name: "options",
type: "`object`",
description: "Additional options relevant for the event service.",
optional: false
}
]
}
]}
/>
### releaseGroupedEvents Method
Grouped events are useful when you have distributed transactions where you need to explicitly group, release, and clear events upon lifecycle transaction events.
If your Event Module supports grouped events, this method is used to emit all events in a group, then clear that group.
For example:
```ts title="src/modules/my-event/service.ts"
class MyEventService extends AbstractEventBusModuleService {
protected groupedEventsMap_: Map<string, Message[]>
constructor() {
// @ts-ignore
super(...arguments)
this.groupedEventsMap_ = new Map()
}
async releaseGroupedEvents(eventGroupId: string): Promise<void> {
const groupedEvents = this.groupedEventsMap_.get(eventGroupId) || []
for (const event of groupedEvents) {
const { options, ...eventBody } = event
// TODO emit event
}
await this.clearGroupedEvents(eventGroupId)
}
// ...
}
```
The `releaseGroupedEvents` receives the group ID as a parameter.
In the example above, you add a `groupedEventsMap_` property to store grouped events. Then, in the method, you emit the events in the group, then clear the grouped events using the `clearGroupedEvents` which you'll learn about next.
To add events to the grouped events map, you can do it in the `emit` method:
```ts title="src/modules/my-event/service.ts"
class MyEventService extends AbstractEventBusModuleService {
// ...
async emit<T>(data: Message<T> | Message<T>[], options: Record<string, unknown>): Promise<void> {
const events = Array.isArray(data) ? data : [data]
for (const event of events) {
console.log(`Received the event ${event.name} with data ${event.data}`)
if (event.metadata.eventGroupId) {
const groupedEvents = this.groupedEventsMap_.get(
event.metadata.eventGroupId
) || []
groupedEvents.push(event)
this.groupedEventsMap_.set(event.metadata.eventGroupId, groupedEvents)
continue
}
// TODO push the event somewhere
}
}
}
```
### clearGroupedEvents Method
If your Event Module supports grouped events, this method is used to remove the events of a group.
For example:
```ts title="src/modules/my-event/service.ts"
class MyEventService extends AbstractEventBusModuleService {
// from previous section
protected groupedEventsMap_: Map<string, Message[]>
async clearGroupedEvents(eventGroupId: string): Promise<void> {
this.groupedEventsMap_.delete(eventGroupId)
}
// ...
}
```
The method accepts the group's name as a parameter.
In the method, you delete the group from the `groupedEventsMap_` property (added in the previous section), deleting the stored events of it as well.
---
## 3. Create Module Definition File
Create the file `src/modules/my-event/index.ts` with the following content:
```ts title="src/modules/my-event/index.ts"
import MyEventService from "./service"
import { Module } from "@medusajs/framework/utils"
export default Module("my-event", {
service: MyEventService,
})
```
This exports the module's definition, indicating that the `MyEventService` is the main service of the module.
---
## 4. Use Module
To use your Event Module, add it to the `modules` object exported as part of the configurations in `medusa-config.ts`. An Event Module is added under the `eventBus` key.
For example:
```ts title="medusa-config.ts"
import { Modules } from "@medusajs/framework/utils"
// ...
module.exports = defineConfig({
// ...
modules: [
{
resolve: "./src/modules/my-event",
options: {
// any options
},
},
],
})
```
@@ -0,0 +1,54 @@
export const metadata = {
title: `Local Event Module`,
}
# {metadata.title}
The Local Event Module uses Node EventEmitter to implement Medusa's pub/sub events system. The Node EventEmitter is limited to a single process environment.
This module is useful for development and testing, but its not recommended to be used in production.
For production, its recommended to use modules like [Redis Event Bus Module](../redis/page.mdx).
---
## Register the Local Event Module
<Note>
The Local Event Module is registered by default in your application.
</Note>
Add the module into the `modules` property of the exported object in `medusa-config.ts`:
```ts title="medusa-config.ts"
import { Modules } from "@medusajs/framework/utils"
// ...
module.exports = defineConfig({
// ...
modules: [
{
resolve: "@medusajs/medusa/event-bus-local",
},
],
})
```
---
## Test the Module
To test the module, start the Medusa application:
```bash npm2yarn
npm run dev
```
You'll see the following message in the terminal's logs:
```bash noCopy noReport
Local Event Bus installed. This is not recommended for production.
```
@@ -0,0 +1,98 @@
import { CardList } from "docs-ui"
export const metadata = {
title: `Event Module`,
}
# {metadata.title}
In this document, you'll learn what an Event Module is and how to use it in your Medusa application.
## What is an Event Module?
An Event Module implements the underlying publish/subscribe system that handles queueing events, emitting them, and executing their subscribers.
This makes the event architecture customizable, as you can either choose one of Medusas event modules or create your own.
<Note>
Learn more about Medusa's event systems in the [Events and Subscribers documentation](!docs!/learn/fundamentals/events-and-subscribers).
</Note>
### Default Event Module
By default, Medusa uses the [Local Event Module](./local/page.mdx). This module uses Nodes EventEmitter to implement the publish/subscribe system. While this is suitable for development, it's recommended to use other Event Modules, such as the [Redis Event Module](./redis/page.mdx), for production. You can also [Create an Event Module](./create/page.mdx).
---
## How to Use the Event Module?
You can use the registered Event Module as part of the [workflows](!docs!/learn/fundamentals/workflows) you build for your custom features. A workflow is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
Medusa provides the helper step [emitEventStep](/references/helper-steps/emitEventStep) that you can use in your workflow. You can also resolve the Event Module's service in a step of your workflow and use its methods to emit events.
For example:
```ts
import { Modules } from "@medusajs/framework/utils"
import {
createStep,
createWorkflow,
} from "@medusajs/framework/workflows-sdk"
const step1 = createStep(
"step-1",
async ({}, { container }) => {
const eventModuleService = container.resolve(
Modules.EVENT
)
await eventModuleService.emit({
name: "custom.event",
data: {
id: "123",
// other data payload
},
})
}
)
export const workflow = createWorkflow(
"workflow-1",
() => {
step1()
}
)
```
In the example above, you create a workflow that has a step. In the step, you resolve the service of the Event Module from the [Medusa container](!docs!/learn/fundamentals/medusa-container).
Then, you use the `emit` method of the Event Module to emit an event with the name `"custom.event"` and the data payload `{ id: "123" }`.
---
## List of Event Modules
Medusa provides the following Event Modules. You can use one of them, or [Create an Event Module](./create/page.mdx).
<CardList
items={[
{
title: "Local",
href: "/infrastructure-modules/event/local",
badge: {
variant: "neutral",
children: "For Development"
}
},
{
title: "Redis",
href: "/infrastructure-modules/event/redis",
badge: {
variant: "green",
children: "For Production"
}
}
]}
/>
@@ -0,0 +1,217 @@
import { Table, Prerequisites } from "docs-ui"
export const metadata = {
title: `Redis Event Module`,
}
# {metadata.title}
The Redis Event Module uses Redis to implement Medusa's pub/sub events system.
It's powered by BullMQ and `io-redis`. BullMQ is responsible for the message queue and worker, and `io-redis` is the underlying Redis client that BullMQ connects to for events storage.
In production, it's recommended to use this module.
---
## Register the Redis Event Module
<Prerequisites items={[
{
text: "Redis installed and Redis server running",
link: "https://redis.io/docs/getting-started/installation/"
}
]} />
Add the module into the `modules` property of the exported object in `medusa-config.ts`:
export const highlights = [
["11", "redisUrl", "The Redis connection URL."]
]
```ts title="medusa-config.ts"
import { Modules } from "@medusajs/framework/utils"
// ...
module.exports = defineConfig({
// ...
modules: [
{
resolve: "@medusajs/medusa/event-bus-redis",
options: {
redisUrl: process.env.EVENTS_REDIS_URL,
},
},
],
})
```
### Environment Variables
Make sure to add the following environment variables:
```bash
EVENTS_REDIS_URL=<YOUR_REDIS_URL>
```
### Redis Event Module Options
<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Option</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
<Table.HeaderCell>Required</Table.HeaderCell>
<Table.HeaderCell>Default</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
`redisUrl`
</Table.Cell>
<Table.Cell>
A string indicating the Redis connection URL.
</Table.Cell>
<Table.Cell>
Yes
</Table.Cell>
<Table.Cell>
\-
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`redisOptions`
</Table.Cell>
<Table.Cell>
An object of Redis options. Refer to the [Redis API Reference](https://redis.github.io/ioredis/index.html#RedisOptions) for details on accepted properties.
</Table.Cell>
<Table.Cell>
No
</Table.Cell>
<Table.Cell>
\-
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`queueName`
</Table.Cell>
<Table.Cell>
A string indicating BullMQ's queue name.
</Table.Cell>
<Table.Cell>
No
</Table.Cell>
<Table.Cell>
`events-queue`
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`queueOptions`
</Table.Cell>
<Table.Cell>
An object of options to pass to the BullMQ constructor. Refer to [BullMQ's API reference](https://api.docs.bullmq.io/interfaces/v3.QueueOptions.html) for allowed properties.
</Table.Cell>
<Table.Cell>
No
</Table.Cell>
<Table.Cell>
\-
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`workerOptions`
</Table.Cell>
<Table.Cell>
An object of options to pass to the BullMQ Worker constructor. Refer to [BullMQ's API reference](https://api.docs.bullmq.io/interfaces/v3.WorkerOptions.html) for allowed properties.
</Table.Cell>
<Table.Cell>
No
</Table.Cell>
<Table.Cell>
\-
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`jobOptions`
</Table.Cell>
<Table.Cell>
An object of options to pass to jobs added to the BullMQ queue. Refer to [BullMQ's API reference](https://api.docs.bullmq.io/modules/v3.html#BulkJobOptions) for allowed properties.
</Table.Cell>
<Table.Cell>
No
</Table.Cell>
<Table.Cell>
\-
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
## Test the Module
To test the module, start the Medusa application:
```bash npm2yarn
npm run dev
```
You'll see the following message in the terminal's logs:
```bash noCopy noReport
Connection to Redis in module 'event-redis' established
```