mediatr

package module
v0.0.0-...-5428427 Latest Latest
Warning

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

Go to latest
Published: Nov 24, 2025 License: MIT Imports: 5 Imported by: 0

README

Mediator Pattern for Go

This package provides a lightweight mediator pattern implementation with support for request/response handlers, behaviors (middleware), and event handling.

Features

  • Type-safe registration using Go generics
  • Request/response pattern with typed handlers
  • Behavior pipeline (similar to middleware)
  • Event/notification pattern with multiple handlers
  • Decoupled communication between components

Usage

Basic Request/Response
package main

import (
    "context"
    "github.com/The127/mediatr"
)

// Define a request type
type GetUserRequest struct {
    UserID string
}

// Define a response type
type GetUserResponse struct {
    Username string
    Email    string
}

func main() {
    m := mediatr.NewMediator()

    // Register a handler for the request
	mediatr.RegisterHandler(m, func(ctx context.Context, request GetUserRequest) (GetUserResponse, error) {
        // Handle the request
        return GetUserResponse{
            Username: "john_doe",
            Email:    "john@example.com",
        }, nil
    })

    // Send a request and receive a response
    response, err := mediatr.Send[GetUserResponse](context.Background(), m, GetUserRequest{
        UserID: "123",
    })
    if err != nil {
        // Handle error
    }
    
    // Use the response
    println(response.Username)
}
Behaviors (Middleware)

Behaviors allow you to add cross-cutting concerns like logging, validation, or authorization to your request pipeline:

// Define a behavior for logging
mediatr.RegisterBehaviour(m, func(ctx context.Context, request GetUserRequest, next mediatr.Next) (any, error) {
    println("Before handling request")
    
    // Call the next behavior or handler
    response, err := next()
    
    println("After handling request")
    return response, err
})

// Register the handler
mediatr.RegisterHandler(m, func(ctx context.Context, request GetUserRequest) (GetUserResponse, error) {
    return GetUserResponse{Username: "john_doe"}, nil
})

// The behavior will be executed before and after the handler
response, err := mediatr.Send[GetUserResponse](context.Background(), m, GetUserRequest{UserID: "123"})

Behaviors are executed in the order they are registered, forming a pipeline:

Request → Behaviour 1 → Behaviour 2 → Handler → Behaviour 2 → Behaviour 1 → Response
Event Handling

Events allow you to notify multiple handlers about something that has happened:

// Define an event type
type UserCreatedEvent struct {
    UserID   string
    Username string
}

// Register multiple event handlers
mediatr.RegisterEventHandler(m, func(ctx context.Context, evt UserCreatedEvent) error {
    println("Sending welcome email to", evt.Username)
    return nil
})

mediatr.RegisterEventHandler(m, func(ctx context.Context, evt UserCreatedEvent) error {
    println("Creating user profile for", evt.Username)
    return nil
})

// Send an event to all registered handlers
err := mediatr.SendEvent(context.Background(), m, UserCreatedEvent{
    UserID:   "123",
    Username: "john_doe",
})

Real-World Example

Here's a complete example showing how handlers, behaviors, and events work together:

package main

import (
    "context"
    "errors"
    "github.com/The127/mediatr"
)

type CreateUserCommand struct {
    Username string
    Email    string
}

type CreateUserResult struct {
    UserID string
}

type UserCreatedEvent struct {
    UserID   string
    Username string
}

func main() {
    m := mediatr.NewMediator()

    // Register a validation behavior
    mediatr.RegisterBehaviour(m, func(ctx context.Context, request CreateUserCommand, next mediatr.Next) (any, error) {
        if request.Username == "" {
            return nil, errors.New("username is required")
        }
        if request.Email == "" {
            return nil, errors.New("email is required")
        }
        return next()
    })

    // Register the command handler
    mediatr.RegisterHandler(m, func(ctx context.Context, cmd CreateUserCommand) (CreateUserResult, error) {
        // Create the user in database
        userID := "user-123"
        
        // Publish an event
        err := mediatr.SendEvent(ctx, m, UserCreatedEvent{
            UserID:   userID,
            Username: cmd.Username,
        })
		if err != nil {
			return CreateUserResult{}, err
		}
        
        return CreateUserResult{UserID: userID}, nil
    })

    // Register event handlers
    mediatr.RegisterEventHandler(m, func(ctx context.Context, evt UserCreatedEvent) error {
        println("Sending welcome email to", evt.Username)
        return nil
    })

    // Execute the command
    result, err := mediatr.Send[CreateUserResult](context.Background(), m, CreateUserCommand{
        Username: "john_doe",
        Email:    "john@example.com",
    })
    if err != nil {
        panic(err)
    }
    
    println("User created with ID:", result.UserID)
}

Key Concepts

Handlers

Handlers process a request and return a response. Each request type can have exactly one handler registered.

type HandlerFunc[TRequest any, TResponse any] func(ctx context.Context, request TRequest) (TResponse, error)
Behaviors

Behaviors are like middleware that wrap around handlers. They can:

  • Execute logic before and after the handler
  • Short-circuit the pipeline by returning early
  • Modify the request or response
  • Add cross-cutting concerns (logging, validation, authorization, etc.)
type BehaviourFunc[TRequest any] func(ctx context.Context, request TRequest, next Next) (any, error)

Behaviors are applied to all requests that match their type constraint. If a behavior is registered for a base type or interface, it will be applied to all requests that implement that type.

Events

Events represent notifications that something has happened. Unlike requests:

  • Events can have multiple handlers
  • Event handlers don't return values (except errors)
  • Events are processed sequentially
  • If an event handler returns an error, processing stops
type EventHandlerFunc[TEvent any] func(ctx context.Context, evt TEvent) error

Benefits

  1. Decoupling - Request senders don't need to know about handlers
  2. Single Responsibility - Each handler has one job
  3. Open/Closed Principle - Add new handlers without modifying existing code
  4. Cross-Cutting Concerns - Behaviors handle common logic like validation and logging
  5. Event-Driven Architecture - Decouple components using events
  6. Type Safety - Generic implementation ensures compile-time type checking

Thread Safety

The mediator itself is thread-safe for registration operations (handlers, behaviors, events should be registered during application startup).

Handler execution is thread-safe as long as the handlers themselves are thread-safe. The same mediator instance can be used concurrently from multiple goroutines.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Logger

func Logger(ctx context.Context) *slog.Logger

func NewMediator

func NewMediator() *mediator

func RegisterBehaviour

func RegisterBehaviour[TRequest any](m *mediator, behaviour BehaviourFunc[TRequest])

func RegisterEventHandler

func RegisterEventHandler[TEvent any](m *mediator, eventHandler EventHandlerFunc[TEvent])

func RegisterHandler

func RegisterHandler[TRequest any, TResponse any](m *mediator, handler HandlerFunc[TRequest, TResponse])

func Send

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

func SendEvent

func SendEvent[TEvent any](ctx context.Context, m Mediator, evt TEvent) error

func WithLogger

func WithLogger(ctx context.Context, l *slog.Logger) context.Context

Types

type BehaviourFunc

type BehaviourFunc[TRequest any] func(ctx context.Context, request TRequest, next Next) (any, error)

type EventHandlerFunc

type EventHandlerFunc[TEvent any] func(ctx context.Context, evt TEvent) error

type HandlerFunc

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

type Mediator

type Mediator interface {
	Send(ctx context.Context, request any, requestType reflect.Type, responseType reflect.Type) (any, error)
	SendEvent(ctx context.Context, evt any, eventType reflect.Type) error
}

type Next

type Next func() (any, error)

Directories

Path Synopsis
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

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