mediatr

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 8, 2025 License: MIT Imports: 4 Imported by: 33

README ΒΆ

go-mediatr
build-status go report license build-status Coverage Status build-status

This package is a Mediator Pattern implementation in golang, and inspired by great jbogard/mediatr library in .Net.

For decoupling some objects in a system we could use Mediator object as an interface, for decrease coupling between the objects. Mostly I uses this pattern when I use CQRS in my system.

There are some samples for using this package here, also I used this packages widely in this microservices sample

🧰 Installation

go get github.com/mehdihadeli/go-mediatr

πŸ”₯ Features

βœ… Handling Request/Response message for delivering message to only one handler (Commands, Queries)

βœ… Handling Notification message for delivering message to multiple handlers (Events)

βœ… Pipelenes Behaviours for handling some cross cutting concerns before or after executing handlers

πŸ›‘οΈ Strategies

Mediatr has two strategies for dispatching messages:

  1. Request/Response messages, dispatched to a single handler.
  2. Notification messages, dispatched to all (multiple) handlers and they don't have any response.

Request/Response Strategy

The request/response message, has just one handler, and can handle both command and query scenarios in CQRS Pattern.

Creating a Request/Response Message

For creating a request (command or query) that has just one handler, we could create a command message or query message as a request like this:

// Command (Request)
type CreateProductCommand struct {
    ProductID   uuid.UUID `validate:"required"`
    Name        string    `validate:"required,gte=0,lte=255"`
    Description string    `validate:"required,gte=0,lte=5000"`
    Price       float64   `validate:"required,gte=0"`
    CreatedAt   time.Time `validate:"required"`
}

// Query (Request)
type GetProdctByIdQuery struct {
    ProductID uuid.UUID `validate:"required"`
}

And for response of these requests, we could create response messages as a response like this:

// Command (Response)
type CreateProductCommandResponse struct {
    ProductID uuid.UUID `json:"productId"`
}

// Query (Response)
type GetProductByIdQueryResponse struct {
    ProductID   uuid.UUID `json:"productId"`
    Name        string    `json:"name"`
    Description string    `json:"description"`
    Price       float64   `json:"price"`
    CreatedAt   time.Time `json:"createdAt"`
}
Creating Request Handler

For handling our requests, we should create a single request handler for each request. Each handler should implement the RequestHandler interface.

type RequestHandler[TRequest any, TResponse any] interface {
	Handle(ctx context.Context, request TRequest) (TResponse, error)
}

Here we Create request handler (command handler and query handler) for our requests, that implements above interface:

// Command Handler
type CreateProductCommandHandler struct {
	productRepository *repository.InMemoryProductRepository
}

func NewCreateProductCommandHandler(productRepository *repository.InMemoryProductRepository) *CreateProductCommandHandler {
	return &CreateProductCommandHandler{productRepository: productRepository}
}

func (c *CreateProductCommandHandler) Handle(ctx context.Context, command *CreateProductCommand) (*creatingProductDtos.CreateProductCommandResponse, error) {

	product := &models.Product{
		ProductID:   command.ProductID,
		Name:        command.Name,
		Description: command.Description,
		Price:       command.Price,
		CreatedAt:   command.CreatedAt,
	}

	createdProduct, err := c.productRepository.CreateProduct(ctx, product)
	if err != nil {
		return nil, err
	}

	response := &creatingProductDtos.CreateProductCommandResponse{ProductID: createdProduct.ProductID}

	return response, nil
}
// Query Handler
type GetProductByIdQueryHandler struct {
    productRepository *repository.InMemoryProductRepository
}

func NewGetProductByIdQueryHandler(productRepository *repository.InMemoryProductRepository) *GetProductByIdQueryHandler {
    return &GetProductByIdQueryHandler{productRepository: productRepository}
}

func (c *GetProductByIdQueryHandler) Handle(ctx context.Context, query *GetProductByIdQuery) (*gettingProductDtos.GetProdctByIdQueryResponse, error) {

    product, err := c.productRepository.GetProductById(ctx, query.ProductID)
    if err != nil {
        return nil, err
    }

    response := &gettingProductDtos.GetProdctByIdQueryResponse{
        ProductID:   product.ProductID,
        Name:        product.Name,
        Description: product.Description,
        Price:       product.Price,
        CreatedAt:   product.CreatedAt,
    }

    return response, nil
}

Note: In the cases we don't need a response from our request handler, we can use Unit type, that actually is an empty struct:.

Registering Request Handler to the MediatR

Before sending or dispatching our requests, we should register our request handlers to the MediatR.

Here we register our request handlers (command handler and query handler) to the MediatR:

// Registering `createProductCommandHandler` request handler for `CreateProductCommand` request to the MediatR
mediatr.RegisterHandler[*creatingProduct.CreateProductCommand, *creatingProductsDtos.CreateProductCommandResponse](createProductCommandHandler)

// Registering `getProductByIdQueryHandler` request handler for `GetProductByIdQuery` request to the MediatR
mediatr.RegisterHandler[*gettingProduct.GetProductByIdQuery, *gettingProductDtos.GetProdctByIdQueryResponse](getProductByIdQueryHandler)
Sending Request to the MediatR

Finally, send a message through the mediator.

Here we send our requests to the MediatR for dispatching them to the request handlers (command handler and query handler):

// Sending `CreateProductCommand` request to mediatr for dispatching to the `CreateProductCommandHandler` request handler
command := &CreateProductCommand{
    ProductID:   uuid.NewV4(),
    Name:        request.name,
    Description: request.description,
    Price:       request.price,
    CreatedAt:   time.Now(),
}

mediatr.Send[*CreateProductCommand, *creatingProductsDtos.CreateProductCommandResponse](ctx, command)
// Sending `GetProductByIdQuery` request to mediatr for dispatching to the `GetProductByIdQueryHandler` request handler
query := &GetProdctByIdQuery{
    ProductID:   uuid.NewV4()
}

mediatr.Send[*GetProductByIdQuery, *gettingProductsDtos.GetProductByIdQueryResponse](ctx, query)

Notification Strategy

The notification message, can have multiple handlers and doesn't have any response, and it can handle an event notification or notification in event driven architecture.

Creating a Notification Message

For creating a notification (event), that has multiple handlers and doesn't have any response, we could create an event notification as a notification like this:

// Event (Notification)
type ProductCreatedEvent struct {
    ProductID uuid.UUID   `json:"productId"`
    Name        string    `json:"name"`
    Description string    `json:"description"`
    Price       float64   `json:"price"`
    CreatedAt   time.Time `json:"createdAt"`
}

This event doesn't have any response.

Creating Notification Handlers

For handling our notification, we can create multiple notification handlers for each notification event. Each handler should implement the NotificationHandler interface.

type NotificationHandler[TNotification any] interface {
    Handle(ctx context.Context, notification TNotification) error
}

Here we Create multiple notification event handler for our notification, that implements above interface:

// Notification Event Handler1
type ProductCreatedEventHandler1 struct {
}

func (c *ProductCreatedEventHandler1) Handle(ctx context.Context, event *ProductCreatedEvent) error {
//Do something with the event here !
    return nil
}
// Notification Event Handler2
type ProductCreatedEventHandler2 struct {
}

func (c *ProductCreatedEventHandler2) Handle(ctx context.Context, event *ProductCreatedEvent) error {
//Do something with the event here !
    return nil
}
Registering Notification Handlers to the MediatR

Before publishing our notifications, we should register our notification handlers to the MediatR.

Here we register our notification handlers to the MediatR:

// Registering `notificationHandler1`, `notificationHandler2` notification handler for `ProductCreatedEvent` notification event to the MediatR
notificationHandler1 := &ProductCreatedEventHandler1{}
notificationHandler2 := &ProductCreatedEventHandler2{}

mediatr.RegisterNotificationHandlers[*events.ProductCreatedEvent](notificationHandler1, notificationHandler2)
Publishing Notification to the MediatR

Finally, publish a notification event through the mediator.

Here we publish our notification to the MediatR for dispatching them to the notification handlers:

// Publishing `ProductCreatedEvent` notification to mediatr for dispatching to the `ProductCreatedEventHandler1`, `ProductCreatedEventHandler2` notification handlers
productCreatedEvent := 	&ProductCreatedEvent {
    ProductID:   createdProduct.ProductID,
    Name:        createdProduct.Name,
    Price:       createdProduct.Price,
    CreatedAt:   createdProduct.CreatedAt,
    Description: createdProduct.Description,
}

mediatr.Publish[*events.ProductCreatedEvent](ctx, productCreatedEvent)

βš’οΈ Using Pipeline Behaviors

Sometimes we need to add some cross-cutting concerns before after running our request handlers like logging, metrics, circuit breaker, retry, etc. In this case we can use PipelineBehavior. It is actually is like a middleware or decorator pattern.

These behaviors will execute before or after running our request handlers with calling Send method for a request on the mediatr.

Creating Pipeline Behavior

For creating a pipeline behaviour we should implement the PipelineBehavior interface:

type PipelineBehavior interface {
	Handle(ctx context.Context, request interface{}, next RequestHandlerFunc) (interface{}, error)
}

The request parameter is the request object passed in through Send method of mediatr, while the next parameter is a continuation for the next action in the behavior chain and its type is RequestHandlerFunc.

Here is an example of a pipeline behavior:

type RequestLoggerBehaviour struct {
}

func (r *RequestLoggerBehaviour) Handle(ctx context.Context, request interface{}, next mediatr.RequestHandlerFunc) (interface{}, error) {
	log.Printf("logging some stuff before handling the request")

	response, err := next()
	if err != nil {
		return nil, err
	}

	log.Println("logging some stuff after handling the request")

	return response, nil
}

In our defined behavior, we need to call next parameter that call next action in the behavior chain, if there aren't any other behaviours next will call our actual request handler and return the response. We can do something before of after of calling next action in the behavior chain.

Registering Pipeline Behavior to the MediatR

For registering our pipeline behavior to the MediatR, we should use RegisterPipelineBehaviors method:

loggerPipeline := &behaviours.RequestLoggerBehaviour{}
err = mediatr.RegisterRequestPipelineBehaviors(loggerPipeline)

Documentation ΒΆ

Overview ΒΆ

Mediatr package implements the mediator pattern for Go, providing:

- Request/response message handling - Notification broadcasting - Pipeline behaviors for cross-cutting concerns

The package is thread-safe and designed for high-performance concurrent use.

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

This section is empty.

Functions ΒΆ

func ClearNotificationRegistrations ΒΆ added in v1.1.9

func ClearNotificationRegistrations()

ClearNotificationRegistrations removes all registered notification handlers.

func ClearPipelineBehaviors ΒΆ added in v1.4.0

func ClearPipelineBehaviors()

ClearPipelineBehaviors removes all registered pipeline behaviors.

func ClearRequestRegistrations ΒΆ added in v1.1.9

func ClearRequestRegistrations()

ClearRequestRegistrations removes all registered request handlers. Useful for testing scenarios.

func Publish ΒΆ

func Publish[TNotification any](ctx context.Context, notification TNotification) error

Publish broadcasts a notification to all registered handlers. All handlers are executed, even if some return errors. Returns the first error encountered, if any.

Example:

type OrderShipped struct { OrderID string }

// Register handlers
mediatr.RegisterNotificationHandlers[OrderShipped](
    &ShippingNotifier{},
    &InventoryUpdater{},
)

// Publish
err := mediatr.Publish(ctx, OrderShipped{OrderID: "123"})
if err != nil { /* handle error */ }

func RegisterNotificationHandler ΒΆ

func RegisterNotificationHandler[TEvent any](handler NotificationHandler[TEvent]) error

RegisterNotificationHandler registers a handler for notifications of specific type. Multiple handlers can be registered for the same notification type.

func RegisterNotificationHandlerFactory ΒΆ added in v1.1.10

func RegisterNotificationHandlerFactory[TEvent any](factory NotificationHandlerFactory[TEvent]) error

RegisterNotificationHandlerFactory registers a factory that creates notification handlers.

func RegisterNotificationHandlers ΒΆ

func RegisterNotificationHandlers[TEvent any](handlers ...NotificationHandler[TEvent]) error

RegisterNotificationHandlers registers multiple handlers for a notification type. Returns error if no handlers are provided or registration fails.

func RegisterNotificationHandlersFactories ΒΆ added in v1.1.10

func RegisterNotificationHandlersFactories[TEvent any](factories ...NotificationHandlerFactory[TEvent]) error

RegisterNotificationHandlersFactories registers multiple handler factories.

func RegisterRequestHandler ΒΆ

func RegisterRequestHandler[TRequest any, TResponse any](handler RequestHandler[TRequest, TResponse]) error

RegisterRequestHandler registers a request handler for a specific request type. Returns an error if a handler is already registered for the request type.

Example:

err := mediatr.RegisterRequestHandler[*MyRequest, *MyResponse](&MyHandler{})

func RegisterRequestHandlerFactory ΒΆ added in v1.1.10

func RegisterRequestHandlerFactory[TRequest any, TResponse any](factory RequestHandlerFactory[TRequest, TResponse]) error

RegisterRequestHandlerFactory registers a factory that creates request handlers. Useful for stateful handlers that need fresh instances per request.

func RegisterRequestPipelineBehaviors ΒΆ

func RegisterRequestPipelineBehaviors(behaviours ...PipelineBehavior) error

RegisterRequestPipelineBehaviors registers middleware behaviors that wrap request handlers. Behaviors are executed in registration order (first registered runs first). Returns error if any behavior is already registered.

func Send ΒΆ

func Send[TRequest any, TResponse any](ctx context.Context, request TRequest) (TResponse, error)

Send dispatches a request to its registered handler and returns the response. Executes all registered pipeline behaviors in order. Returns error if: - No handler is registered for the request - Handler returns an error - Any pipeline behavior returns an error

Example:

response, err := mediatr.Send[*MyRequest, *MyResponse](ctx, &MyRequest{})

Types ΒΆ

type NotificationHandler ΒΆ

type NotificationHandler[TNotification any] interface {
	Handle(ctx context.Context, notification TNotification) error
}

NotificationHandler processes notifications of a specific type. Multiple handlers can process the same notification.

type NotificationHandlerFactory ΒΆ added in v1.1.10

type NotificationHandlerFactory[TNotification any] func() NotificationHandler[TNotification]

NotificationHandlerFactory creates new instances of notification handlers.

type PipelineBehavior ΒΆ

type PipelineBehavior interface {
	Handle(ctx context.Context, request interface{}, next RequestHandlerFunc) (interface{}, error)
}

PipelineBehavior defines middleware-like components that can intercept requests. Implement this interface to add cross-cutting concerns like logging, validation, etc.

type RequestHandler ΒΆ

type RequestHandler[TRequest any, TResponse any] interface {
	Handle(ctx context.Context, request TRequest) (TResponse, error)
}

RequestHandler handles a specific request type and returns a response. Implement this interface for your request handlers.

Example:

type MyHandler struct{}
func (h *MyHandler) Handle(ctx context.Context, req MyRequest) (MyResponse, error) {
    // handle request
}

type RequestHandlerFactory ΒΆ added in v1.1.10

type RequestHandlerFactory[TRequest any, TResponse any] func() RequestHandler[TRequest, TResponse]

RequestHandlerFactory creates new instances of a request handler. Useful when handlers need fresh instances per request.

type RequestHandlerFunc ΒΆ

type RequestHandlerFunc func(ctx context.Context) (interface{}, error)

RequestHandlerFunc is a continuation function used in pipeline behaviors. It represents the next handler in the pipeline chain.

type Unit ΒΆ

type Unit struct{}

Unit represents a void return type, used for handlers that don't return data.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL