Go implementation of the Mediator pattern with support for request/response, notifications (pub/sub), and pipeline behaviors.
go get github.com/oshturhq/medixRequires Go 1.21+ (generics support).
package main
import (
"context"
"log/slog"
"github.com/oshturhq/medix"
)
// Define request and response
type GetUserRequest struct {
ID int
}
type GetUserResponse struct {
Name string
}
// Implement handler
type GetUserHandler struct{}
func (h *GetUserHandler) Handle(ctx context.Context, req GetUserRequest) (GetUserResponse, error) {
return GetUserResponse{Name: "John"}, nil
}
func main() {
// Register handler
medix.RegisterRequestHandler[GetUserRequest, GetUserResponse](&GetUserHandler{})
// Send request
resp, err := medix.Send[GetUserRequest, GetUserResponse](context.Background(), GetUserRequest{ID: 1})
if err != nil {
panic(err)
}
slog.Info("user fetched", "name", resp.Name)
}One request, one handler. Use Send to dispatch.
// Register
medix.RegisterRequestHandler[MyRequest, MyResponse](&MyHandler{})
// Or with factory (new instance per request)
medix.RegisterRequestHandlerFactory[MyRequest, MyResponse](func() medix.RequestHandler[MyRequest, MyResponse] {
return &MyHandler{db: connectDB()}
})
// Send
response, err := medix.Send[MyRequest, MyResponse](ctx, MyRequest{...})One notification, multiple handlers. Use Publish to broadcast.
type UserCreated struct {
UserID int
Email string
}
type EmailHandler struct{}
func (h *EmailHandler) Handle(ctx context.Context, e UserCreated) error {
// Send welcome email
return nil
}
type AuditHandler struct{}
func (h *AuditHandler) Handle(ctx context.Context, e UserCreated) error {
// Log to audit trail
return nil
}
// Register multiple handlers
medix.RegisterNotificationHandlers[UserCreated](
&EmailHandler{},
&AuditHandler{},
)
// Publish (all handlers are called sequentially)
err := medix.Publish(ctx, UserCreated{UserID: 1, Email: "user@example.com"})Cross-cutting concerns that wrap all request handlers.
type LoggingBehavior struct {
logger *slog.Logger
}
func (b *LoggingBehavior) Handle(ctx context.Context, request any, next medix.RequestHandlerFunc) (any, error) {
b.logger.Info("before handler")
response, err := next(ctx)
b.logger.Info("after handler")
return response, err
}
type TimingBehavior struct {
logger *slog.Logger
}
func (b *TimingBehavior) Handle(ctx context.Context, request any, next medix.RequestHandlerFunc) (any, error) {
start := time.Now()
response, err := next(ctx)
b.logger.Info("request completed", "duration", time.Since(start))
return response, err
}
// Register behaviors (order matters: first registered = outermost)
medix.RegisterRequestPipelineBehaviors(
&LoggingBehavior{logger: slog.Default()},
&TimingBehavior{logger: slog.Default()},
)Execution order:
LoggingBehavior.Before -> TimingBehavior.Before -> Handler -> TimingBehavior.After -> LoggingBehavior.After
type Validator interface {
Validate() error
}
type ValidationBehavior struct{}
func (b *ValidationBehavior) Handle(ctx context.Context, request any, next medix.RequestHandlerFunc) (any, error) {
if v, ok := request.(Validator); ok {
if err := v.Validate(); err != nil {
return nil, err
}
}
return next(ctx)
}
// Request with validation
type CreateUserRequest struct {
Name string
Email string
}
func (r CreateUserRequest) Validate() error {
if r.Name == "" {
return errors.New("name is required")
}
return nil
}| Function | Description |
|---|---|
RegisterRequestHandler[TReq, TRes](handler) |
Register a singleton handler |
RegisterRequestHandlerFactory[TReq, TRes](factory) |
Register a factory (new instance per request) |
Send[TReq, TRes](ctx, request) |
Dispatch request to handler |
| Function | Description |
|---|---|
RegisterNotificationHandler[T](handler) |
Register a single notification handler |
RegisterNotificationHandlers[T](handlers...) |
Register multiple handlers at once |
RegisterNotificationHandlerFactory[T](factory) |
Register with factory |
Publish[T](ctx, notification) |
Publish to all registered handlers |
| Function | Description |
|---|---|
RegisterRequestPipelineBehaviors(behaviors...) |
Register pipeline behaviors |
| Function | Description |
|---|---|
ClearRequestRegistrations() |
Clear all request handlers |
ClearNotificationRegistrations() |
Clear all notification handlers |
ClearPipelineBehaviors() |
Clear all pipeline behaviors |
The library works well with hexagonal (ports & adapters) architecture:
internal/
├── domain/ # Core business logic
│ └── user/
│ ├── entity.go # User entity
│ └── repository.go # Repository interface (port)
│
├── application/ # Use cases
│ ├── command/ # Write operations (CreateUser, DeleteUser)
│ ├── query/ # Read operations (GetUser, ListUsers)
│ ├── event/ # Domain events (UserCreated, UserDeleted)
│ └── behavior/ # Pipeline behaviors (logging, validation)
│
├── infrastructure/ # Adapters
│ ├── persistence/ # Database implementations
│ └── notification/ # Event handlers (email, audit)
│
└── main.go # Composition root
See internal/example for a complete working example.
- All registration functions are thread-safe
SendandPublishare safe for concurrent use- Context cancellation is respected