Microservices

🏗️ Microservices Architecture Theory

Microservices architecture is a design approach where applications are built as a collection of loosely coupled, independently deployable services. Each service owns its data and business logic, communicating through well-defined APIs.

API Gateway Routing • Auth • Rate Limiting Service Mesh (Istio) User Service • Authentication • Profile Mgmt DB Order Service • Order Processing • Payment Logic DB Inventory Service • Stock Management • Warehouse Logic DB Notification Service • Email/SMS • Push Notifications DB Message Queue (RabbitMQ) Async Communication Service Discovery (Consul) Monitoring (Prometheus) Config Store (etcd/Vault) Tracing (Jaeger)

🎯 Key Principles

🔗
Loose Coupling

Services communicate through well-defined APIs, minimizing dependencies and enabling independent development and deployment.

🎯
Single Responsibility

Each service focuses on a specific business capability, following the Single Responsibility Principle from SOLID design.

🔄
Decentralized Governance

Teams own their services end-to-end, making technology and architectural decisions independently.

💾
Data Ownership

Each service owns and manages its own data, avoiding shared databases and ensuring data consistency.

🚀 Advanced gRPC Patterns

Implement production-ready gRPC services with advanced patterns including streaming, authentication, and service mesh integration.

// user.proto
syntax = "proto3";

package user;
option go_package = "github.com/example/user";

service UserService {
    rpc GetUser(GetUserRequest) returns (User);
    rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
    rpc CreateUser(CreateUserRequest) returns (User);
    rpc UpdateUser(UpdateUserRequest) returns (User);
}

message User {
    string id = 1;
    string email = 2;
    string name = 3;
    int64 created_at = 4;
}

message GetUserRequest {
    string id = 1;
}

message ListUsersRequest {
    int32 page = 1;
    int32 limit = 2;
}

message ListUsersResponse {
    repeated User users = 1;
    int32 total = 2;
}
// server.go
package main

import (
    "context"
    "log"
    "net"
    
    "google.golang.org/grpc"
    pb "github.com/example/user"
)

type userServer struct {
    pb.UnimplementedUserServiceServer
    db *Database
}

func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    user, err := s.db.GetUser(req.Id)
    if err != nil {
        return nil, err
    }
    
    return &pb.User{
        Id:        user.ID,
        Email:     user.Email,
        Name:      user.Name,
        CreatedAt: user.CreatedAt.Unix(),
    }, nil
}

func (s *userServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) {
    user := &User{
        Email: req.Email,
        Name:  req.Name,
    }
    
    if err := s.db.CreateUser(user); err != nil {
        return nil, err
    }
    
    return &pb.User{
        Id:        user.ID,
        Email:     user.Email,
        Name:      user.Name,
        CreatedAt: user.CreatedAt.Unix(),
    }, nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    
    s := grpc.NewServer()
    pb.RegisterUserServiceServer(s, &userServer{db: NewDatabase()})
    
    log.Println("gRPC server starting on :50051")
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

🌐 Enterprise API Gateway

Build a comprehensive API gateway with advanced features including rate limiting, circuit breaking, and request/response transformation.

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
)

type ProductAPI struct {
    service ProductService
    validate *validator.Validate
}

func NewProductAPI() *gin.Engine {
    r := gin.New()
    r.Use(gin.Logger())
    r.Use(gin.Recovery())
    r.Use(CORSMiddleware())
    
    api := &ProductAPI{
        service: NewProductService(),
        validate: validator.New(),
    }
    
    // Health check
    r.GET("/health", api.HealthCheck)
    
    // Product routes
    v1 := r.Group("/api/v1")
    {
        products := v1.Group("/products")
        products.Use(AuthMiddleware())
        {
            products.GET("", api.ListProducts)
            products.GET("/:id", api.GetProduct)
            products.POST("", api.CreateProduct)
            products.PUT("/:id", api.UpdateProduct)
            products.DELETE("/:id", api.DeleteProduct)
        }
    }
    
    return r
}

func (api *ProductAPI) ListProducts(c *gin.Context) {
    var query ListProductsQuery
    if err := c.ShouldBindQuery(&query); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    products, total, err := api.service.ListProducts(c, query)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch products"})
        return
    }
    
    c.JSON(http.StatusOK, gin.H{
        "products": products,
        "total":    total,
        "page":     query.Page,
        "limit":    query.Limit,
    })
}

func (api *ProductAPI) CreateProduct(c *gin.Context) {
    var req CreateProductRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    if err := api.validate.Struct(req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    product, err := api.service.CreateProduct(c, req)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create product"})
        return
    }
    
    c.JSON(http.StatusCreated, product)
}

🔍 Dynamic Service Discovery

Implement advanced service discovery with health checking, load balancing, and service mesh integration.

package discovery

import (
    "fmt"
    "github.com/hashicorp/consul/api"
)

type ServiceDiscovery struct {
    client *api.Client
    serviceID string
}

func NewServiceDiscovery(addr string) (*ServiceDiscovery, error) {
    config := api.DefaultConfig()
    config.Address = addr
    
    client, err := api.NewClient(config)
    if err != nil {
        return nil, err
    }
    
    return &ServiceDiscovery{
        client: client,
    }, nil
}

func (sd *ServiceDiscovery) RegisterService(name, address string, port int) error {
    sd.serviceID = fmt.Sprintf("%s-%s-%d", name, address, port)
    
    registration := &api.AgentServiceRegistration{
        ID:      sd.serviceID,
        Name:    name,
        Address: address,
        Port:    port,
        Check: &api.AgentServiceCheck{
            HTTP:                           fmt.Sprintf("http://%s:%d/health", address, port),
            Interval:                       "10s",
            Timeout:                        "2s",
            DeregisterCriticalServiceAfter: "30s",
        },
    }
    
    return sd.client.Agent().ServiceRegister(registration)
}

func (sd *ServiceDiscovery) DiscoverService(name string) ([]*api.ServiceEntry, error) {
    services, _, err := sd.client.Health().Service(name, "", true, nil)
    return services, err
}

func (sd *ServiceDiscovery) Deregister() error {
    if sd.serviceID != "" {
        return sd.client.Agent().ServiceDeregister(sd.serviceID)
    }
    return nil
}

⚡ Resilience Patterns

Implement comprehensive resilience patterns including circuit breaker, retry with backoff, and bulkhead isolation.

package circuit

import (
    "errors"
    "sync"
    "time"
)

type State int

const (
    StateClosed State = iota
    StateOpen
    StateHalfOpen
)

type CircuitBreaker struct {
    mu              sync.RWMutex
    state           State
    failures        int
    successes       int
    lastFailureTime time.Time
    
    maxFailures     int
    timeout         time.Duration
    successThreshold int
}

func NewCircuitBreaker(maxFailures int, timeout time.Duration) *CircuitBreaker {
    return &CircuitBreaker{
        state:            StateClosed,
        maxFailures:      maxFailures,
        timeout:          timeout,
        successThreshold: 2,
    }
}

func (cb *CircuitBreaker) Call(fn func() error) error {
    cb.mu.Lock()
    defer cb.mu.Unlock()
    
    // Check current state
    switch cb.state {
    case StateOpen:
        if time.Since(cb.lastFailureTime) > cb.timeout {
            cb.state = StateHalfOpen
            cb.successes = 0
        } else {
            return errors.New("circuit breaker is open")
        }
    }
    
    // Execute function
    err := fn()
    
    if err != nil {
        cb.onFailure()
        return err
    }
    
    cb.onSuccess()
    return nil
}

func (cb *CircuitBreaker) onSuccess() {
    cb.failures = 0
    
    if cb.state == StateHalfOpen {
        cb.successes++
        if cb.successes >= cb.successThreshold {
            cb.state = StateClosed
        }
    }
}

func (cb *CircuitBreaker) onFailure() {
    cb.failures++
    cb.lastFailureTime = time.Now()
    cb.successes = 0
    
    if cb.failures >= cb.maxFailures {
        cb.state = StateOpen
    }
}

📨 Event-Driven Architecture

Build robust event-driven systems with message queues, event sourcing, and saga patterns for distributed transactions.

package messaging

import (
    "encoding/json"
    "log"
    
    "github.com/streadway/amqp"
)

type MessageBroker struct {
    conn    *amqp.Connection
    channel *amqp.Channel
}

func NewMessageBroker(url string) (*MessageBroker, error) {
    conn, err := amqp.Dial(url)
    if err != nil {
        return nil, err
    }
    
    ch, err := conn.Channel()
    if err != nil {
        return nil, err
    }
    
    return &MessageBroker{
        conn:    conn,
        channel: ch,
    }, nil
}

func (mb *MessageBroker) Publish(exchange, routingKey string, message interface{}) error {
    body, err := json.Marshal(message)
    if err != nil {
        return err
    }
    
    return mb.channel.Publish(
        exchange,
        routingKey,
        false,
        false,
        amqp.Publishing{
            ContentType: "application/json",
            Body:        body,
        },
    )
}

func (mb *MessageBroker) Subscribe(queue string, handler func([]byte) error) error {
    msgs, err := mb.channel.Consume(
        queue,
        "",
        false, // auto-ack
        false,
        false,
        false,
        nil,
    )
    if err != nil {
        return err
    }
    
    go func() {
        for msg := range msgs {
            if err := handler(msg.Body); err != nil {
                log.Printf("Error processing message: %v", err)
                msg.Nack(false, true) // requeue
            } else {
                msg.Ack(false)
            }
        }
    }()
    
    return nil
}

func (mb *MessageBroker) Close() {
    mb.channel.Close()
    mb.conn.Close()
}

🔍 Observability Stack

Implement comprehensive observability with distributed tracing, metrics collection, and centralized logging.

package tracing

import (
    "context"
    
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/jaeger"
    "go.opentelemetry.io/otel/propagation"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)

func InitTracer(serviceName, jaegerURL string) (*sdktrace.TracerProvider, error) {
    exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(jaegerURL)))
    if err != nil {
        return nil, err
    }
    
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exp),
        sdktrace.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String(serviceName),
        )),
    )
    
    otel.SetTracerProvider(tp)
    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{},
        propagation.Baggage{},
    ))
    
    return tp, nil
}

// Middleware for tracing HTTP requests
func TracingMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        tracer := otel.Tracer("http-server")
        
        ctx, span := tracer.Start(c.Request.Context(), c.Request.URL.Path)
        defer span.End()
        
        c.Request = c.Request.WithContext(ctx)
        c.Next()
        
        span.SetAttributes(
            semconv.HTTPMethodKey.String(c.Request.Method),
            semconv.HTTPStatusCodeKey.Int(c.Writer.Status()),
        )
    }
}

🏗️ Advanced Gateway Patterns

Implement advanced gateway patterns including request aggregation, response caching, and backend for frontend (BFF) pattern.

package gateway

import (
    "net/http"
    "net/http/httputil"
    "net/url"
    "strings"
)

type Gateway struct {
    services map[string]*url.URL
    proxies  map[string]*httputil.ReverseProxy
}

func NewGateway() *Gateway {
    return &Gateway{
        services: make(map[string]*url.URL),
        proxies:  make(map[string]*httputil.ReverseProxy),
    }
}

func (g *Gateway) RegisterService(name, addr string) error {
    serviceURL, err := url.Parse(addr)
    if err != nil {
        return err
    }
    
    g.services[name] = serviceURL
    g.proxies[name] = httputil.NewSingleHostReverseProxy(serviceURL)
    
    // Add retry logic
    g.proxies[name].ModifyResponse = func(r *http.Response) error {
        if r.StatusCode >= 500 {
            // Implement retry logic here
        }
        return nil
    }
    
    return nil
}

func (g *Gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // Extract service name from path
    path := r.URL.Path
    parts := strings.Split(path, "/")
    
    if len(parts) < 2 {
        http.NotFound(w, r)
        return
    }
    
    serviceName := parts[1]
    proxy, exists := g.proxies[serviceName]
    if !exists {
        http.NotFound(w, r)
        return
    }
    
    // Rewrite path
    r.URL.Path = "/" + strings.Join(parts[2:], "/")
    
    // Add headers
    r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
    r.Host = g.services[serviceName].Host
    
    proxy.ServeHTTP(w, r)
}

🏛️ Advanced Microservices Patterns

🔄
Saga Pattern

Manage distributed transactions across multiple services using choreography or orchestration.

type OrderSaga struct {
    steps []SagaStep
    compensations []CompensationStep
}

func (s *OrderSaga) Execute(ctx context.Context) error {
    for i, step := range s.steps {
        if err := step.Execute(ctx); err != nil {
            s.compensate(ctx, i-1)
            return err
        }
    }
    return nil
}
🔐
Strangler Fig Pattern

Gradually replace legacy monoliths by incrementally routing traffic to new microservices.

type StranglerProxy struct {
    legacyService   LegacyService
    microservices   map[string]Service
    migrationRules  []MigrationRule
}

func (p *StranglerProxy) Route(req *Request) {
    for _, rule := range p.migrationRules {
        if rule.Matches(req) {
            return p.microservices[rule.Service].Handle(req)
        }
    }
    return p.legacyService.Handle(req)
}
📊
CQRS + Event Sourcing

Separate read and write models while persisting events as the source of truth.

type EventStore interface {
    SaveEvents(aggregateID string, events []Event) error
    GetEvents(aggregateID string) ([]Event, error)
}

type ReadModelProjector struct {
    eventBus EventBus
    views    map[string]ReadModel
}

func (p *ReadModelProjector) Project(event Event) {
    for _, view := range p.views {
        view.Apply(event)
    }
}
🏠
Backend for Frontend (BFF)

Create specialized backends optimized for specific frontend requirements.

type MobileBFF struct {
    userService     UserService
    productService  ProductService
    orderService    OrderService
}

func (bff *MobileBFF) GetDashboard(userID string) *MobileDashboard {
    user := bff.userService.GetUser(userID)
    orders := bff.orderService.GetRecentOrders(userID, 5)
    recommendations := bff.productService.GetRecommendations(userID)
    
    return &MobileDashboard{
        User: user,
        RecentOrders: orders,
        Recommendations: recommendations,
    }
}

🛡️ Security Patterns

// Zero Trust Security Implementation
package security

import (
    "context"
    "crypto/x509"
    "github.com/golang-jwt/jwt/v4"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/metadata"
)

type ZeroTrustInterceptor struct {
    certs      *x509.CertPool
    jwtSecret  []byte
    policies   PolicyEngine
}

func (z *ZeroTrustInterceptor) UnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    // 1. Mutual TLS verification
    peerInfo, ok := peer.FromContext(ctx)
    if !ok || peerInfo.AuthInfo == nil {
        return nil, status.Error(codes.Unauthenticated, "no peer info")
    }
    
    // 2. JWT validation
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, status.Error(codes.Unauthenticated, "no metadata")
    }
    
    tokens := md.Get("authorization")
    if len(tokens) == 0 {
        return nil, status.Error(codes.Unauthenticated, "no token")
    }
    
    claims, err := z.validateJWT(tokens[0])
    if err != nil {
        return nil, status.Error(codes.Unauthenticated, err.Error())
    }
    
    // 3. Authorization policy check
    if !z.policies.Authorize(claims, info.FullMethod, req) {
        return nil, status.Error(codes.PermissionDenied, "access denied")
    }
    
    // 4. Context enrichment
    ctx = context.WithValue(ctx, "user", claims.Subject)
    ctx = context.WithValue(ctx, "permissions", claims.Permissions)
    
    return handler(ctx, req)
}

func (z *ZeroTrustInterceptor) validateJWT(token string) (*CustomClaims, error) {
    parsedToken, err := jwt.ParseWithClaims(token, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
        return z.jwtSecret, nil
    })
    
    if err != nil || !parsedToken.Valid {
        return nil, err
    }
    
    return parsedToken.Claims.(*CustomClaims), nil
}

// Service Mesh Security with Istio
type IstioSecurityConfig struct {
    PeerAuthentication   *PeerAuthenticationPolicy
    AuthorizationPolicy  *AuthorizationPolicy
    RequestAuthentication *RequestAuthenticationPolicy
}

// Example Istio configuration (YAML would be applied to k8s)
func (i *IstioSecurityConfig) GenerateConfig() string {
    return `
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: user-service-authz
  namespace: production
spec:
  selector:
    matchLabels:
      app: user-service
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/production/sa/api-gateway"]
  - to:
    - operation:
        methods: ["GET", "POST"]
`
}

📈 Performance Optimization Patterns

// Advanced Caching Strategy
package cache

import (
    "context"
    "encoding/json"
    "fmt"
    "time"
    
    "github.com/go-redis/redis/v8"
    "github.com/patrickmn/go-cache"
)

type MultiLevelCache struct {
    l1Cache    *cache.Cache           // In-memory cache
    l2Cache    *redis.Client          // Distributed cache
    l3Cache    PersistentStore        // Database
    stats      CacheStatistics
}

func NewMultiLevelCache() *MultiLevelCache {
    return &MultiLevelCache{
        l1Cache: cache.New(5*time.Minute, 10*time.Minute),
        l2Cache: redis.NewClient(&redis.Options{
            Addr: "localhost:6379",
        }),
        stats: NewCacheStatistics(),
    }
}

func (m *MultiLevelCache) Get(ctx context.Context, key string, dest interface{}) error {
    // L1 Cache (in-memory)
    if value, found := m.l1Cache.Get(key); found {
        m.stats.RecordHit("L1")
        return json.Unmarshal(value.([]byte), dest)
    }
    
    // L2 Cache (Redis)
    if value, err := m.l2Cache.Get(ctx, key).Bytes(); err == nil {
        m.stats.RecordHit("L2")
        // Populate L1 cache
        m.l1Cache.Set(key, value, cache.DefaultExpiration)
        return json.Unmarshal(value, dest)
    }
    
    // L3 Cache (Database)
    if err := m.l3Cache.Get(ctx, key, dest); err == nil {
        m.stats.RecordHit("L3")
        // Populate higher levels
        data, _ := json.Marshal(dest)
        m.l2Cache.Set(ctx, key, data, time.Hour)
        m.l1Cache.Set(key, data, cache.DefaultExpiration)
        return nil
    }
    
    m.stats.RecordMiss()
    return fmt.Errorf("key not found: %s", key)
}

// Connection Pool Optimization
type ServiceClient struct {
    pool     *ConnectionPool
    circuit  *CircuitBreaker
    metrics  MetricsCollector
}

type ConnectionPool struct {
    connections chan *grpc.ClientConn
    maxSize     int
    current     int
    factory     ConnectionFactory
}

func (p *ConnectionPool) Get(ctx context.Context) (*grpc.ClientConn, error) {
    select {
    case conn := <-p.connections:
        return conn, nil
    case <-ctx.Done():
        return nil, ctx.Err()
    default:
        if p.current < p.maxSize {
            return p.factory.Create()
        }
        // Wait for available connection
        select {
        case conn := <-p.connections:
            return conn, nil
        case <-ctx.Done():
            return nil, ctx.Err()
        }
    }
}

🔧 Production Best Practices

✅ Implementation Checklist

🚫 Anti-Patterns to Avoid

❌ Distributed Monolith

Creating services that are tightly coupled and must be deployed together defeats the purpose of microservices architecture.

❌ Shared Database

Multiple services sharing the same database creates tight coupling and prevents independent scaling.

❌ Synchronous Chain Calls

Long chains of synchronous service calls create cascading failures and poor performance.

❌ Nano-services

Creating too many tiny services increases operational overhead without significant benefits.

🎯 Microservices Comparison

Aspect Monolith Microservices Modular Monolith
Development Speed Fast initially, slows with growth Slower setup, faster parallel development Moderate, good for medium teams
Operational Complexity Low High (service mesh, discovery, monitoring) Medium
Scalability Vertical scaling only Independent horizontal scaling Selective scaling with effort
Technology Diversity Single stack Polyglot architecture Limited diversity
Testing Complexity Simple integration testing Complex (contract testing, e2e) Moderate
Fault Isolation Single point of failure Excellent isolation Module-level isolation
Data Consistency ACID transactions Eventual consistency, saga patterns ACID with careful design

🎯 Hands-on Challenges

Challenge 1: Build Event-Driven Order System

Create a microservices system for order processing:

Challenge 2: Service Mesh Implementation

Deploy your microservices with Istio service mesh:

Challenge 3: Advanced Monitoring Setup

Build comprehensive observability: