This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Real-World Examples

Production-ready patterns and complete application examples.

Real-World Examples

Production-ready patterns and complete application examples.

Available Examples

Coming Soon

We’re actively working on additional real-world examples. Contributions are welcome!

Complete Applications

  • Event-Driven Microservices - Pub/sub patterns
  • CQRS Pattern - Command Query Responsibility Segregation
  • Saga Pattern - Distributed transactions

Production Patterns

  • Health Checks and Readiness
  • Retry and Circuit Breaking
  • Distributed Tracing with OpenTelemetry
  • Structured Logging
  • Metrics and Monitoring

Testing Strategies

  • Unit Testing Services
  • Integration Testing
  • Contract Testing
  • Load Testing

Deployment

  • Kubernetes Deployment
  • Docker Compose Setup
  • CI/CD Pipeline Examples
  • Blue-Green Deployment

Integration Examples

  • PostgreSQL with Transactions
  • Redis Caching Strategies
  • Message Queue Integration
  • External API Integration

Each example will include:

  • Complete, runnable code
  • Configuration for development and production
  • Testing approach
  • Common pitfalls and solutions

Want to contribute? See our Contributing Guide.

1 - API Gateway with Backend Services

A complete example showing an API gateway routing to multiple backend microservices.

Architecture

                  ┌─────────────┐
   Client ───────>│ API Gateway │
                  └──────┬──────┘
                         │
          ┌──────────────┼──────────────┐
          │              │              │
    ┌─────▼────┐   ┌────▼─────┐  ┌────▼─────┐
    │  Users   │   │  Orders  │  │ Products │
    │ Service  │   │ Service  │  │ Service  │
    └──────────┘   └──────────┘  └──────────┘
          │              │              │
          └──────────────┼──────────────┘
                         │
                  ┌──────▼──────┐
                  │  PostgreSQL │
                  └─────────────┘

Services

1. Users Service

// services/users/main.go
package main

import (
    "context"
    "database/sql"
    "go-micro.dev/v6"
    "go-micro.dev/v6/server"
    _ "github.com/lib/pq"
)

type User struct {
    ID    int64  `json:"id"`
    Email string `json:"email"`
    Name  string `json:"name"`
}

type UsersService struct {
    db *sql.DB
}

type GetUserRequest struct {
    ID int64 `json:"id"`
}

type GetUserResponse struct {
    User *User `json:"user"`
}

func (s *UsersService) Get(ctx context.Context, req *GetUserRequest, rsp *GetUserResponse) error {
    var u User
    err := s.db.QueryRow("SELECT id, email, name FROM users WHERE id = $1", req.ID).
        Scan(&u.ID, &u.Email, &u.Name)
    if err != nil {
        return err
    }
    rsp.User = &u
    return nil
}

func main() {
    db, err := sql.Open("postgres", "postgres://user:pass@localhost/users?sslmode=disable")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    svc := micro.NewService("users",
        micro.Version("1.0.0"),
    )

    svc.Init()

    server.RegisterHandler(svc.Server(), &UsersService{db: db})

    if err := svc.Run(); err != nil {
        panic(err)
    }
}

2. Orders Service

// services/orders/main.go
package main

import (
    "context"
    "database/sql"
    "time"
    "go-micro.dev/v6"
    "go-micro.dev/v6/client"
    "go-micro.dev/v6/server"
)

type Order struct {
    ID        int64     `json:"id"`
    UserID    int64     `json:"user_id"`
    ProductID int64     `json:"product_id"`
    Amount    float64   `json:"amount"`
    Status    string    `json:"status"`
    CreatedAt time.Time `json:"created_at"`
}

type OrdersService struct {
    db     *sql.DB
    client client.Client
}

type CreateOrderRequest struct {
    UserID    int64   `json:"user_id"`
    ProductID int64   `json:"product_id"`
    Amount    float64 `json:"amount"`
}

type CreateOrderResponse struct {
    Order *Order `json:"order"`
}

func (s *OrdersService) Create(ctx context.Context, req *CreateOrderRequest, rsp *CreateOrderResponse) error {
    // Verify user exists
    userReq := s.client.NewRequest("users", "UsersService.Get", &struct{ ID int64 }{ID: req.UserID})
    userRsp := &struct{ User interface{} }{}
    if err := s.client.Call(ctx, userReq, userRsp); err != nil {
        return err
    }

    // Verify product exists
    prodReq := s.client.NewRequest("products", "ProductsService.Get", &struct{ ID int64 }{ID: req.ProductID})
    prodRsp := &struct{ Product interface{} }{}
    if err := s.client.Call(ctx, prodReq, prodRsp); err != nil {
        return err
    }

    // Create order
    var o Order
    err := s.db.QueryRow(`
        INSERT INTO orders (user_id, product_id, amount, status, created_at)
        VALUES ($1, $2, $3, $4, $5)
        RETURNING id, user_id, product_id, amount, status, created_at
    `, req.UserID, req.ProductID, req.Amount, "pending", time.Now()).
        Scan(&o.ID, &o.UserID, &o.ProductID, &o.Amount, &o.Status, &o.CreatedAt)

    if err != nil {
        return err
    }

    rsp.Order = &o
    return nil
}

func main() {
    db, err := sql.Open("postgres", "postgres://user:pass@localhost/orders?sslmode=disable")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    svc := micro.NewService("orders",
        micro.Version("1.0.0"),
    )

    svc.Init()

    server.RegisterHandler(svc.Server(), &OrdersService{
        db:     db,
        client: svc.Client(),
    })

    if err := svc.Run(); err != nil {
        panic(err)
    }
}

3. API Gateway

// gateway/main.go
package main

import (
    "encoding/json"
    "net/http"
    "strconv"
    "go-micro.dev/v6"
    "go-micro.dev/v6/client"
)

type Gateway struct {
    client client.Client
}

func (g *Gateway) GetUser(w http.ResponseWriter, r *http.Request) {
    idStr := r.URL.Query().Get("id")
    id, err := strconv.ParseInt(idStr, 10, 64)
    if err != nil {
        http.Error(w, "invalid id", http.StatusBadRequest)
        return
    }

    req := g.client.NewRequest("users", "UsersService.Get", &struct{ ID int64 }{ID: id})
    rsp := &struct{ User interface{} }{}

    if err := g.client.Call(r.Context(), req, rsp); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(rsp)
}

func (g *Gateway) CreateOrder(w http.ResponseWriter, r *http.Request) {
    var body struct {
        UserID    int64   `json:"user_id"`
        ProductID int64   `json:"product_id"`
        Amount    float64 `json:"amount"`
    }

    if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
        http.Error(w, "invalid request", http.StatusBadRequest)
        return
    }

    req := g.client.NewRequest("orders", "OrdersService.Create", body)
    rsp := &struct{ Order interface{} }{}

    if err := g.client.Call(r.Context(), req, rsp); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(rsp)
}

func main() {
    svc := micro.NewService("api.gateway",
    )
    svc.Init()

    gw := &Gateway{client: svc.Client()}

    http.HandleFunc("/users", gw.GetUser)
    http.HandleFunc("/orders", gw.CreateOrder)

    http.ListenAndServe(":8080", nil)
}

Running the Example

Development (Local)

# Terminal 1: Users service
cd services/users
go run main.go

# Terminal 2: Products service
cd services/products
go run main.go

# Terminal 3: Orders service
cd services/orders
go run main.go

# Terminal 4: API Gateway
cd gateway
go run main.go

Testing

# Get user
curl http://localhost:8080/users?id=1

# Create order
curl -X POST http://localhost:8080/orders \
  -H 'Content-Type: application/json' \
  -d '{"user_id": 1, "product_id": 100, "amount": 99.99}'

Docker Compose

version: '3.8'

services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    ports:
      - "5432:5432"

  users:
    build: ./services/users
    environment:
      MICRO_REGISTRY: nats
      MICRO_REGISTRY_ADDRESS: nats://nats:4222
      DATABASE_URL: postgres://postgres:secret@postgres/users
    depends_on:
      - postgres
      - nats

  products:
    build: ./services/products
    environment:
      MICRO_REGISTRY: nats
      MICRO_REGISTRY_ADDRESS: nats://nats:4222
      DATABASE_URL: postgres://postgres:secret@postgres/products
    depends_on:
      - postgres
      - nats

  orders:
    build: ./services/orders
    environment:
      MICRO_REGISTRY: nats
      MICRO_REGISTRY_ADDRESS: nats://nats:4222
      DATABASE_URL: postgres://postgres:secret@postgres/orders
    depends_on:
      - postgres
      - nats

  gateway:
    build: ./gateway
    ports:
      - "8080:8080"
    environment:
      MICRO_REGISTRY: nats
      MICRO_REGISTRY_ADDRESS: nats://nats:4222
    depends_on:
      - users
      - products
      - orders

  nats:
    image: nats:latest
    ports:
      - "4222:4222"

Run with:

docker-compose up

Key Patterns

  1. Service isolation: Each service owns its database
  2. Service communication: Via Go Micro client
  3. Gateway pattern: Single entry point for clients
  4. Error handling: Proper HTTP status codes
  5. Registry: mDNS for local, NATS for Docker

Production Considerations

  • Add authentication/authorization
  • Implement request tracing
  • Add circuit breakers for service calls
  • Use connection pooling
  • Add rate limiting
  • Implement proper logging
  • Use health checks
  • Add metrics collection

See Production Patterns for more details.

2 - Graceful Shutdown

Properly shutting down services to avoid dropped requests and data loss.

The Problem

Without graceful shutdown:

  • In-flight requests are dropped
  • Database connections leak
  • Resources aren’t cleaned up
  • Load balancers don’t know service is down

Solution

Go Micro handles SIGTERM/SIGINT by default, but you need to implement cleanup logic.

Basic Pattern

package main

import (
    "context"
    "os"
    "os/signal"
    "syscall"
    "time"
    "go-micro.dev/v6"
    "go-micro.dev/v6/logger"
)

func main() {
    svc := micro.NewService("myservice",
        micro.BeforeStop(func() error {
            logger.Info("Service stopping, running cleanup...")
            return cleanup()
        }),
    )

    svc.Init()

    // Your service logic
    if err := svc.Handle(new(Handler)); err != nil {
        logger.Fatal(err)
    }

    // Run with graceful shutdown
    if err := svc.Run(); err != nil {
        logger.Fatal(err)
    }

    logger.Info("Service stopped gracefully")
}

func cleanup() error {
    // Close database connections
    // Flush logs
    // Stop background workers
    // etc.
    return nil
}

Database Cleanup

type Service struct {
    db *sql.DB
}

func (s *Service) Shutdown(ctx context.Context) error {
    logger.Info("Closing database connections...")
    
    // Stop accepting new requests
    s.db.SetMaxOpenConns(0)
    
    // Wait for existing connections to finish (with timeout)
    done := make(chan struct{})
    go func() {
        s.db.Close()
        close(done)
    }()
    
    select {
    case <-done:
        logger.Info("Database closed gracefully")
        return nil
    case <-ctx.Done():
        logger.Warn("Database close timeout, forcing")
        return ctx.Err()
    }
}

Background Workers

type Worker struct {
    quit chan struct{}
    done chan struct{}
}

func (w *Worker) Start() {
    w.quit = make(chan struct{})
    w.done = make(chan struct{})
    
    go func() {
        defer close(w.done)
        ticker := time.NewTicker(5 * time.Second)
        defer ticker.Stop()
        
        for {
            select {
            case <-ticker.C:
                w.doWork()
            case <-w.quit:
                logger.Info("Worker stopping...")
                return
            }
        }
    }()
}

func (w *Worker) Stop(timeout time.Duration) error {
    close(w.quit)
    
    select {
    case <-w.done:
        logger.Info("Worker stopped gracefully")
        return nil
    case <-time.After(timeout):
        return fmt.Errorf("worker shutdown timeout")
    }
}

Complete Example

package main

import (
    "context"
    "database/sql"
    "fmt"
    "os"
    "os/signal"
    "sync"
    "syscall"
    "time"
    
    "go-micro.dev/v6"
    "go-micro.dev/v6/logger"
)

type Application struct {
    db      *sql.DB
    workers []*Worker
    wg      sync.WaitGroup
    mu      sync.RWMutex
    closing bool
}

func NewApplication(db *sql.DB) *Application {
    return &Application{
        db:      db,
        workers: make([]*Worker, 0),
    }
}

func (app *Application) AddWorker(w *Worker) {
    app.workers = append(app.workers, w)
    w.Start()
}

func (app *Application) Shutdown(ctx context.Context) error {
    app.mu.Lock()
    if app.closing {
        app.mu.Unlock()
        return nil
    }
    app.closing = true
    app.mu.Unlock()
    
    logger.Info("Starting graceful shutdown...")
    
    // Stop accepting new work
    logger.Info("Stopping workers...")
    for _, w := range app.workers {
        if err := w.Stop(5 * time.Second); err != nil {
            logger.Warnf("Worker failed to stop: %v", err)
        }
    }
    
    // Wait for in-flight requests (with timeout)
    shutdownComplete := make(chan struct{})
    go func() {
        app.wg.Wait()
        close(shutdownComplete)
    }()
    
    select {
    case <-shutdownComplete:
        logger.Info("All requests completed")
    case <-ctx.Done():
        logger.Warn("Shutdown timeout, forcing...")
    }
    
    // Close resources
    logger.Info("Closing database...")
    if err := app.db.Close(); err != nil {
        logger.Errorf("Database close error: %v", err)
    }
    
    logger.Info("Shutdown complete")
    return nil
}

func main() {
    db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
    if err != nil {
        logger.Fatal(err)
    }
    
    app := NewApplication(db)
    
    // Add background workers
    app.AddWorker(&Worker{name: "cleanup"})
    app.AddWorker(&Worker{name: "metrics"})
    
    svc := micro.NewService("myservice",
        micro.BeforeStop(func() error {
            ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
            defer cancel()
            return app.Shutdown(ctx)
        }),
    )
    
    svc.Init()
    
    handler := &Handler{app: app}
    if err := svc.Handle(handler); err != nil {
        logger.Fatal(err)
    }
    
    // Run service
    if err := svc.Run(); err != nil {
        logger.Fatal(err)
    }
}

Kubernetes Integration

Liveness and Readiness Probes

func (h *Handler) Health(ctx context.Context, req *struct{}, rsp *HealthResponse) error {
    // Liveness: is the service alive?
    rsp.Status = "ok"
    return nil
}

func (h *Handler) Ready(ctx context.Context, req *struct{}, rsp *ReadyResponse) error {
    h.app.mu.RLock()
    closing := h.app.closing
    h.app.mu.RUnlock()
    
    if closing {
        // Stop receiving traffic during shutdown
        return fmt.Errorf("shutting down")
    }
    
    // Check dependencies
    if err := h.app.db.Ping(); err != nil {
        return fmt.Errorf("database unhealthy: %w", err)
    }
    
    rsp.Status = "ready"
    return nil
}

Kubernetes Manifest

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myservice
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: myservice
        image: myservice:latest
        ports:
        - containerPort: 8080
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        lifecycle:
          preStop:
            exec:
              # Give service time to drain before SIGTERM
              command: ["/bin/sh", "-c", "sleep 10"]
      terminationGracePeriodSeconds: 40

Best Practices

  1. Set timeouts: Don’t wait forever for shutdown
  2. Stop accepting work early: Set readiness to false
  3. Drain in-flight requests: Let current work finish
  4. Close resources properly: Databases, file handles, etc.
  5. Log shutdown progress: Help debugging
  6. Handle SIGTERM and SIGINT: Kubernetes sends SIGTERM
  7. Coordinate with load balancer: Use readiness probes
  8. Test shutdown: Regularly test graceful shutdown works

Testing Shutdown

# Start service
go run main.go &
PID=$!

# Send some requests
for i in {1..10}; do
    curl http://localhost:8080/endpoint &
done

# Trigger graceful shutdown
kill -TERM $PID

# Verify all requests completed
wait

Common Pitfalls

  • No timeout: Service hangs during shutdown
  • Not stopping workers: Background jobs continue
  • Database leaks: Connections not closed
  • Ignored signals: Service killed forcefully
  • No readiness probe: Traffic during shutdown