Skip to content
All essays
ArchitectureMarch 20, 202522 min

Microservices Architecture: Service Mesh, API Gateway & Patterns

Master microservices architecture. Learn service mesh, API gateway patterns, inter-service communication, and 2025 best practices.

Ü
Ümit Uz
Mobile & Full Stack Developer

Introduction to Microservices

Microservices architecture structures an application as a collection of loosely coupled, independently deployable services. In 2025, microservices have evolved with cloud-native technologies, service mesh, and GraphQL APIs.

Monolith vs Microservices:

Monolithic Architecture:
+----------------------------------+
|         Single Application       |
|  +----+ +----+ +----+ +----+    |
|  |Auth| |User| |Order| |Pay |    |
|  |    | |    | |    | |    |    |
|  +----+ +----+ +----+ +----+    |
|         Shared Database          |
+----------------------------------+

Microservices Architecture:
+--------+  +--------+  +--------+
|  Auth  |  |  User  |  | Order  |
|Service |  |Service |  |Service |
+--------+  +--------+  +--------+
    |           |           |
    +-----------+-----------+
                |
        +---------------+
        |  API Gateway  |
        +---------------+

Benefits:
- Independent deployment
- Technology diversity
- Scalability per service
- Fault isolation
- Team autonomy

Challenges:
- Network latency
- Distributed complexity
- Data consistency
- Service discovery
- Monitoring & debugging

API Gateway Patterns

API Gateway as a Facade

API Gateway Architecture:
                    Client
                      |
                      v
              +---------------+
              |  API Gateway  |
              +---------------+
                      |
          +-----------+-----------+
          |           |           |
    +-----+-----+ +--+--+ +--+---+
    |   Auth    | |User | |Order |
    | Service   | |Svc  | |Svc   |
    +-----------+ +-----+ +------+

Responsibilities:
- Request routing
- API composition
- Authentication & authorization
- Rate limiting
- Response aggregation
- Protocol translation
typescript
import { APIGateway } from '@aws-sdk/client-apigateway';
import { Lambda } from '@aws-sdk/client-lambda';

class APIGatewayService {
  private gateway: APIGateway;
  private lambda: Lambda;
  private routes: Map<string, RouteConfig>;

  constructor() {
    this.routes = new Map();
    this.setupRoutes();
  }

  private setupRoutes(): void {
    // Authentication routes
    this.routes.set('/auth/login', {
      service: 'auth-service',
      method: 'POST',
      rateLimit: 10,  // 10 requests per minute
      authRequired: false
    });

    // User routes
    this.routes.set('/users/{id}', {
      service: 'user-service',
      method: 'GET',
      rateLimit: 100,
      authRequired: true
    });

    // Order routes
    this.routes.set('/orders', {
      service: 'order-service',
      method: 'POST',
      rateLimit: 50,
      authRequired: true
    });
  }

  async handleRequest(request: Request): Promise<Response> {
    // Extract route
    const route = this.matchRoute(request.url, request.method);

    if (!route) {
      return new Response('Not Found', { status: 404 });
    }

    // Check authentication
    if (route.authRequired) {
      const authResult = await this.authenticate(request);
      if (!authResult.valid) {
        return new Response('Unauthorized', { status: 401 });
      }
      request.user = authResult.user;
    }

    // Apply rate limiting
    const rateLimitResult = await this.checkRateLimit(
      request,
      route.rateLimit
    );

    if (!rateLimitResult.allowed) {
      return new Response('Too Many Requests', {
        status: 429,
        headers: {
          'Retry-After': rateLimitResult.retryAfter.toString()
        }
      });
    }

    // Route to backend service
    return await this.proxyToService(route.service, request);
  }

  private async authenticate(
    request: Request
  ): Promise<{ valid: boolean; user?: any }> {
    const token = request.headers.get('Authorization')?.replace('Bearer ', '');

    if (!token) {
      return { valid: false };
    }

    try {
      // Validate JWT token
      const payload = await this.verifyToken(token);
      return { valid: true, user: payload };
    } catch (error) {
      return { valid: false };
    }
  }

  private async checkRateLimit(
    request: Request,
    limit: number
  ): Promise<{ allowed: boolean; retryAfter?: number }> {
    const clientId = this.getClientId(request);
    const key = `rate_limit:${clientId}`;

    const current = await this.redis.incr(key);

    if (current === 1) {
      await this.redis.expire(key, 60);  // 1 minute window
    }

    if (current > limit) {
      const ttl = await this.redis.ttl(key);
      return { allowed: false, retryAfter: ttl };
    }

    return { allowed: true };
  }

  private async proxyToService(
    serviceName: string,
    request: Request
  ): Promise<Response> {
    const serviceUrl = this.getServiceUrl(serviceName);

    try {
      const response = await fetch(serviceUrl + request.url, {
        method: request.method,
        headers: request.headers,
        body: request.body
      });

      return response;
    } catch (error) {
      // Circuit breaker pattern
      await this.circuitBreaker.recordFailure(serviceName);

      return new Response('Service Unavailable', { status: 503 });
    }
  }
}

BFF (Backend for Frontend) Pattern

BFF Architecture:
Mobile Client      Web Client
    |                 |
    v                 v
+-------+         +-------+
| Mobile |         |  Web  |
|  BFF  |         |  BFF  |
+-------+         +-------+
    |                 |
    +-------+---------+
            |
    +-------+-------+
    |  Microservices |
    +---------------+

Benefits:
- Optimized data format per client
- Reduced network calls
- Client-specific logic
- Better mobile performance
typescript
class MobileBFF {
  async getDashboard(userId: string): Promise<MobileDashboard> {
    // Parallel calls to multiple services
    const [user, orders, notifications] = await Promise.all([
      this.userService.getProfile(userId),
      this.orderService.getRecentOrders(userId, 5),
      this.notificationService.getUnread(userId)
    ]);

    // Mobile-optimized response
    return {
      user: {
        name: user.name,
        avatar: user.avatar  // Thumbnail size
      },
      orders: orders.map(o => ({
        id: o.id,
        status: o.status,
        total: o.total,
        items: o.items.length  // Just count for mobile
      })),
      notificationCount: notifications.length
    };
  }
}

class WebBFF {
  async getDashboard(userId: string): Promise<WebDashboard> {
    const [user, orders, notifications] = await Promise.all([
      this.userService.getProfile(userId),
      this.orderService.getRecentOrders(userId, 20),
      this.notificationService.getAll(userId)
    ]);

    // Web-optimized response with full details
    return {
      user: {
        name: user.name,
        email: user.email,
        avatar: user.avatar,  // Full resolution
        preferences: user.preferences
      },
      orders: orders.map(o => ({
        ...o,
        items: o.items.map(item => ({
          ...item,
          product: {
            name: item.product.name,
            images: item.product.images,  // All images
            description: item.product.description
          }
        }))
      })),
      notifications: notifications.map(n => ({
        ...n,
        fullMessage: n.message
      }))
    };
  }
}

Service Mesh

What is Service Mesh?

Service Mesh Architecture:
              Service Mesh (Istio/Linkerd)
                    |
    +---------------+---------------+
    |               |               |
Service A    Service B    Service C
    |               |               |
Sidecar       Sidecar       Sidecar
(Envoy)       (Envoy)       (Envoy)
    |               |               |
    +---------------+---------------+
                    |
              Network/Infrastructure

Features:
- Service discovery
- Load balancing
- Traffic management
- Security (mTLS)
- Observability (metrics, traces, logs)
- Resilience (retries, timeouts, circuit breaking)

Implementing Service Mesh with Istio

yaml
# VirtualService for traffic management
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: user-service
spec:
  hosts:
  - user-service
  http:
  - match:
    - headers:
        x-canary:
          exact: "true"
    route:
    - destination:
        host: user-service
        subset: v2
      weight: 100
  - route:
    - destination:
        host: user-service
        subset: v1
      weight: 90
    - destination:
        host: user-service
        subset: v2
      weight: 10
---
# DestinationRule for subsets
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: user-service
spec:
  host: user-service
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---
# ServiceEntry for external services
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: external-api
spec:
  hosts:
  - api.external.com
  ports:
  - number: 443
    name: https
    protocol: HTTPS
  location: MESH_EXTERNAL
  resolution: DNS

Traffic Management Patterns

typescript
class IstioTrafficManager {
  async deployCanary(
    serviceName: string,
    newVersion: string,
    initialTraffic: number
  ): Promise<void> {
    // Create new deployment
    await this.k8s.apply({
      apiVersion: 'apps/v1',
      kind: 'Deployment',
      metadata: { name: `${serviceName}-${newVersion}` },
      spec: {
        replicas: 1,
        selector: {
          matchLabels: { version: newVersion }
        },
        template: {
          metadata: {
            labels: {
              app: serviceName,
              version: newVersion
            }
          },
          spec: {
            containers: [{
              name: serviceName,
              image: `registry/${serviceName}:${newVersion}`
            }]
          }
        }
      }
    });

    // Update VirtualService for canary
    await this.updateVirtualService(serviceName, {
      route: [
        {
          destination: {
            host: serviceName,
            subset: 'stable'
          },
          weight: 100 - initialTraffic
        },
        {
          destination: {
            host: serviceName,
            subset: newVersion
          },
          weight: initialTraffic
        }
      ]
    });

    // Create DestinationRule
    await this.updateDestinationRule(serviceName, {
      subsets: [
        { name: 'stable', labels: { version: 'stable' } },
        { name: newVersion, labels: { version: newVersion } }
      ]
    });
  }

  async gradualRollout(
    serviceName: string,
    version: string,
    steps: number[]
  ): Promise<void> {
    for (const traffic of steps) {
      console.log(`Rolling out ${traffic}% traffic to ${version}`);

      await this.updateVirtualService(serviceName, {
        route: [
          {
            destination: {
              host: serviceName,
              subset: 'stable'
            },
            weight: 100 - traffic
          },
          {
            destination: {
              host: serviceName,
              subset: version
            },
            weight: traffic
          }
        ]
      });

      // Wait and monitor
      await this.waitForDuration(5 * 60);  // 5 minutes
      const metrics = await this.getMetrics(serviceName, version);

      if (metrics.errorRate > 0.01) {  // 1% error threshold
        console.error('High error rate, rolling back');
        await this.rollback(serviceName);
        throw new Error('Canary failed');
      }
    }

    console.log(`Full rollout to ${version} successful`);
  }

  async implementFaultInjection(
    serviceName: string,
    fault: FaultConfig
  ): Promise<void> {
    await this.updateVirtualService(serviceName, {
      fault: {
        delay: fault.delay ? {
          percentage:
            value: fault.delay.percentage,
          fixedDelay: fault.delay.duration
        } : undefined,
        abort: fault.abort ? {
          percentage:
            value: fault.abort.percentage,
          httpStatus: fault.abort.statusCode
        } : undefined
      }
    });
  }

  async setupCircuitBreaker(
    serviceName: string,
    config: CircuitBreakerConfig
  ): Promise<void> {
    await this.updateDestinationRule(serviceName, {
      trafficPolicy: {
        outlierDetection: {
          consecutive5xxErrors: config.consecutiveErrors,
          interval: config.interval,
          baseEjectionTime: config.ejectionTime,
          maxEjectionPercent: config.maxEjectionPercent
        },
        connectionPool: {
          tcp: {
            maxConnections: config.maxConnections
          },
          http: {
            http1MaxPendingRequests: config.maxPendingRequests,
            http2MaxRequests: config.maxRequests
          }
        }
      }
    });
  }
}

Inter-Service Communication

Synchronous Communication

typescript
// REST with resilience patterns
class ResilientServiceClient {
  private circuitBreaker: CircuitBreaker;
  private retryPolicy: RetryPolicy;

  async callService(
    serviceName: string,
    endpoint: string,
    data: any
  ): Promise<any> {
    return this.circuitBreaker.execute(async () => {
      return this.retryPolicy.execute(async () => {
        const response = await fetch(
          `http://${serviceName}${endpoint}`,
          {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'X-Request-ID': this.generateRequestId()
            },
            body: JSON.stringify(data),
            timeout: 5000  // 5 second timeout
          }
        );

        if (!response.ok) {
          throw new ServiceError(
            `Service ${serviceName} returned ${response.status}`,
            response.status
          );
        }

        return await response.json();
      });
    });
  }
}

// gRPC for high-performance inter-service communication
import { grpc } from '@grpc/grpc-js';

class OrderServiceClient {
  private client: any;

  constructor() {
    this.client = new OrderServiceClient(
      'order-service:50051',
      grpc.credentials.createInsecure()
    );
  }

  async createOrder(order: Order): Promise<OrderResponse> {
    return new Promise((resolve, reject) => {
      const deadline = new Date();
      deadline.setSeconds(deadline.getSeconds() + 5);  // 5s timeout

      this.client.createOrder(
        {
          userId: order.userId,
          items: order.items.map(item => ({
            productId: item.productId,
            quantity: item.quantity
          })),
          timestamp: order.timestamp
        },
        { deadline },
        (error: any, response: any) => {
          if (error) {
            reject(error);
          } else {
            resolve(response);
          }
        }
      );
    });
  }
}

Asynchronous Communication

typescript
// Event-driven architecture with Kafka
import { Kafka } from 'kafkajs';

class EventPublisher {
  private producer: any;
  private kafka: Kafka;

  constructor() {
    this.kafka = new Kafka({
      clientId: 'order-service',
      brokers: ['kafka1:9092', 'kafka2:9092', 'kafka3:9092']
    });

    this.producer = this.kafka.producer();
  }

  async publishOrderCreated(order: Order): Promise<void> {
    const event: DomainEvent = {
      eventId: uuidv4(),
      eventType: 'OrderCreated',
      timestamp: Date.now(),
      version: 1,
      data: {
        orderId: order.id,
        userId: order.userId,
        total: order.total,
        items: order.items
      }
    };

    await this.producer.send({
      topic: 'order-events',
      messages: [{
        key: order.id,
        value: JSON.stringify(event),
        headers: {
          'event-type': 'OrderCreated',
          'correlation-id': event.eventId
        }
      }]
    });
  }

  async publishOrderCancelled(orderId: string, reason: string): Promise<void> {
    const event: DomainEvent = {
      eventId: uuidv4(),
      eventType: 'OrderCancelled',
      timestamp: Date.now(),
      version: 1,
      data: {
        orderId,
        reason
      }
    };

    await this.producer.send({
      topic: 'order-events',
      messages: [{
        key: orderId,
        value: JSON.stringify(event)
      }]
    });
  }
}

// Event consumer with Exactly-Once semantics
class EventConsumer {
  private consumer: any;

  async start(): Promise<void> {
    await this.consumer.subscribe({
      topic: 'order-events',
      fromBeginning: false
    });

    await this.consumer.run({
      eachMessage: async ({ topic, partition, message }) => {
        const event = JSON.parse(message.value.toString());

        try {
          await this.processEvent(event);

          // Commit offset after successful processing
          await this.consumer.commitOffsets({
            topic,
            partition,
            offset: message.offset + 1
          });
        } catch (error) {
          console.error('Failed to process event:', error);
          // Retry with backoff
          throw error;
        }
      }
    });
  }

  private async processEvent(event: DomainEvent): Promise<void> {
    switch (event.eventType) {
      case 'OrderCreated':
        await this.handleOrderCreated(event.data);
        break;
      case 'OrderCancelled':
        await this.handleOrderCancelled(event.data);
        break;
      default:
        console.warn(`Unknown event type: ${event.eventType}`);
    }
  }

  private async handleOrderCreated(data: any): Promise<void> {
    // Update read model
    await this.readModel.upsertOrder({
      orderId: data.orderId,
      userId: data.userId,
      total: data.total,
      status: 'CREATED'
    });

    // Send notification
    await this.notificationService.send({
      userId: data.userId,
      type: 'ORDER_CONFIRMATION',
      data: { orderId: data.orderId }
    });

    // Update analytics
    await this.analytics.track({
      event: 'order_created',
      properties: {
        orderId: data.orderId,
        total: data.total,
        itemCount: data.items.length
      }
    });
  }
}

Data Management Patterns

Database per Service Pattern

Database per Service:
+------------+      +------------+      +------------+
|   Order    |      |   User     |      | Inventory |
|  Service   |      |  Service   |      |  Service   |
+------------+      +------------+      +------------+
      |                    |                    |
      v                    v                    v
+------------+      +------------+      +------------+
|  Order DB  |      |  User DB   |      |InventoryDB|
+------------+      +------------+      +------------+

Benefits:
- Loose coupling
- Independent scaling
- Technology diversity
- Team autonomy

Challenges:
- Distributed transactions
- Data consistency
- Cross-service queries

Saga Pattern for Distributed Transactions

typescript
class OrderSagaOrchestrator {
  async execute(orderId: string): Promise<void> {
    const saga = new Saga();

    // Step 1: Create order
    saga.addStep(
      async () => this.orderService.create(orderId),
      async () => this.orderService.cancel(orderId)
    );

    // Step 2: Reserve inventory
    saga.addStep(
      async () => this.inventoryService.reserve(orderId),
      async () => this.inventoryService.release(orderId)
    );

    // Step 3: Process payment
    saga.addStep(
      async () => this.paymentService.charge(orderId),
      async () => this.paymentService.refund(orderId)
    );

    // Step 4: Schedule shipping
    saga.addStep(
      async () => this.shippingService.schedule(orderId),
      async () => this.shippingService.cancel(orderId)
    );

    await saga.execute();
  }
}

// Choreography-based Saga
class OrderService {
  async createOrder(order: Order): Promise<void> {
    const orderId = uuidv4();

    // Save order
    await this.db.orders.insert({
      id: orderId,
      ...order,
      status: 'PENDING'
    });

    // Publish event
    await this.eventBus.publish({
      eventType: 'OrderCreated',
      data: { orderId, ...order }
    });
  }

  async onOrderPaid(event: OrderPaidEvent): Promise<void> {
    // Update order status
    await this.db.orders.update(event.orderId, {
      status: 'PAID'
    });

    // Trigger next step
    await this.eventBus.publish({
      eventType: 'OrderPaymentConfirmed',
      data: { orderId: event.orderId }
    });
  }

  async onInventoryReservationFailed(event: Event): Promise<void> {
    // Compensate: cancel order
    await this.db.orders.update(event.orderId, {
      status: 'CANCELLED',
      reason: 'Inventory unavailable'
    });

    // Notify user
    await this.notificationService.send({
      userId: event.userId,
      type: 'ORDER_CANCELLED',
      data: { orderId: event.orderId }
    });
  }
}

Observability in Microservices

Distributed Tracing

typescript
import { trace } from '@opentelemetry/api';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';

class TracingSetup {
  static initialize(): void {
    const provider = new NodeTracerProvider({
      resource: new Resource({
        [SemanticResourceAttributes.SERVICE_NAME]: 'order-service',
        [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0'
      })
    });

    const exporter = new JaegerExporter({
      endpoint: 'http://jaeger:14268/api/traces'
    });

    provider.addSpanProcessor(
      new BatchSpanProcessor(exporter)
    );

    provider.register();

    // Instrumentations
    new ExpressInstrumentation().setTracerProvider(provider);
    new HttpInstrumentation().setTracerProvider(provider);
  }
}

// Usage in service
class OrderService {
  async createOrder(order: Order): Promise<string> {
    const tracer = trace.getTracer('order-service');

    return tracer.startActiveSpan('createOrder', async (span) => {
      try {
        span.setAttribute('user.id', order.userId);
        span.setAttribute('order.total', order.total);

        const orderId = await this.saveOrder(order);

        span.setStatus({ code: SpanStatusCode.OK });
        return orderId;
      } catch (error) {
        span.recordException(error);
        span.setStatus({
          code: SpanStatusCode.ERROR,
          message: error.message
        });
        throw error;
      } finally {
        span.end();
      }
    });
  }
}

Service Health Monitoring

typescript
class HealthCheckService {
  private checks: Map<string, HealthCheck>;

  constructor() {
    this.checks = new Map();
    this.setupDefaultChecks();
  }

  private setupDefaultChecks(): void {
    this.register('database', async () => {
      try {
        await this.db.query('SELECT 1');
        return { status: 'healthy' };
      } catch (error) {
        return {
          status: 'unhealthy',
          error: error.message
        };
      }
    });

    this.register('redis', async () => {
      try {
        await this.redis.ping();
        return { status: 'healthy' };
      } catch (error) {
        return {
          status: 'unhealthy',
          error: error.message
        };
      }
    });

    this.register('kafka', async () => {
      try {
        const admin = this.kafka.admin();
        await admin.connect();
        await admin.disconnect();
        return { status: 'healthy' };
      } catch (error) {
        return {
          status: 'unhealthy',
          error: error.message
        };
      }
    });
  }

  register(name: string, check: HealthCheckFunction): void {
    this.checks.set(name, check);
  }

  async getHealth(): Promise<HealthReport> {
    const results: Map<string, HealthCheckResult> = new Map();

    for (const [name, check] of this.checks) {
      try {
        const result = await check();
        results.set(name, result);
      } catch (error) {
        results.set(name, {
          status: 'unhealthy',
          error: error.message
        });
      }
    }

    const overall = Array.from(results.values()).every(
      r => r.status === 'healthy'
    );

    return {
      status: overall ? 'healthy' : 'degraded',
      checks: Object.fromEntries(results),
      timestamp: new Date().toISOString()
    };
  }
}

2025 Microservices Best Practices

1. Use Kubernetes for Orchestration:
   - Declarative configuration
   - Self-healing
   - Auto-scaling
   - Service discovery built-in

2. Implement Service Mesh:
   - Zero-trust security
   - Traffic management
   - Observability out of the box
   - Policy enforcement

3. Embrace GraphQL for APIs:
   - Single gateway
   - Client-specific queries
   - Reduced over-fetching
   - Strong typing

4. Use Event-Driven Architecture:
   - Loose coupling
   - Better scalability
   - Natural audit trail
   - Event sourcing

5. Implement Chaos Engineering:
   - Test resilience
   - Proactive failure testing
   - Improve reliability
   - Build confidence

6. Adopt GitOps:
   - Declarative infrastructure
   - Version control
   - Automated deployments
   - Rollback capability

7. Use Feature Flags:
   - Progressive delivery
   - A/B testing
   - Quick rollbacks
   - Experiment safely

8. Invest in Observability:
   - Distributed tracing
   - Real-time metrics
   - Structured logging
   - Alert on SLOs

Build scalable, resilient microservices with modern patterns and practices!

Related essays

Next essay
Edge Computing with Cloudflare Workers