docs: add documentation for v1.8 (#3669)

This commit is contained in:
Shahed Nasser
2023-04-03 13:50:59 +02:00
committed by GitHub
parent 0cca13779d
commit c6bfad14d8
123 changed files with 7610 additions and 2697 deletions
@@ -0,0 +1,260 @@
---
description: 'In this document, youll learn how to create an events module, then test and publish the module as an NPM package.'
addHowToData: true
---
# Create Events Module
In this document, youll learn how to create an events module.
## Overview
Medusa provides ready-made modules for events, including the local and Redis modules. If you prefer another technology used for managing events, you can build a module locally and use it in your Medusa backend. You can also publish to NPM and reuse it across multiple Medusa backend instances.
In this document, youll learn how to build your own Medusa events module, mainly focusing on creating the event bus service and the available methods you need to implement within your module.
---
## (Optional) Step 0: Prepare Module Directory
Before you start implementing your module, it's recommended to prepare the directory or project holding your custom implementation.
You can refer to the [Project Preparation step in the Create Module documentation](../modules/create.mdx#optional-step-0-project-preparation) to learn how to do that.
---
## Step 1: Create the Service
Create the file `services/event-bus-custom.ts` which will hold your event bus service. Note that the name of the file is recommended to be in the format `event-bus-<service_name>` where `<service_name>` is the name of the service youre integrating. For example, `event-bus-redis`.
Add the following content to the file:
```ts title=services/event-bus-custom.ts
import { EmitData, EventBusTypes } from "@medusajs/types"
import { AbstractEventBusModuleService } from "@medusajs/utils"
class CustomEventBus extends AbstractEventBusModuleService {
async emit<T>(
eventName: string,
data: T,
options: Record<string, unknown>
): Promise<void>;
async emit<T>(data: EmitData<T>[]): Promise<void>;
async emit(
eventName: unknown,
data?: unknown,
options?: unknown
): Promise<void> {
throw new Error("Method not implemented.")
}
}
export default CustomEventBus
```
This creates the class `CustomEventBus` that implements the `AbstractEventBusModuleService` class imported from `@medusajs/utils`. Feel free to rename the class to whats relevant for your event bus service.
In the class you must implement the `emit` method. You can optionally implement the `subscribe` and `unsubscribe` methods.
---
## Step 2: Implement Methods
### Note About the eventToSubscribersMap Property
The `AbstractEventBusModuleService` implements two methods for handling subscription: `subscribe` and `unsubscribe`. In these methods, the subscribed handler methods are managed within a class property `eventToSubscribersMap`, which is a JavaScript Map. They map keys are the event names, whereas the value of each key is an array of subscribed handler methods.
In your custom implementation, you can use this property to manage the subscribed handler methods. For example, you can get the subscribers of a method using the `get` method of the map:
```ts
const eventSubscribers =
this.eventToSubscribersMap.get(eventName) || []
```
Alternatively, you can implement custom logic for the `subscribe` and `unsubscribe` events, which is explained later in this guide.
### constructor
The `constructor` method of a service allows you to prepare any third-party client or service necessary to be used in other methods. It also allows you to get access to the modules options which are typically defined in `medusa-config.js`, and to other services and resources in the Medusa backend using [dependency injection](../fundamentals/dependency-injection.md).
Heres an example of how you can use the `constructor` to store the options of your module:
<!-- eslint-disable prefer-rest-params -->
```ts title=services/event-bus-custom.ts
class CustomEventBus extends AbstractEventBusModuleService {
protected readonly moduleOptions: Record<string, any>
constructor({
// inject resources from the Medusa backend
// for example, you can inject the logger
logger,
}, options) {
super(...arguments)
this.moduleOptions = options
}
// ...
}
```
### emit
The `emit` method is used to push an event from Medusa into your messaging system. Typically, the subscribers to that event would then pick up the message and execute their asynchronous tasks.
The `emit` method has two different signatures:
1. The first signature accepts three parameters. The first parameter is `eventName` being a required string indicating the name of the event to trigger. The second parameter is `data` being the optional data to send to subscribers of that event. The third optional parameter `options` which can be used to pass options specific to the event bus.
2. The second signature accepts one parameter, which is an array of objects having three properties: `eventName`, `data`, and `options`. These are the same as the parameters that can be passed in the first signature. This signature allows emitting more than one event.
The `options` parameter depends on the event bus integrating. For example, the Redis event bus accept the following options:
```ts title=services/event-bus-custom.ts
type JobData<T> = {
eventName: string
data: T
completedSubscriberIds?: string[] | undefined
}
```
You can implement your method in a way that supports both signatures by checking the type of the first input. For example:
```ts title=services/event-bus-custom.ts
class CustomEventBus extends AbstractEventBusModuleService {
// ...
async emit<T>(
eventName: string,
data: T,
options: Record<string, unknown>
): Promise<void>;
async emit<T>(data: EmitData<T>[]): Promise<void>;
async emit<T>(
eventOrData: string | EmitData<T>[],
data?: T,
options: Record<string, unknown> = {}
): Promise<void> {
const isBulkEmit = Array.isArray(eventOrData)
// emit event
}
}
```
### (optional) subscribe
As mentioned earlier, this method is already implemented in the `AbstractEventBusModuleService` class. This section explains how you can implement your custom subscribe logic if necessary.
The `subscribe` method attaches a handler method to the specified event, which is run when the event is triggered. It is typically used inside a subscriber class.
The `subscribe` method accepts three parameters:
1. The first parameter `eventName` is a required string. It indicates which event the handler method is subscribing to.
2. The second parameter `subscriber` is a required function that performs an action when the event is triggered.
3. The third parameter `context` is an optional object that has the property `subscriberId`. Subscriber IDs are useful to differentiate between handler methods when retrying a failed method. Its also useful for unsubscribing an event handler. Note that if you must implement the mechanism around assigning IDs to subscribers when you override the `subscribe` method.
The implementation of this method depends on the service youre using for the event bus:
```ts title=services/event-bus-custom.ts
class CustomEventBus extends AbstractEventBusModuleService {
// ...
subscribe(
eventName: string | symbol,
subscriber: EventBusTypes.Subscriber,
context?: EventBusTypes.SubscriberContext): this {
// TODO implement subscription
}
}
```
### (optional) unsubscribe
As mentioned earlier, this method is already implemented in the `AbstractEventBusModuleService` class. This section explains how you can implement your custom unsubscribe logic if necessary.
The `unsubscribe` method is used to unsubscribe a handler method from an event.
The `unsubscribe` method accepts three parameters:
1. The first parameter `eventName` is a required string. It indicates which event the handler method is unsubscribing from.
2. The second parameter `subscriber` is a required function that was initially subscribed to the event.
3. The third parameter `context` is an optional object that has the property `subscriberId`. It can be used to specify the ID of the subscriber to unsubscribe.
The implementation of this method depends on the service youre using for the event bus:
```ts title=services/event-bus-custom.ts
class CustomEventBus extends AbstractEventBusModuleService {
// ...
unsubscribe(
eventName: string | symbol,
subscriber: EventBusTypes.Subscriber,
context?: EventBusTypes.SubscriberContext): this {
// TODO implement subscription
}
}
```
---
## Step 3: Export the Service
After implementing the event bus service, you must export it so that the Medusa backend can use it.
Create the file `index.ts` with the following content:
```ts title=services/event-bus-custom.ts
import { ModuleExports } from "@medusajs/modules-sdk"
import { CustomEventBus } from "./services"
const service = CustomEventBus
const moduleDefinition: ModuleExports = {
service,
}
export default moduleDefinition
```
This exports a module definition, which requires at least a `service`. If you named your service something other than `CustomEventBus`, make sure to replace it with that.
You can learn more about what other properties you can export in your module definition in the [Create a Module documentation](../modules/create.mdx#step-2-export-module).
---
## Step 4: Test your Module
You can test your module in the Medusa backend by referencing it in the configurations.
To do that, add the module to the exported configuration in `medusa-config.js` as follows:
```js title=medusa-config.js
module.exports = {
// ...
modules: {
// ...
cacheService: {
resolve: "path/to/custom-module",
options: {
// any necessary options
},
},
},
}
```
Make sure to replace the `path/to/custom-module` with a relative path from your Medusa backend to your module. You can learn more about module reference in the [Create Module documentation](../modules/create.mdx#module-reference).
You can also add any necessary options to the module.
Then, to test the module, run the Medusa backend which also runs your module:
```bash npm2yarn
npm run start
```
---
## (Optional) Step 5: Publish your Module
You can publish your events module to NPM. This can be useful if you want to reuse your module across Medusa backend instances, or want to allow other developers to use it.
You can refer to the [Publish Module documentation](../modules/publish.md) to learn how to publish your module.
@@ -7,14 +7,6 @@ addHowToData: true
In this document, youll learn how to create a [Subscriber](./subscribers.mdx) in Medusa that listens to events to perform an action.
## Prerequisites
Medusa's event system works by pushing data to a Queue that each handler then gets notified of. The queuing system is based on Redis, so it's required for subscribers to work.
You can learn how to [install Redis](../backend/prepare-environment.mdx#redis) and [configure it with Medusa](../backend/configurations.md#redis) before you get started.
---
## Implementation
A subscriber is a TypeScript or JavaScript file that is created under `src/subscribers`. Its file name, by convension, should be the class name of the subscriber without the word `Subscriber`. For example, if the subscriber is `HelloSubscriber`, the file name should be `hello.ts`.
@@ -41,12 +33,25 @@ export default OrderNotifierSubscriber
This subscriber registers the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed. It receives the order ID in the `data` parameter. You can then use the orders details to perform any kind of task you need.
:::note
:::tip
The `data` object won't contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`.
For the `order.placed` event, the `data` object won't contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`.
:::
### Subscriber ID
The `subscribe` method of the `eventBusService` accepts a third optional parameter which is a context object. This object has a property `subscriberId` with its value being a string. This ID is useful when there is more than one handler method attached to a single event or if you have multiple Medusa backends running. This allows the events bus service to differentiate between handler methods when retrying a failed one.
If a subscriber ID is not passed on subscription, all handler methods are run again. This can lead to data inconsistencies or general unwanted behavior in your system. On the other hand, if you want all handler methods to run again when one of them fails, you can omit passing a subscriber ID.
An example of using the subscribe method with the third parameter:
```ts
eventBusService.subscribe("order.placed", this.handleOrder, {
subscriberId: "my-unique-subscriber",
})
```
---
## Using Services in Subscribers
@@ -55,7 +60,7 @@ You can access any service through the dependencies injected to your subscriber
For example:
```ts
```ts title=src/subscribers/orderNotifier.ts
class OrderNotifierSubscriber {
constructor({ productService, eventBusService }) {
this.productService = productService
@@ -71,7 +76,7 @@ class OrderNotifierSubscriber {
You can then use `this.productService` anywhere in your subscribers methods. For example:
```ts
```ts title=src/subscribers/orderNotifier.ts
class OrderNotifierSubscriber {
// ...
handleOrder = async (data) => {
+383 -2
View File
@@ -826,6 +826,193 @@ Object of the following format:
---
## Inventory Item Events
This section holds all events related to inventory items.
<table class="reference-table">
<thead>
<tr>
<th>
Event Name
</th>
<th>
Description
</th>
<th>
Event Data Payload
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
`inventory-item.created`
</td>
<td>
Triggered when an inventory item is created.
</td>
<td>
Object of the following format:
```js noReport noCopy
{
id // string ID of the inventory item
}
```
</td>
</tr>
<tr>
<td>
`inventory-item.updated`
</td>
<td>
Triggered when an inventory item is updated.
</td>
<td>
Object of the following format:
```js noReport noCopy
{
id // string ID of the inventory item
}
```
</td>
</tr>
<tr>
<td>
`inventory-item.deleted`
</td>
<td>
Triggered when an inventory item is deleted.
</td>
<td>
Object of the following format:
```js noReport noCopy
{
id // string ID of the inventory item
}
```
</td>
</tr>
</tbody>
</table>
---
## Inventory Level Events
This section holds all events related to inventory levels.
<table class="reference-table">
<thead>
<tr>
<th>
Event Name
</th>
<th>
Description
</th>
<th>
Event Data Payload
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
`inventory-level.created`
</td>
<td>
Triggered when an inventory level is created.
</td>
<td>
Object of the following format:
```js noReport noCopy
{
id // string ID of the inventory level
}
```
</td>
</tr>
<tr>
<td>
`inventory-level.updated`
</td>
<td>
Triggered when an inventory level is updated.
</td>
<td>
Object of the following format:
```js noReport noCopy
{
id // string ID of the inventory level
}
```
</td>
</tr>
<tr>
<td>
`inventory-level.deleted`
</td>
<td>
Triggered when an inventory level is deleted, which can be done either directly using its ID or based on the ID of a location. The returned ID depends on how the inventory level was deleted.
</td>
<td>
Object of the following format:
```js noReport noCopy
{
id // (optional) string ID of the inventory level, available if it was deleted directly
location_id // (optional) string ID of location, available if level was deleted by location ID
}
```
</td>
</tr>
</tbody>
</table>
---
## Invite Events
This section holds all events related to invites.
@@ -1817,7 +2004,7 @@ Triggered when the capturing of a payment fails.
The entire payment passed as an object. You can refer to the [Payment entity](../../references/entities/classes/Payment.md) for an idea of what fields to expect.
In addition, an error object is passed within the same object as the Payment provider:
In addition, an error object is passed within the same object as the Payment Processor:
```js noReport noCopy
{
@@ -2076,6 +2263,12 @@ Object of the following format:
This section holds all events related to product categories.
:::note
Product Category feature is currently in beta mode and guarded by a feature flag. You can learn how to enable it in the [Product Categories documentation](../../modules/products/categories.md).
:::
<table class="reference-table">
<thead>
<tr>
@@ -2438,6 +2631,101 @@ Object of the following format:
---
## Reservation Item Events
This section holds all events related to reservation items.
<table class="reference-table">
<thead>
<tr>
<th>
Event Name
</th>
<th>
Description
</th>
<th>
Event Data Payload
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
`reservation-item.created`
</td>
<td>
Triggered when a reservation item is created.
</td>
<td>
Object of the following format:
```js noReport noCopy
{
id // string ID of the reservation item
}
```
</td>
</tr>
<tr>
<td>
`reservation-item.updated`
</td>
<td>
Triggered when an reservation item is updated.
</td>
<td>
Object of the following format:
```js noReport noCopy
{
id // string ID of the reservation item
}
```
</td>
</tr>
<tr>
<td>
`reservation-item.deleted`
</td>
<td>
Triggered when a reservation item is deleted, which can be done either directly using its ID or based on the ID of a location or a line item. The returned ID depends on how the reservation item was deleted.
</td>
<td>
Object of the following format:
```js noReport noCopy
{
id // (optional) string ID of the reservation item, available if it was deleted directly
location_id // (optional) string ID of location, available if item was deleted by location ID
line_item_id // (optional) string ID of line item, available if reservation item was deleted by line item ID
}
```
</td>
</tr>
</tbody>
</table>
---
## Sales Channel Events
This section holds all events related to sales channels.
@@ -2533,6 +2821,99 @@ Object of the following format:
---
## Stock Location Events
This section holds all events related to stock locations.
<table class="reference-table">
<thead>
<tr>
<th>
Event Name
</th>
<th>
Description
</th>
<th>
Event Data Payload
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
`stock-location.created`
</td>
<td>
Triggered when a stock location is created.
</td>
<td>
Object of the following format:
```js noReport noCopy
{
id // string ID of the stock location
}
```
</td>
</tr>
<tr>
<td>
`stock-location.updated`
</td>
<td>
Triggered when an stock location is updated.
</td>
<td>
Object of the following format:
```js noReport noCopy
{
id // string ID of the stock location
}
```
</td>
</tr>
<tr>
<td>
`stock-location.deleted`
</td>
<td>
Triggered when a stock location is deleted.
</td>
<td>
Object of the following format:
```js noReport noCopy
{
id // string ID of the stock location
}
```
</td>
</tr>
</tbody>
</table>
---
## Swap Events
This section holds all events related to swaps.
@@ -2958,6 +3339,6 @@ Object of the following format:
## See Also
- [Events architecture overview](./index.md)
- [Events overview](./index.mdx)
- [Use services in subscribers](./create-subscriber.md#using-services-in-subscribers)
- [Create a notification provider](../notification/overview.mdx)
-227
View File
@@ -1,227 +0,0 @@
---
description: 'Learn how the events system is implemented in Medusa. It is built on a publish-subscribe architecture. The Medusa core publishes events when certain actions take place.'
---
# Events Architecture
In this document, you'll learn how the events system is implemented in Medusa.
## Overview
The events system in Medusa is built on a publish/subscribe architecture. The Medusa core publishes events when certain actions take place.
Those events can be subscribed to using subscribers. When you subscribe to an event, you can perform a task asynchronusly every time the event is triggered.
:::info
You can learn more about subscribers and their use cases in the [Subscribers](./subscribers.mdx) documentation.
:::
---
## Publishing and Subscribing
The `EventBusService` is responsible for publishing and processing events.
:::note
The current implementation of the `EventBusService` is powered by Redis. However, an upcoming version of Medusa introduces an event bus module. This will allow you to use any publishing and subscribing provider. That will not change the general purpose and flow of the `EventBusService`.
:::
The `EventBusService` exposes two methods in its public API for event processing; `emit` and `subscribe`.
### emit
The `emit` method accepts as a first parameter the event name. It adds it to a Bull queue (powered by Redis) as a job, and processes it asynchronously.
The second parameter contains any data that should be emitted with the event. Subscribers that handle the event will receive that data as a method parameter.
The third parameter is an options object. It accepts options related to the number of retries if a subscriber handling the event fails, the delay time, and more. The options are explained in a [later section](#retrying-handlers)
The `emit` method has the following signature:
```ts
export default class EventBusService {
// ...
async emit<T>(
eventName: string,
data: T,
options: Record<string, unknown> &
EmitOptions = { attempts: 1 }
): Promise<StagedJob | void>
}
```
Here's an example of how you can emit an event using the `EventBusService`:
```ts
eventBusService.emit(
"product.created",
{ id: "prod_..." },
{ attempts: 2 }
)
```
The `EventBusService` emits the event `product.created` by passing the event name as a first argument. An object is passed as a second argument which is the data passed to the event handler methods in subscribers. This object contains the ID of the product.
Options are passed in the third argument. The `attempt` property specifies how many times the subscriber should be retried if it fails (by default it's one).
### subscribe
The `subscribe` method will attach a handler method to the specified event, which is run when the event is triggered. It is usually used insde a subscriber class.
The `subscribe` method accepts the event name as the first parameter. This is the event that the handler method will attach to.
The second parameter is the handler method that will be triggered when the event is emitted.
The third parameter is an optional `context` parameter. It allows you to configure the ID of the handler method.
The `subscribe` method has the following signature:
```ts
export default class EventBusService {
// ...
subscribe(
event: string | symbol,
subscriber: Subscriber,
context?: SubscriberContext
): this
}
```
Here's an example of how you can subscribe to an event using the `EventBusService`:
```ts title=src/subscribers/my.ts
import { EventBusService } from "@medusajs/medusa"
class MySubscriber {
constructor({
eventBusService: EventBusService,
}) {
eventBusService.subscribe("product.created", (data) => {
// TODO handle event
console.log(data.id)
})
}
}
```
In the constructor of a subscriber, you use the `EventBusService` to subscribe to the event `product.created`. In the handler method, you can perform a task every time the product is created. Notice how the handler method accepts the `data` as a parameter as explain in the previous section.
:::note
You can learn more about how to create a subscriber in [this documentation](./create-subscriber.md)
:::
---
## Processing Events
In the `EventBusService` service, the `worker_` method defines the logic run for each event emitted into the queue.
By default, all handler methods to that event are retrieved and, for each of the them, the stored data provided as a second parameter in `emit` is passed as an argument.
---
## Retrying Handlers
A handler method might fail to process an event. This could happen because it communicates with a third party service currently down or due to an error in its logic.
In some cases, you might want to retry those failed handlers.
As briefly explained earlier, you can pass options when emitting an event as a third argument that are used to configure how the queue worker processes your job. If you pass `attempts` upon emitting the event, the processing of a handler method is retried when it fails.
Aside from `attempts`, there are other options to futher configure the retry mechanism:
```ts
type EmitOptions = {
delay?: number
attempts: number
backoff?: {
type: "fixed" | "exponential"
delay: number
}
}
```
Here's what each of these options mean:
- `delay`: delay the triggering of the handler methods by a number of milliseconds.
- `attempts`: the number of times a subscriber handler should be retried when it fails.
- `backoff`: the wait time between each retry
### Note on Subscriber IDs
If you have more than one handler methods attached to a single event, or if you have multiple backend instances running, you must pass a subscriber ID as a third parameter to the `subscribe` method. This allows the `EventBusService` to differentiate between handler methods when retrying a failed one.
If a subscriber ID is not passed on subscription, all handler methods are run again. This can lead to data inconsistencies or general unwanted behavior in your system.
On the other hand, if you want all handler methods to run again when one of them fails, you can omit passing a subscriber ID.
An example of passing a subscriber ID:
```ts title=src/subscribers/my.ts
import { EventBusService } from "@medusajs/medusa"
class MySubscriber {
constructor({
eventBusService: EventBusService,
}) {
eventBusService.subscribe(
"product.created",
(data) => {
// TODO handle event
console.log(data.id)
},
"my-unique-subscriber")
}
}
```
:::info
You can learn more about subscriber IDs in [Bull's documentation](https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md#queueadd).
:::
---
## Database transactions
<!-- vale docs.Acronyms = NO -->
Transactions in Medusa ensure atomicity, consistency, isolation, and durability, or ACID, guarantees for operations in the Medusa core.
<!-- vale docs.Acronyms = YES -->
In many cases, [services](../services/overview.mdx) typically update resources in the database and emit an event within a transactional operation. To ensure that these events don't cause data inconsistencies (for example, a plugin subscribes to an event to contact a third-party service, but the transaction fails) the concept of a staged job is introduced.
Instead of events being processed immediately, they're stored in the database as a staged job until they're ready. In other words, until the transaction has succeeded.
This rather complex logic is abstracted away from the consumers of the `EventBusService`, but here's an example of the flow when an API request is made:
1. API request starts.
2. Transaction is initiated.
3. Service layer performs some logic.
4. Events are emitted and stored in the database for eventual processing.
5. Transaction is committed.
6. API request ends.
7. Events in the database become visible.
To pull staged jobs from the database, a separate enqueuer polls the database every three seconds to discover new visible jobs. These jobs are then added to the queue and processed as described in the [Processing](#processing-events) section earlier.
:::info
This pattern is heavily inspired by the [Transactionally-staged Job Drain described in this blog post](https://brandur.org/job-drain).
:::
---
## See Also
- [Events reference](./events-list.md)
- [Create a subscriber](./create-subscriber.md)
+131
View File
@@ -0,0 +1,131 @@
---
description: 'Learn how the events system is implemented in Medusa. It is built on a publish-subscribe architecture. The Medusa core publishes events when certain actions take place.'
---
import DocCardList from '@theme/DocCardList';
import Icons from '@theme/Icon';
# Events
In this document, youll learn what events are and why theyre useful in Medusa.
## Overview
Events are used in Medusa to inform different parts of the commerce ecosystem that this event occurred. For example, when an order is placed, the `order.placed` event is triggered, which informs notification services like SendGrid to send a confirmation email to the customer.
The events system in Medusa is built on a publish/subscribe architecture. The Medusa core publish an event when an action takes place, and modules, plugins, or other forms of customizations can subscribe to that event. [Subscribers](./subscribers.mdx) can then perform a task asynchronously when the event is triggered.
Although the core implements the main logic behind the events system, youll need to use an event module that takes care of the publish/subscribe functionality such as subscribing and emitting events. Medusa provides modules that you can use both for development and production, including Redis and Local modules.
---
## Database Transactions
Transactions in Medusa ensure Atomicity, Consistency, Isolation, and Durability (ACID) guarantees for operations in the Medusa core.
In many cases, services typically update resources in the database and emit an event within a transactional operation. To ensure that these events don't cause data inconsistencies (for example, a plugin subscribes to an event to contact a third-party service, but the transaction fails) the concept of a staged job is introduced.
Instead of events being processed immediately, they're stored in the database as a staged job until they're ready. In other words, until the transaction has succeeded.
This rather complex logic is abstracted away from the consumers of the EventBusService, but here's an example of the flow when an API request is made:
- API request starts.
- Transaction is initiated.
- Service layer performs some logic.
- Events are emitted and stored in the database for eventual processing.
- Transaction is committed.
- API request ends.
- Events in the database become visible.
To pull staged jobs from the database, a separate enqueuer polls the database every three seconds to discover new visible jobs. These jobs are then added to the queue and processed as described in the Processing section earlier.
:::note
This pattern is heavily inspired by the Transactionally-staged Job Drain described in this blog post.
:::
---
## Emitting Events
You can emit events in Medusa using the `EventBusService`. For example:
```ts
this.eventBusService.emit("custom-event", {
// attach any data to the event
})
```
You can also emit more than one event:
```ts
this.eventBusService.emit([
{
eventName: "custom-event-1",
data: {
// attach any data to the event
},
},
{
eventName: "custom-event-2",
data: {
// attach any data to the event
},
},
])
```
---
## Available Modules
Medusas default starter project comes with the local event module (`@medusajs/event-bus-local`). For production environments, its recommended to use the Redis event module package (`@medusajs/event-bus-redis`) that you can install.
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/development/events/modules/redis',
label: 'Redis',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to install Redis events module in Medusa.'
}
},
{
type: 'link',
href: '/development/events/modules/local',
label: 'Local',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to install local events module in Medusa.'
}
},
]} />
---
## Custom Development
Developers can create custom event modules, allowing them to integrate any third-party services or logic to handle this functionality. Developers can also create and use subscribers to handle events in Medusa.
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/development/events/create-module',
label: 'Create an Event Module',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create an event module.'
}
},
{
type: 'link',
href: '/development/events/create-subscriber',
label: 'Create a Subscriber',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create a subscriber.'
}
},
]} />
@@ -0,0 +1,68 @@
---
description: 'In this document, youll learn about the local events module and how you can install it in your Medusa backend.'
addHowToData: true
---
# Local Events Module
In this document, youll learn about the local events module and how you can install it in your Medusa backend.
## Overview
Medusas modular architecture allows developers to extend or completely replace the logic used for events. You can create a custom module, or you can use the modules Medusa provides.
One of these modules is the local events module. This module allows you to utilize Node EventEmitter for the events system in Medusa. The Node EventEmitter is limited to a single process environment. This module is useful for development and testing, but its recommended to use the [Redis events module](./redis.md) in production.
This document will you guide you through installing the local events module.
---
## Prerequisites
Its assumed you already have a Medusa backend installed. If not, you can learn how to install it by following [this guide](../../backend/install.mdx).
---
## Step 1: Install the Module
In the root directory of your Medusa backend, install the Redis events module with the following command:
```bash npm2yarn
npm install @medusajs/event-bus-local
```
---
## Step 2: Add Configuration
In `medusa-config.js`, add the following to the exported object:
```js title=medusa-config.js
module.exports = {
// ...
modules: {
// ...
eventBus: {
resolve: "@medusajs/event-bus-local",
},
},
}
```
This registers the local events module as the main events service to use. This module does not require any options.
---
## Step 4: Test Module
To test the module, run the following command to start the Medusa backend:
```bash npm2yarn
npm run start
```
If the module was installed successfully, you should see the following message in the logs:
```bash noCopy noReport
Local Event Bus installed. This is not recommended for production.
```
@@ -0,0 +1,95 @@
---
description: 'In this document, youll learn about the Redis events module and how you can install it in your Medusa backend.'
addHowToData: true
---
# Redis Events Module
In this document, youll learn about the Redis events module and how you can install it in your Medusa backend.
## Overview
Medusas modular architecture allows developers to extend or completely replace the logic used for events. You can create a custom module, or you can use the modules Medusa provides.
One of these modules is the Redis module. This module allows you to utilize Redis for the event bus functionality. When installed, the Medusas events system is 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.
This document will you guide you through installing the Redis module.
---
## Prerequisites
### Medusa Backend
Its assumed you already have a Medusa backend installed. If not, you can learn how to install it by following [this guide](../../backend/install.mdx).
### Redis
You must have Redis installed and configured in your Medusa backend. You can learn how to install Redis in [their documentation](https://redis.io/docs/getting-started/installation/).
---
## Step 1: Install the Module
In the root directory of your Medusa backend, install the Redis events module with the following command:
```bash npm2yarn
npm install @medusajs/event-bus-redis
```
---
## Step 2: Add Environment Variable
The Redis events module requires a connection URL to Redis as part of its options. If you dont already have an environment variable set for a Redis URL, make sure to add one:
```bash
EVENTS_REDIS_URL=<YOUR_REDIS_URL>
```
Where `<YOUR_REDIS_URL>` is a connection URL to your Redis instance.
---
## Step 3: Add Configuration
In `medusa-config.js`, add the following to the exported object:
```js title=medusa-config.js
module.exports = {
// ...
modules: {
// ...
eventBus: {
resolve: "@medusajs/event-bus-redis",
options: {
redisUrl: process.env.EVENTS_REDIS_URL,
},
},
},
}
```
This registers the Redis events module as the main event bus service to use. In the options, you pass `redisUrl` with the value being the environment variable you set. This is the only required option.
Other available options include:
- `queueName`: a string indicating the name of the BullMQ queue. By default, its `events-queue`.
- `queueOptions`: an object containing options for the BullMQ queue. You can learn about available options in [BullMQs documentation](https://api.docs.bullmq.io/interfaces/QueueOptions.html). By default, its an empty object.
- `redisOptions`: an object containing options for the Redis instance. You can learn about available options in [io-rediss documentation](https://luin.github.io/ioredis/index.html#RedisOptions). By default, its an empty object.
---
## Step 4: Test Module
To test the module, run the following command to start the Medusa backend:
```bash npm2yarn
npm run start
```
If the module was installed successfully, you should see the following message in the logs:
```bash noCopy noReport
Connection to Redis in module 'event-bus-redis' established
```
@@ -9,16 +9,6 @@ import Icons from '@theme/Icon';
In this document, you'll learn what Subscribers are in Medusa.
## What are Events
In Medusa, there are events that are emitted when a certain action occurs. For example, if a customer places an order, the `order.placed` event is emitted with the order data.
The purpose of these events is to allow other parts of the platform, or third-party integrations, to listen to those events and perform a certain action. That is done by creating subscribers.
Medusa's queuing and events system is handled by Redis. So, you must have [Redis configured](../backend/prepare-environment.mdx#redis) on your backend to use subscribers.
---
## What are Subscribers
Subscribers register handlers for an events and allows you to perform an action when that event occurs. For example, if you want to send your customer an email when they place an order, then you can listen to the `order.placed` event and send the email when the event is emitted.