Emit Workflow and Service Events

In this chapter, you'll learn about event types and how to emit an event in a service or workflow.

Event Types#

In your customization, you can emit an event, then listen to it in a subscriber and perform an asynchronous action, such as sending a notification or sending data to a third-party system.

There are two types of events in MedusaGo:

  1. Workflow event: an event that's emitted in a workflow after a commerce feature is performed. For example, MedusaGo emits the order.placed event after a cart is completed.
  2. Service event: an event that's emitted to track, trace, or debug processes under the hood. For example, you can emit an event with an audit trail.

Which Event Type to Use?#

Workflow events are the most common event type in development, as most custom features and customizations are built around workflows.

Some examples of workflow events:

  1. When a user creates a blog post and you're emitting an event to send a newsletter email.
  2. When you finish syncing products to a third-party system and you want to notify the admin user of new products added.
  3. When a customer purchases a digital product and you want to generate and send it to them.

You should only go for a service event if you're emitting an event for processes under the hood that don't directly affect front-facing features.

Some examples of service events:

  1. When you're tracing data manipulation and changes, and you want to track every time some custom data is changed.
  2. When you're syncing data with a search engine.

Emit Event in a Workflow#

To emit a workflow event, use the EmitEventStep helper step provided in the core-flows/src/common/steps package. It allows you to emit an event from within a workflow, and it only emits the event after the workflow has finished successfully.

For example:

src/workflows/hello-world.go
// HelloWorldWorkflow emits the custom.created event once it finishes
// successfully.
var HelloWorldWorkflow = composer.CreateWorkflow("hello-world",
	func(e *composer.Execution, input struct{}) (any, error) {
		// ...

		payload := dal.NewRecord()
		payload.Set("id", "123")
		// other data payload

		_, err := commonsteps.EmitEventStep.Run(e, commonsteps.EmitEventStepInput{
			EventName: "custom.created",
			Data:      payload,
		})
		return nil, err
	})

You run the step like any other step of a workflow, with Run. It accepts an EmitEventStepInput struct having the following properties:

  • EventName: The event's name.
  • Data: The data payload. For a single event, pass an object, such as a *dal.Record. Subscribers listening to the event will receive this data in the event's payload.
  • Options: (optional) Options for the event module. Learn more in the Event Options section.
  • Metadata: (optional) Additional metadata to add to the event.

In this example, you emit the event custom.created and pass in the data payload an id property. The event is only emitted if the workflow finishes successfully. If the workflow fails, the event isn't emitted.

Test it Out#

If you execute the workflow, the event is emitted and you can see it in your application's logs.

Any subscribers listening to the event are executed.

Emit Event Multiple Times#

You can also emit the event multiple times with EmitEventStep by passing a slice of payloads, of type []any, to the Data property. The event is emitted once per each element in the slice.

For example:

src/workflows/hello-world.go
// emitMany emits the event three times, once for each element of Data.
func emitMany(e *composer.Execution) error {
	payloads := make([]any, 0, 3)
	for _, id := range []string{"123", "456", "789"} {
		payload := dal.NewRecord()
		payload.Set("id", id)
		payloads = append(payloads, payload)
	}

	_, err := commonsteps.EmitEventStep.Run(e, commonsteps.EmitEventStepInput{
		EventName: "custom.created",
		Data:      payloads,
	})
	return err
}

The event custom.created will be emitted three times, once per each payload in the slice passed to the Data property. The subscriber listening to the event will be executed three times, each time receiving one of the payloads as the event's payload.

Event Options#

The Options property is a *dal.Record that's passed to the event module. Which options an event module uses depends on the module:

Option

Description

delay

The time to wait, in milliseconds, before the event is delivered to the subscribers. Both event modules support it.

priority

When the event is processed relative to other events in the queue. The Redis Event Module supports it. The Local Event Module ignores it.

attempts

How many times the Redis Event Module tries to deliver the event if a subscriber fails.

Emit Event with Custom Priority#

You can specify a priority level when emitting events to control when the event is processed relative to other events in the queue. This is useful to ensure that critical business events are handled promptly, while less important events can be deferred.

The priority is a number between 1 and 2097152, where a lower number means a higher priority. The default is 100. The EventPriority variable of the utils/src/event-bus package has the following values: CRITICAL (10), HIGH (50), DEFAULT (100), LOW (500), and LOWEST (2097152). An invalid value is replaced with the default priority and logs a warning.

To emit an event with custom priority, pass the priority option:

src/workflows/critical-operation.go
// emitCritical emits an event with the CRITICAL priority.
func emitCritical(e *composer.Execution) error {
	options := dal.NewRecord()
	options.Set("priority", eventbus.EventPriority.CRITICAL)

	payload := dal.NewRecord()
	payload.Set("id", "123")

	_, err := commonsteps.EmitEventStep.Run(e, commonsteps.EmitEventStepInput{
		EventName: "payment.processed",
		Data:      payload,
		Options:   options,
	})
	return err
}

In the above example, the payment.processed event is emitted with the CRITICAL priority level, ensuring it is processed before other lower-priority events when you use the Redis Event Module.


Emit Event in a Service#

To emit a service event, resolve the event module from the container, then use its Emit method:

  1. In the function that creates your service, keep the container that the module is loaded with. It's in the Container property of the ServiceOptions that the function receives:
src/modules/blog/service.go
// BlogModuleService is the main service of a module.
type BlogModuleService struct {
	*utilsmodulessdk.MedusaService
	container typesmodulessdk.Resolver
}

// NewBlogModuleService is the Service function of the module's definition.
// It keeps the container the module is loaded with, to resolve the event
// module when an event is emitted.
func NewBlogModuleService(opts typesmodulessdk.ServiceOptions, base *utilsmodulessdk.MedusaService) (any, error) {
	return &BlogModuleService{MedusaService: base, container: opts.Container}, nil
}

// PerformAction emits a service event.
func (s *BlogModuleService) PerformAction(ctx context.Context) error {
	// TODO perform action

	bus, ok := s.container.Resolve(utilsmodulessdk.EVENT_BUS).(eventbus.Emitter)
	if !ok {
		return errors.New("the event module isn't registered")
	}

	payload := dal.NewRecord()
	payload.Set("id", "123")
	// other data payload

	return bus.Emit(ctx, []eventbus.Message{
		{Name: "custom.event", Data: payload},
	})
}
Note: The Container that the service receives is the container of the application, so the event module is available in it. Resolve the event module when you emit the event, not when the service is created, since it may not be registered yet at that point.
  1. Use the event module's Emit method in the service's methods to emit an event. In the example above, the PerformAction method emits the custom.event event.

The method accepts a context and a slice of Message values, which have the following properties:

  • Name: The event's name.
  • Data: The data payload. You can pass any value, and subscribers listening to the event will receive this data in the event's payload.
  • Options and Metadata: (optional) The event options, and additional metadata.

Test it Out#

If you execute the PerformAction method of your service, the event is emitted and you can see it in your application's logs.

Any subscribers listening to the event are also executed.

Found a problem with this page?Report Issue
Edited Nov 14·Edit this page