Microservices Patterns¶
Patterns for building microservices with Marten.
Health Checks¶
Use middleware.Health for liveness probes — it answers before any other middleware runs, keeping probe latency minimal:
// Simple liveness
app.Use(middleware.Health("/health"))
// Readiness with dependency checks
app.Use(middleware.HealthWithConfig(middleware.HealthConfig{
Path: "/ready",
Handler: func(c *marten.Ctx) error {
if err := db.Ping(); err != nil {
return c.JSON(503, marten.M{
"status": "unavailable",
"reason": "database unreachable",
})
}
return c.OK(marten.M{"status": "ok"})
},
}))
Request Tracing¶
app.Use(middleware.RequestID)
app.Use(middleware.Logger)
func handler(c *marten.Ctx) error {
id := c.RequestID()
log.Printf("[%s] Processing request", id)
// Propagate to downstream services
// req.Header.Set("X-Request-ID", id)
return c.OK(marten.M{"request_id": id})
}
Graceful Shutdown¶
func main() {
app := marten.New()
app.OnShutdown(func() {
db.Close()
log.Println("connections closed")
})
app.RunGraceful(":8080", 10*time.Second)
}
Rate Limiting¶
Timeout Protection¶
Service Structure¶
Example Service¶
package main
import (
"log"
"time"
"github.com/gomarten/marten"
"github.com/gomarten/marten/middleware"
)
func main() {
app := marten.New()
// Standard microservice middleware stack
app.Use(
middleware.Health("/health"), // Answer probes first
middleware.RequestID, // Assign trace ID
middleware.Logger, // Log all requests
middleware.Recover, // Catch panics
middleware.Timeout(5*time.Second), // Enforce SLA
middleware.RateLimit(middleware.RateLimitConfig{
Requests: 100,
Window: time.Minute,
}),
)
// Readiness probe with dependency check
app.Use(middleware.HealthWithConfig(middleware.HealthConfig{
Path: "/ready",
Handler: func(c *marten.Ctx) error {
if err := db.Ping(); err != nil {
return c.JSON(503, marten.M{"status": "unavailable"})
}
return c.OK(marten.M{"status": "ok"})
},
}))
// API
api := app.Group("/api/v1")
api.GET("/resource", getResource)
api.POST("/resource", createResource)
log.Println("Service running on :8080")
app.RunGraceful(":8080", 10*time.Second)
}
Async Job Pattern¶
Use c.Accepted() (202) when a request is queued for async processing:
func enqueueJob(c *marten.Ctx) error {
var req JobRequest
if err := c.Bind(&req); err != nil {
return c.BadRequest(err.Error())
}
jobID := queue.Enqueue(req)
return c.Accepted(marten.M{
"job_id": jobID,
"status": "queued",
})
}
Key Patterns¶
- Use
middleware.Health— not a manual route — so probes bypass rate limiting and logging - Use
middleware.Healthfor liveness,HealthWithConfigfor readiness (readiness can check dependencies) - Pass
c.Context()to database and HTTP calls so timeouts cancel in-flight work - Use
c.Accepted()for async job endpoints - Use
OnShutdownto close connections cleanly - Use request IDs for distributed tracing