Dilip Singh logo
All posts
Web DevelopmentAdvanced2026-03-20·12 min read

GoLang Microservices: Patterns from Building 12 Production Services

Battle-tested Go patterns for microservices — context propagation, graceful shutdown, structured logging, circuit breakers, gRPC + HTTP duality, and the boring infrastructure that wins.

Why Go for Microservices

After shipping 12 Go services in production at Hureka, the pattern is clear: Go's simplicity, single binary deployment, and goroutine concurrency make it ideal for I/O-bound services that need to be cheap to run and easy to reason about.

Service Skeleton

go
package main

import ( "context" "errors" "log/slog" "net/http" "os" "os/signal" "syscall" "time" )

func main() { logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) slog.SetDefault(logger)

srv := &http.Server{ Addr: ":" + getEnv("PORT", "8080"), Handler: buildRouter(), ReadTimeout: 5 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, }

go func() { slog.Info("server starting", "addr", srv.Addr) if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { slog.Error("server failed", "err", err) os.Exit(1) } }()

quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit

slog.Info("shutting down") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { slog.Error("graceful shutdown failed", "err", err) } } ```

Context Propagation Everywhere

go
func (h *Handler) HandleRequest(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
    defer cancel()

// Propagate request_id from headers if rid := r.Header.Get("X-Request-ID"); rid != "" { ctx = context.WithValue(ctx, requestIDKey, rid) }

result, err := h.service.Process(ctx, payload) if err != nil { if errors.Is(err, context.DeadlineExceeded) { http.Error(w, "timeout", http.StatusGatewayTimeout) return } http.Error(w, "internal", http.StatusInternalServerError) return } json.NewEncoder(w).Encode(result) } ```

Circuit Breaker for External Calls

go
import "github.com/sony/gobreaker/v2"

cb := gobreaker.NewCircuitBreaker[Response](gobreaker.Settings{ Name: "billing-api", MaxRequests: 5, Interval: 30 * time.Second, Timeout: 60 * time.Second, ReadyToTrip: func(c gobreaker.Counts) bool { return c.ConsecutiveFailures > 5 }, })

result, err := cb.Execute(func() (Response, error) { return billingClient.Charge(ctx, req) }) ```

Structured Logging

go
slog.InfoContext(ctx, "payment processed",
    "amount", req.Amount,
    "currency", req.Currency,
    "tenant_id", tenantID,
    "duration_ms", time.Since(start).Milliseconds(),
)

gRPC + HTTP from the Same Process

go
import "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"

mux := runtime.NewServeMux() pb.RegisterUserServiceHandlerServer(ctx, mux, userServer)

mainMux := http.NewServeMux() mainMux.Handle("/api/", mux) // REST via gateway mainMux.HandleFunc("/health", healthHandler)

http.ListenAndServe(":8080", mainMux) go grpcServer.Serve(grpcListener) ```

Operational Wins

  1. 1Statically linked binary — One file, no runtime, fits in a 20MB distroless image
  2. 2Built-in pprof — `net/http/pprof` for live CPU/memory profiling in production
  3. 3Cheap goroutines — Spawn freely; a goroutine is ~2KB
  4. 4Predictable GC — No long pauses; Go's GC is sub-millisecond at typical loads
DS
Dilip Singh
Lead Software Architect · Hureka Technologies

14+ years building enterprise software and AI systems. Architecting multi-agent AI platforms, RAG pipelines, voice AI, and high-performance SaaS for global clients.