Go Guide
The Go implementation uses generics (Go 1.21+) for type-safe handler registration and dispatch. Handlers are plain functions; no interface implementations are required for basic usage.
The Go module lives in a dedicated public repository so that go get works without authentication. Import path: github.com/chandru415/slck-mediator/go/mediator
Table of contents
Architecture
flowchart TD
subgraph "Your Application"
CMD["CreateOrderCommand"] --> M[Mediator]
QRY["GetOrderQuery"] --> M
EVT["OrderCreatedEvent"] --> M
end
subgraph "Framework"
M --> PL["Middleware Pipeline"]
PL --> H1["RequestHandler"]
PL --> H2["StreamHandler"]
PL --> H3["NotificationHandler"]
end
H1 --> DB[(Database)]
H1 --> EB[[Event Bus]]
style M fill:#00ADD8,color:#fff
Quick Start
go get github.com/chandru415/slck-mediator/go/mediator@latest
1. Define messages
Messages are plain Go structs. No embedding or interface satisfaction is required.
package orders
// Command (returns a single value)
type CreateOrderCommand struct {
UserID string
Amount float64
}
// Query
type GetOrderQuery struct {
OrderID string
}
type GetOrderResult struct {
OrderID string
Status string
}
// Notification (dispatched to N handlers)
type OrderCreatedEvent struct {
OrderID string
}
2. Define handlers
Handler functions receive a *RequestContext as the first argument, providing access to the mediator, logger, metrics, and tracer.
import framework "github.com/chandru415/slck-mediator/go/mediator"
func HandleCreateOrder(ctx *framework.RequestContext, cmd CreateOrderCommand) (string, error) {
orderID := "order-" + cmd.UserID
ctx.Logger.Info("order.created", map[string]any{"order_id": orderID})
return orderID, nil
}
func HandleGetOrder(ctx *framework.RequestContext, q GetOrderQuery) (GetOrderResult, error) {
return GetOrderResult{OrderID: q.OrderID, Status: "confirmed"}, nil
}
3. Wire the mediator
package main
import (
"context"
"fmt"
framework "github.com/chandru415/slck-mediator/go/mediator"
)
func main() {
logger := framework.ConsoleLogger{}
metrics := framework.NewInMemoryMetrics()
tracer := framework.NewInMemoryTracer("my-service", logger, metrics)
m := framework.NewMediator(framework.Config{
Logger: logger,
Metrics: metrics,
Tracer: tracer,
})
framework.RegisterRequestHandler[CreateOrderCommand, string](m, HandleCreateOrder)
framework.RegisterRequestHandler[GetOrderQuery, GetOrderResult](m, HandleGetOrder)
orderID, err := framework.Send[string](m, context.Background(), CreateOrderCommand{
UserID: "user-1",
Amount: 99.99,
})
if err != nil {
panic(err)
}
fmt.Println(orderID)
}
4. Add middleware
m.RegisterPipeline(framework.NewLoggingMiddleware(logger))
m.RegisterPipeline(framework.NewRetryMiddleware(3, 100))
m.RegisterPipeline(framework.NewTimeoutMiddleware(5_000))
Streaming
Stream handlers write values to a channel. The framework closes the channel when the handler function returns.
func HandlePriceTicker(
ctx *framework.RequestContext,
req PriceTickerQuery,
out chan<- float64,
) error {
for i := 0; i < 5; i++ {
out <- rand.Float64() * 100
time.Sleep(200 * time.Millisecond)
}
return nil
}
framework.RegisterStreamHandler[PriceTickerQuery, float64](m, HandlePriceTicker)
ch, err := framework.Stream[float64](m, context.Background(), PriceTickerQuery{Symbol: "AAPL"})
for price := range ch {
fmt.Println(price)
}
Code Generation
Add a //go:generate directive to generate a direct-dispatch GeneratedMediator:
//go:generate go run path/to/generate-mediator.go
go generate ./...
The generated mediator uses type-switch statements, eliminating all map lookups and interface assertions at dispatch time.
Never edit the generated file manually. Re-run go generate whenever you add, rename, or remove a handler or message type.
Full API Reference
See the README for the complete API including outbox, inbox, saga, event bus, and all middleware options.