Microservices Development with AI Assistance: Complete Guide

Building microservices architectures is one of the most complex challenges in modern software development. From designing service boundaries to implementing distributed tracing, the decisions you make ripple through your entire system. AI assistants like GitHub Copilot, ChatGPT, and Claude can dramatically accelerate microservices development when used strategically.

In this comprehensive guide, we will explore how to leverage AI for every phase of microservices development: from domain analysis and service boundary design to implementing resilient inter-service communication, setting up distributed tracing, and generating robust service contracts. You will learn practical prompting strategies, see working code examples, and understand how to validate AI-generated microservices code for production readiness.

AI-Assisted Service Boundary Design

One of the hardest decisions in microservices architecture is determining where to draw service boundaries. Poor boundaries lead to distributed monoliths, while well-designed boundaries enable independent deployment, scalability, and team autonomy. AI can help analyze your domain and suggest optimal decomposition strategies.

Domain-Driven Design Analysis with AI

Start by providing your AI assistant with domain context and asking it to identify bounded contexts. Here is an effective prompt pattern:

// Prompt for service boundary analysis
`
I'm designing a microservices architecture for an e-commerce platform.
Here are the key business capabilities:

1. Customer Management: Registration, profiles, preferences, loyalty points
2. Product Catalog: Products, categories, inventory levels, pricing
3. Order Processing: Cart, checkout, order tracking, returns
4. Payment Processing: Payment methods, transactions, refunds, invoices
5. Shipping: Carriers, tracking, delivery estimates, warehouse management
6. Notifications: Email, SMS, push notifications, preferences

Using Domain-Driven Design principles, identify:
1. Bounded contexts and their boundaries
2. Aggregates within each context
3. Context mapping relationships (upstream/downstream)
4. Shared kernel or anti-corruption layers needed
5. Event flows between contexts
`

AI will analyze the domain and provide structured recommendations. Here is an example output you might receive:

// AI-Generated Domain Analysis
interface BoundedContextAnalysis {
  contexts: {
    customerContext: {
      aggregates: ['Customer', 'LoyaltyAccount', 'CustomerPreferences'];
      entities: ['Address', 'PaymentMethod', 'CustomerSegment'];
      events: ['CustomerRegistered', 'PreferencesUpdated', 'LoyaltyPointsEarned'];
      upstream: [];
      downstream: ['OrderContext', 'NotificationContext'];
    };
    catalogContext: {
      aggregates: ['Product', 'Category', 'PricingRule'];
      entities: ['ProductVariant', 'Inventory', 'Promotion'];
      events: ['ProductCreated', 'InventoryUpdated', 'PriceChanged'];
      upstream: [];
      downstream: ['OrderContext', 'SearchContext'];
    };
    orderContext: {
      aggregates: ['Order', 'Cart'];
      entities: ['OrderItem', 'ShippingDetails'];
      events: ['OrderPlaced', 'OrderShipped', 'OrderCancelled', 'ReturnRequested'];
      upstream: ['CustomerContext', 'CatalogContext', 'PaymentContext'];
      downstream: ['ShippingContext', 'NotificationContext'];
      antiCorruptionLayer: ['PaymentContext']; // Different payment models
    };
  };
}

Generating Service Scaffolding

Once boundaries are defined, use AI to generate the initial service structure with proper organization:

// Prompt for service scaffolding
`
Generate a Node.js/TypeScript microservice scaffold for the Order Service with:
1. Hexagonal architecture (ports and adapters)
2. Domain events with EventEmitter pattern
3. Repository pattern for data access
4. Input validation with Zod schemas
5. Health check and readiness endpoints
6. OpenTelemetry instrumentation setup
7. Docker and docker-compose configuration
`

// AI generates this structure:
// order-service/
// ├── src/
// │   ├── domain/
// │   │   ├── entities/
// │   │   │   ├── Order.ts
// │   │   │   ├── OrderItem.ts
// │   │   │   └── Cart.ts
// │   │   ├── events/
// │   │   │   ├── OrderPlaced.ts
// │   │   │   └── DomainEventPublisher.ts
// │   │   ├── repositories/
// │   │   │   └── OrderRepository.ts (interface)
// │   │   └── services/
// │   │       └── OrderService.ts
// │   ├── application/
// │   │   ├── commands/
// │   │   │   ├── CreateOrderCommand.ts
// │   │   │   └── CancelOrderCommand.ts
// │   │   ├── queries/
// │   │   │   └── GetOrderQuery.ts
// │   │   └── handlers/
// │   │       └── OrderCommandHandler.ts
// │   ├── infrastructure/
// │   │   ├── persistence/
// │   │   │   └── PostgresOrderRepository.ts
// │   │   ├── messaging/
// │   │   │   └── KafkaEventPublisher.ts
// │   │   └── http/
// │   │       └── OrderController.ts
// │   └── config/
// │       ├── tracing.ts
// │       └── database.ts
// ├── Dockerfile
// └── docker-compose.yml

Domain Entity Generation

AI excels at generating domain entities with proper validation and business rules:

// order-service/src/domain/entities/Order.ts
import { z } from 'zod';
import { v4 as uuidv4 } from 'uuid';
import { DomainEvent } from '../events/DomainEvent';
import { OrderPlacedEvent } from '../events/OrderPlaced';
import { OrderCancelledEvent } from '../events/OrderCancelled';

// Zod schemas for validation
const OrderItemSchema = z.object({
  productId: z.string().uuid(),
  productName: z.string().min(1),
  quantity: z.number().int().positive(),
  unitPrice: z.number().positive(),
  currency: z.enum(['USD', 'EUR', 'GBP']),
});

const ShippingAddressSchema = z.object({
  street: z.string().min(1),
  city: z.string().min(1),
  state: z.string().min(1),
  postalCode: z.string().min(1),
  country: z.string().length(2), // ISO country code
});

export type OrderItem = z.infer<typeof OrderItemSchema>;
export type ShippingAddress = z.infer<typeof ShippingAddressSchema>;

export type OrderStatus =
  | 'PENDING'
  | 'CONFIRMED'
  | 'PROCESSING'
  | 'SHIPPED'
  | 'DELIVERED'
  | 'CANCELLED';

export interface OrderProps {
  id?: string;
  customerId: string;
  items: OrderItem[];
  shippingAddress: ShippingAddress;
  status?: OrderStatus;
  createdAt?: Date;
  updatedAt?: Date;
}

export class Order {
  private readonly _id: string;
  private readonly _customerId: string;
  private _items: OrderItem[];
  private _shippingAddress: ShippingAddress;
  private _status: OrderStatus;
  private _createdAt: Date;
  private _updatedAt: Date;
  private _domainEvents: DomainEvent[] = [];

  private constructor(props: OrderProps) {
    this._id = props.id || uuidv4();
    this._customerId = props.customerId;
    this._items = props.items;
    this._shippingAddress = props.shippingAddress;
    this._status = props.status || 'PENDING';
    this._createdAt = props.createdAt || new Date();
    this._updatedAt = props.updatedAt || new Date();
  }

  // Factory method with validation
  static create(props: Omit<OrderProps, 'id' | 'status' | 'createdAt' | 'updatedAt'>): Order {
    // Validate items
    props.items.forEach(item => OrderItemSchema.parse(item));
    ShippingAddressSchema.parse(props.shippingAddress);

    if (props.items.length === 0) {
      throw new Error('Order must contain at least one item');
    }

    const order = new Order(props);

    // Raise domain event
    order.addDomainEvent(new OrderPlacedEvent({
      orderId: order.id,
      customerId: order.customerId,
      totalAmount: order.totalAmount,
      itemCount: order.items.length,
      occurredAt: new Date(),
    }));

    return order;
  }

  // Reconstitute from persistence
  static fromPersistence(props: OrderProps): Order {
    return new Order(props);
  }

  // Business logic methods
  confirm(): void {
    if (this._status !== 'PENDING') {
      throw new Error(`Cannot confirm order in ${this._status} status`);
    }
    this._status = 'CONFIRMED';
    this._updatedAt = new Date();
  }

  cancel(reason: string): void {
    const cancellableStatuses: OrderStatus[] = ['PENDING', 'CONFIRMED'];
    if (!cancellableStatuses.includes(this._status)) {
      throw new Error(`Cannot cancel order in ${this._status} status`);
    }

    this._status = 'CANCELLED';
    this._updatedAt = new Date();

    this.addDomainEvent(new OrderCancelledEvent({
      orderId: this._id,
      customerId: this._customerId,
      reason,
      refundAmount: this.totalAmount,
      occurredAt: new Date(),
    }));
  }

  ship(trackingNumber: string): void {
    if (this._status !== 'PROCESSING') {
      throw new Error(`Cannot ship order in ${this._status} status`);
    }
    this._status = 'SHIPPED';
    this._updatedAt = new Date();
  }

  // Getters
  get id(): string { return this._id; }
  get customerId(): string { return this._customerId; }
  get items(): ReadonlyArray<OrderItem> { return [...this._items]; }
  get shippingAddress(): ShippingAddress { return { ...this._shippingAddress }; }
  get status(): OrderStatus { return this._status; }
  get createdAt(): Date { return this._createdAt; }
  get updatedAt(): Date { return this._updatedAt; }

  get totalAmount(): number {
    return this._items.reduce(
      (sum, item) => sum + item.unitPrice * item.quantity,
      0
    );
  }

  // Domain events
  private addDomainEvent(event: DomainEvent): void {
    this._domainEvents.push(event);
  }

  pullDomainEvents(): DomainEvent[] {
    const events = [...this._domainEvents];
    this._domainEvents = [];
    return events;
  }
}

AI-Generated Inter-Service Communication

Inter-service communication is where microservices can fail catastrophically. AI can help generate resilient communication patterns with proper error handling, circuit breakers, and retry logic.

HTTP Client with Resilience Patterns

Use this prompt to generate a production-ready service client:

// Prompt for resilient HTTP client
`
Generate a TypeScript HTTP client for calling the Inventory Service with:
1. Circuit breaker using opossum library
2. Exponential backoff retry with configurable attempts
3. Request timeout handling
4. OpenTelemetry trace context propagation
5. Structured logging for debugging
6. Fallback response when service is unavailable
7. Health check integration
`

// AI generates:
// inventory-client.ts
import CircuitBreaker from 'opossum';
import { trace, context, SpanKind, SpanStatusCode } from '@opentelemetry/api';
import { W3CTraceContextPropagator } from '@opentelemetry/core';

interface InventoryCheckRequest {
  productId: string;
  quantity: number;
}

interface InventoryCheckResponse {
  available: boolean;
  currentStock: number;
  reservationId?: string;
}

interface CircuitBreakerConfig {
  timeout: number;
  errorThresholdPercentage: number;
  resetTimeout: number;
  volumeThreshold: number;
}

const defaultConfig: CircuitBreakerConfig = {
  timeout: 5000,           // 5 second timeout
  errorThresholdPercentage: 50,  // Open circuit at 50% failure
  resetTimeout: 30000,     // Try again after 30 seconds
  volumeThreshold: 5,      // Minimum calls before circuit can trip
};

export class InventoryServiceClient {
  private readonly baseUrl: string;
  private readonly circuitBreaker: CircuitBreaker;
  private readonly tracer = trace.getTracer('inventory-client');
  private readonly propagator = new W3CTraceContextPropagator();

  constructor(
    baseUrl: string,
    config: Partial<CircuitBreakerConfig> = {}
  ) {
    this.baseUrl = baseUrl;

    const finalConfig = { ...defaultConfig, ...config };

    this.circuitBreaker = new CircuitBreaker(
      this.makeRequest.bind(this),
      {
        timeout: finalConfig.timeout,
        errorThresholdPercentage: finalConfig.errorThresholdPercentage,
        resetTimeout: finalConfig.resetTimeout,
        volumeThreshold: finalConfig.volumeThreshold,
      }
    );

    // Set up fallback
    this.circuitBreaker.fallback(this.fallbackResponse.bind(this));

    // Circuit breaker events for monitoring
    this.circuitBreaker.on('open', () => {
      console.warn('[InventoryClient] Circuit breaker OPENED');
    });
    this.circuitBreaker.on('halfOpen', () => {
      console.info('[InventoryClient] Circuit breaker HALF-OPEN');
    });
    this.circuitBreaker.on('close', () => {
      console.info('[InventoryClient] Circuit breaker CLOSED');
    });
  }

  async checkInventory(
    request: InventoryCheckRequest
  ): Promise<InventoryCheckResponse> {
    return this.tracer.startActiveSpan(
      'inventory.check',
      { kind: SpanKind.CLIENT },
      async (span) => {
        try {
          span.setAttributes({
            'inventory.product_id': request.productId,
            'inventory.quantity_requested': request.quantity,
            'peer.service': 'inventory-service',
          });

          const response = await this.circuitBreaker.fire(
            '/inventory/check',
            'POST',
            request
          );

          span.setAttributes({
            'inventory.available': response.available,
            'inventory.current_stock': response.currentStock,
          });
          span.setStatus({ code: SpanStatusCode.OK });

          return response;
        } catch (error) {
          span.setStatus({
            code: SpanStatusCode.ERROR,
            message: error instanceof Error ? error.message : 'Unknown error',
          });
          span.recordException(error as Error);
          throw error;
        } finally {
          span.end();
        }
      }
    );
  }

  async reserveInventory(
    productId: string,
    quantity: number,
    orderId: string
  ): Promise<{ reservationId: string; expiresAt: Date }> {
    return this.circuitBreaker.fire(
      '/inventory/reserve',
      'POST',
      { productId, quantity, orderId }
    );
  }

  private async makeRequest<T>(
    path: string,
    method: string,
    body?: unknown
  ): Promise<T> {
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
    };

    // Propagate trace context
    this.propagator.inject(
      context.active(),
      headers,
      {
        set: (carrier, key, value) => {
          carrier[key] = value;
        },
      }
    );

    const response = await this.fetchWithRetry(
      `${this.baseUrl}${path}`,
      {
        method,
        headers,
        body: body ? JSON.stringify(body) : undefined,
      },
      3 // max retries
    );

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`Inventory service error: ${response.status} - ${error}`);
    }

    return response.json();
  }

  private async fetchWithRetry(
    url: string,
    options: RequestInit,
    maxRetries: number,
    attempt: number = 1
  ): Promise<Response> {
    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 5000);

      const response = await fetch(url, {
        ...options,
        signal: controller.signal,
      });

      clearTimeout(timeoutId);

      // Retry on 5xx errors
      if (response.status >= 500 && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 100; // Exponential backoff
        console.warn(
          `[InventoryClient] Retry ${attempt}/${maxRetries} after ${delay}ms`
        );
        await this.sleep(delay);
        return this.fetchWithRetry(url, options, maxRetries, attempt + 1);
      }

      return response;
    } catch (error) {
      if (attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 100;
        console.warn(
          `[InventoryClient] Retry ${attempt}/${maxRetries} after error: ${error}`
        );
        await this.sleep(delay);
        return this.fetchWithRetry(url, options, maxRetries, attempt + 1);
      }
      throw error;
    }
  }

  private fallbackResponse(
    path: string,
    method: string,
    body?: unknown
  ): InventoryCheckResponse {
    console.warn('[InventoryClient] Using fallback response');
    // Return a safe default that allows the order but flags for later verification
    return {
      available: true,
      currentStock: -1, // Indicates unknown/fallback
      reservationId: undefined,
    };
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // Health check
  async isHealthy(): Promise<boolean> {
    try {
      const response = await fetch(`${this.baseUrl}/health`, {
        method: 'GET',
        signal: AbortSignal.timeout(2000),
      });
      return response.ok;
    } catch {
      return false;
    }
  }

  getCircuitState(): string {
    return this.circuitBreaker.opened ? 'OPEN' :
           this.circuitBreaker.halfOpen ? 'HALF_OPEN' : 'CLOSED';
  }
}

Event-Driven Communication with Kafka

For asynchronous communication, use AI to generate event producers and consumers with proper error handling:

// events/OrderEventPublisher.ts
import { Kafka, Producer, Partitioners, CompressionTypes } from 'kafkajs';
import { trace, SpanKind, SpanStatusCode } from '@opentelemetry/api';
import { DomainEvent } from '../domain/events/DomainEvent';

interface EventMetadata {
  correlationId: string;
  causationId?: string;
  userId?: string;
  timestamp: Date;
}

export class OrderEventPublisher {
  private producer: Producer;
  private readonly tracer = trace.getTracer('order-event-publisher');
  private isConnected = false;

  constructor(private readonly kafka: Kafka) {
    this.producer = kafka.producer({
      createPartitioner: Partitioners.DefaultPartitioner,
      idempotent: true, // Exactly-once semantics
      maxInFlightRequests: 5,
      transactionTimeout: 60000,
    });
  }

  async connect(): Promise<void> {
    if (this.isConnected) return;

    await this.producer.connect();
    this.isConnected = true;
    console.info('[OrderEventPublisher] Connected to Kafka');
  }

  async disconnect(): Promise<void> {
    if (!this.isConnected) return;

    await this.producer.disconnect();
    this.isConnected = false;
    console.info('[OrderEventPublisher] Disconnected from Kafka');
  }

  async publish(
    event: DomainEvent,
    metadata: EventMetadata
  ): Promise<void> {
    return this.tracer.startActiveSpan(
      `kafka.publish.${event.eventType}`,
      { kind: SpanKind.PRODUCER },
      async (span) => {
        try {
          span.setAttributes({
            'messaging.system': 'kafka',
            'messaging.destination': 'order-events',
            'messaging.message_id': event.eventId,
            'event.type': event.eventType,
            'event.correlation_id': metadata.correlationId,
          });

          const message = {
            key: event.aggregateId, // Partition by aggregate for ordering
            value: JSON.stringify({
              ...event,
              metadata: {
                ...metadata,
                traceId: span.spanContext().traceId,
                spanId: span.spanContext().spanId,
              },
            }),
            headers: {
              'event-type': event.eventType,
              'correlation-id': metadata.correlationId,
              'content-type': 'application/json',
              'schema-version': '1.0',
            },
          };

          await this.producer.send({
            topic: 'order-events',
            compression: CompressionTypes.GZIP,
            messages: [message],
          });

          span.setStatus({ code: SpanStatusCode.OK });
          console.info(
            `[OrderEventPublisher] Published ${event.eventType}`,
            { eventId: event.eventId, correlationId: metadata.correlationId }
          );
        } catch (error) {
          span.setStatus({
            code: SpanStatusCode.ERROR,
            message: error instanceof Error ? error.message : 'Publish failed',
          });
          span.recordException(error as Error);
          throw error;
        } finally {
          span.end();
        }
      }
    );
  }

  async publishBatch(
    events: DomainEvent[],
    metadata: EventMetadata
  ): Promise<void> {
    const messages = events.map(event => ({
      key: event.aggregateId,
      value: JSON.stringify({ ...event, metadata }),
      headers: {
        'event-type': event.eventType,
        'correlation-id': metadata.correlationId,
      },
    }));

    await this.producer.send({
      topic: 'order-events',
      compression: CompressionTypes.GZIP,
      messages,
    });
  }
}

// Event consumer with dead letter queue
// events/OrderEventConsumer.ts
import { Kafka, Consumer, EachMessagePayload } from 'kafkajs';
import { trace, SpanKind, SpanStatusCode, context } from '@opentelemetry/api';

type EventHandler = (event: DomainEvent, metadata: any) => Promise<void>;

export class OrderEventConsumer {
  private consumer: Consumer;
  private readonly tracer = trace.getTracer('order-event-consumer');
  private handlers = new Map<string, EventHandler>();
  private isRunning = false;

  constructor(
    private readonly kafka: Kafka,
    private readonly groupId: string
  ) {
    this.consumer = kafka.consumer({
      groupId,
      sessionTimeout: 30000,
      heartbeatInterval: 3000,
      maxBytesPerPartition: 1048576, // 1MB
      retry: {
        initialRetryTime: 100,
        retries: 8,
      },
    });
  }

  registerHandler(eventType: string, handler: EventHandler): void {
    this.handlers.set(eventType, handler);
  }

  async start(topics: string[]): Promise<void> {
    await this.consumer.connect();
    await this.consumer.subscribe({
      topics,
      fromBeginning: false
    });

    this.isRunning = true;

    await this.consumer.run({
      eachMessage: async (payload) => {
        await this.handleMessage(payload);
      },
    });
  }

  private async handleMessage(payload: EachMessagePayload): Promise<void> {
    const { topic, partition, message } = payload;
    const eventType = message.headers?.['event-type']?.toString();

    if (!eventType || !message.value) {
      console.warn('[Consumer] Invalid message received', { topic, partition });
      return;
    }

    return this.tracer.startActiveSpan(
      `kafka.consume.${eventType}`,
      { kind: SpanKind.CONSUMER },
      async (span) => {
        try {
          span.setAttributes({
            'messaging.system': 'kafka',
            'messaging.destination': topic,
            'messaging.kafka.partition': partition,
            'messaging.kafka.offset': message.offset,
            'event.type': eventType,
          });

          const event = JSON.parse(message.value!.toString());
          const handler = this.handlers.get(eventType);

          if (!handler) {
            console.warn(`[Consumer] No handler for event type: ${eventType}`);
            span.setStatus({ code: SpanStatusCode.OK });
            return;
          }

          await handler(event, event.metadata);
          span.setStatus({ code: SpanStatusCode.OK });
        } catch (error) {
          span.setStatus({
            code: SpanStatusCode.ERROR,
            message: error instanceof Error ? error.message : 'Processing failed',
          });
          span.recordException(error as Error);

          // Send to dead letter queue
          await this.sendToDeadLetterQueue(message, error as Error);
        } finally {
          span.end();
        }
      }
    );
  }

  private async sendToDeadLetterQueue(
    message: any,
    error: Error
  ): Promise<void> {
    const dlqProducer = this.kafka.producer();
    await dlqProducer.connect();

    try {
      await dlqProducer.send({
        topic: 'order-events-dlq',
        messages: [{
          key: message.key,
          value: message.value,
          headers: {
            ...message.headers,
            'dlq-reason': error.message,
            'dlq-timestamp': new Date().toISOString(),
            'original-topic': 'order-events',
          },
        }],
      });
    } finally {
      await dlqProducer.disconnect();
    }
  }

  async stop(): Promise<void> {
    this.isRunning = false;
    await this.consumer.stop();
    await this.consumer.disconnect();
  }
}

Distributed Tracing with OpenTelemetry

Distributed tracing is essential for debugging microservices. AI can help set up comprehensive instrumentation that follows requests across service boundaries.

OpenTelemetry Configuration

// config/tracing.ts - Initialize before any other imports
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { Resource } from '@opentelemetry/resources';
import {
  ATTR_SERVICE_NAME,
  ATTR_SERVICE_VERSION,
  ATTR_DEPLOYMENT_ENVIRONMENT
} from '@opentelemetry/semantic-conventions';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { W3CTraceContextPropagator } from '@opentelemetry/core';

export function initializeTracing(serviceName: string): NodeSDK {
  const traceExporter = new OTLPTraceExporter({
    url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
         'http://localhost:4318/v1/traces',
    headers: {},
  });

  const metricExporter = new OTLPMetricExporter({
    url: process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT ||
         'http://localhost:4318/v1/metrics',
  });

  const sdk = new NodeSDK({
    resource: new Resource({
      [ATTR_SERVICE_NAME]: serviceName,
      [ATTR_SERVICE_VERSION]: process.env.APP_VERSION || '1.0.0',
      [ATTR_DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV || 'development',
      'service.namespace': 'ecommerce',
      'service.instance.id': process.env.HOSTNAME || 'local',
    }),
    spanProcessor: new BatchSpanProcessor(traceExporter, {
      maxQueueSize: 2048,
      maxExportBatchSize: 512,
      scheduledDelayMillis: 5000,
      exportTimeoutMillis: 30000,
    }),
    metricReader: new PeriodicExportingMetricReader({
      exporter: metricExporter,
      exportIntervalMillis: 60000,
    }),
    textMapPropagator: new W3CTraceContextPropagator(),
    instrumentations: [
      getNodeAutoInstrumentations({
        '@opentelemetry/instrumentation-fs': { enabled: false },
        '@opentelemetry/instrumentation-http': {
          requestHook: (span, request) => {
            span.updateName(`${request.method} ${request.url}`);
          },
        },
        '@opentelemetry/instrumentation-express': {
          requestHook: (span, info) => {
            span.setAttribute('http.route', info.route);
          },
        },
      }),
    ],
  });

  // Graceful shutdown
  process.on('SIGTERM', () => {
    sdk.shutdown()
      .then(() => console.log('Tracing terminated'))
      .catch((error) => console.error('Error terminating tracing', error))
      .finally(() => process.exit(0));
  });

  sdk.start();
  console.info(`[Tracing] Initialized for ${serviceName}`);

  return sdk;
}

// Usage in main.ts
// import './config/tracing'; // Must be first import
// initializeTracing('order-service');

Custom Span Instrumentation

// instrumentation/tracing-utils.ts
import {
  trace,
  context,
  SpanKind,
  SpanStatusCode,
  Span,
  Context
} from '@opentelemetry/api';

const tracer = trace.getTracer('order-service');

// Decorator for automatic span creation
export function Traced(spanName?: string) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
    const name = spanName || `${target.constructor.name}.${propertyKey}`;

    descriptor.value = async function (...args: any[]) {
      return tracer.startActiveSpan(
        name,
        { kind: SpanKind.INTERNAL },
        async (span) => {
          try {
            const result = await originalMethod.apply(this, args);
            span.setStatus({ code: SpanStatusCode.OK });
            return result;
          } catch (error) {
            span.setStatus({
              code: SpanStatusCode.ERROR,
              message: error instanceof Error ? error.message : 'Unknown error',
            });
            span.recordException(error as Error);
            throw error;
          } finally {
            span.end();
          }
        }
      );
    };

    return descriptor;
  };
}

// Utility for adding business context to spans
export function addBusinessContext(
  span: Span,
  context: Record<string, string | number | boolean>
): void {
  Object.entries(context).forEach(([key, value]) => {
    span.setAttribute(`business.${key}`, value);
  });
}

// Create child span with context propagation
export async function withSpan<T>(
  name: string,
  fn: (span: Span) => Promise<T>,
  options?: { kind?: SpanKind; attributes?: Record<string, any> }
): Promise<T> {
  return tracer.startActiveSpan(
    name,
    {
      kind: options?.kind || SpanKind.INTERNAL,
      attributes: options?.attributes,
    },
    async (span) => {
      try {
        const result = await fn(span);
        span.setStatus({ code: SpanStatusCode.OK });
        return result;
      } catch (error) {
        span.setStatus({
          code: SpanStatusCode.ERROR,
          message: error instanceof Error ? error.message : 'Unknown error',
        });
        span.recordException(error as Error);
        throw error;
      } finally {
        span.end();
      }
    }
  );
}

// Example usage in service
class OrderService {
  @Traced('order.create')
  async createOrder(input: CreateOrderInput): Promise<Order> {
    const span = trace.getActiveSpan();

    if (span) {
      addBusinessContext(span, {
        customerId: input.customerId,
        itemCount: input.items.length,
        totalAmount: this.calculateTotal(input.items),
      });
    }

    // Validate input
    await withSpan('order.validate', async (validationSpan) => {
      validationSpan.setAttribute('validation.items_count', input.items.length);
      await this.validateOrderInput(input);
    });

    // Reserve inventory
    await withSpan('inventory.reserve', async (inventorySpan) => {
      inventorySpan.setAttribute('peer.service', 'inventory-service');
      await this.inventoryClient.reserveInventory(input.items);
    }, { kind: SpanKind.CLIENT });

    // Create order
    const order = await withSpan('order.persist', async () => {
      return this.orderRepository.save(Order.create(input));
    });

    return order;
  }
}

AI-Generated Service Contracts

Service contracts define the API between services. AI can generate OpenAPI specifications, AsyncAPI contracts for events, and contract tests to ensure compatibility.

OpenAPI Specification Generation

// Prompt for OpenAPI generation
`
Generate an OpenAPI 3.1 specification for the Order Service with:
1. CRUD operations for orders
2. Proper request/response schemas with examples
3. Error response definitions (400, 404, 500)
4. Authentication via Bearer token
5. Pagination for list endpoints
6. Correlation ID header for tracing
`

// AI generates:
// openapi/order-service.yaml
openapi: 3.1.0
info:
  title: Order Service API
  version: 1.0.0
  description: Microservice for managing customer orders
  contact:
    name: Platform Team
    email: platform@example.com

servers:
  - url: https://api.example.com/orders/v1
    description: Production
  - url: https://staging-api.example.com/orders/v1
    description: Staging

security:
  - bearerAuth: []

paths:
  /orders:
    post:
      operationId: createOrder
      summary: Create a new order
      tags: [Orders]
      parameters:
        - $ref: '#/components/parameters/CorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
            example:
              customerId: "cust_123"
              items:
                - productId: "prod_456"
                  quantity: 2
                  unitPrice: 29.99
              shippingAddress:
                street: "123 Main St"
                city: "San Francisco"
                state: "CA"
                postalCode: "94102"
                country: "US"
      responses:
        '201':
          description: Order created successfully
          headers:
            X-Correlation-Id:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalError'

    get:
      operationId: listOrders
      summary: List orders with pagination
      tags: [Orders]
      parameters:
        - $ref: '#/components/parameters/CorrelationId'
        - name: customerId
          in: query
          schema:
            type: string
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/OrderStatus'
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
      responses:
        '200':
          description: List of orders
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderListResponse'

  /orders/{orderId}:
    get:
      operationId: getOrder
      summary: Get order by ID
      tags: [Orders]
      parameters:
        - $ref: '#/components/parameters/CorrelationId'
        - name: orderId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Order details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '404':
          $ref: '#/components/responses/NotFound'

    delete:
      operationId: cancelOrder
      summary: Cancel an order
      tags: [Orders]
      parameters:
        - $ref: '#/components/parameters/CorrelationId'
        - name: orderId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason:
                  type: string
                  minLength: 1
                  maxLength: 500
      responses:
        '200':
          description: Order cancelled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  parameters:
    CorrelationId:
      name: X-Correlation-Id
      in: header
      required: false
      schema:
        type: string
        format: uuid
      description: Correlation ID for distributed tracing

  schemas:
    CreateOrderRequest:
      type: object
      required: [customerId, items, shippingAddress]
      properties:
        customerId:
          type: string
        items:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/OrderItemInput'
        shippingAddress:
          $ref: '#/components/schemas/Address'

    OrderItemInput:
      type: object
      required: [productId, quantity, unitPrice]
      properties:
        productId:
          type: string
        quantity:
          type: integer
          minimum: 1
        unitPrice:
          type: number
          format: float
          minimum: 0

    Order:
      type: object
      properties:
        id:
          type: string
          format: uuid
        customerId:
          type: string
        items:
          type: array
          items:
            $ref: '#/components/schemas/OrderItem'
        shippingAddress:
          $ref: '#/components/schemas/Address'
        status:
          $ref: '#/components/schemas/OrderStatus'
        totalAmount:
          type: number
          format: float
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    OrderItem:
      type: object
      properties:
        productId:
          type: string
        productName:
          type: string
        quantity:
          type: integer
        unitPrice:
          type: number
          format: float

    OrderStatus:
      type: string
      enum: [PENDING, CONFIRMED, PROCESSING, SHIPPED, DELIVERED, CANCELLED]

    Address:
      type: object
      required: [street, city, state, postalCode, country]
      properties:
        street:
          type: string
        city:
          type: string
        state:
          type: string
        postalCode:
          type: string
        country:
          type: string
          minLength: 2
          maxLength: 2

    OrderListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Order'
        pagination:
          $ref: '#/components/schemas/Pagination'

    Pagination:
      type: object
      properties:
        page:
          type: integer
        limit:
          type: integer
        total:
          type: integer
        totalPages:
          type: integer

    Error:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
        message:
          type: string
        details:
          type: object
        correlationId:
          type: string

  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: "VALIDATION_ERROR"
            message: "Invalid order items"
            details:
              items: "At least one item is required"

    Unauthorized:
      description: Authentication required
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: "UNAUTHORIZED"
            message: "Valid authentication token required"

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: "NOT_FOUND"
            message: "Order not found"

    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: "INTERNAL_ERROR"
            message: "An unexpected error occurred"

Contract Testing with Pact

// tests/contracts/order-consumer.pact.ts
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import { InventoryServiceClient } from '../../src/clients/InventoryServiceClient';

const { like, eachLike, uuid, boolean, integer } = MatchersV3;

describe('Inventory Service Contract', () => {
  const provider = new PactV3({
    consumer: 'OrderService',
    provider: 'InventoryService',
    logLevel: 'warn',
  });

  describe('check inventory availability', () => {
    it('returns availability for valid product', async () => {
      // Arrange
      const productId = '123e4567-e89b-12d3-a456-426614174000';
      const quantity = 5;

      await provider
        .given('product exists with sufficient stock')
        .uponReceiving('a request to check inventory')
        .withRequest({
          method: 'POST',
          path: '/inventory/check',
          headers: {
            'Content-Type': 'application/json',
          },
          body: {
            productId: like(productId),
            quantity: like(quantity),
          },
        })
        .willRespondWith({
          status: 200,
          headers: {
            'Content-Type': 'application/json',
          },
          body: {
            available: boolean(true),
            currentStock: integer(100),
            reservationId: uuid(),
          },
        });

      // Act & Assert
      await provider.executeTest(async (mockServer) => {
        const client = new InventoryServiceClient(mockServer.url);
        const result = await client.checkInventory({ productId, quantity });

        expect(result.available).toBe(true);
        expect(result.currentStock).toBeGreaterThan(0);
      });
    });

    it('returns unavailable when insufficient stock', async () => {
      const productId = '123e4567-e89b-12d3-a456-426614174001';

      await provider
        .given('product exists with insufficient stock')
        .uponReceiving('a request to check inventory for out of stock item')
        .withRequest({
          method: 'POST',
          path: '/inventory/check',
          headers: {
            'Content-Type': 'application/json',
          },
          body: {
            productId: like(productId),
            quantity: like(100),
          },
        })
        .willRespondWith({
          status: 200,
          body: {
            available: boolean(false),
            currentStock: integer(5),
          },
        });

      await provider.executeTest(async (mockServer) => {
        const client = new InventoryServiceClient(mockServer.url);
        const result = await client.checkInventory({
          productId,
          quantity: 100
        });

        expect(result.available).toBe(false);
      });
    });
  });

  describe('reserve inventory', () => {
    it('successfully reserves inventory', async () => {
      const productId = '123e4567-e89b-12d3-a456-426614174000';
      const orderId = '123e4567-e89b-12d3-a456-426614174002';

      await provider
        .given('product is available for reservation')
        .uponReceiving('a request to reserve inventory')
        .withRequest({
          method: 'POST',
          path: '/inventory/reserve',
          body: {
            productId: like(productId),
            quantity: like(2),
            orderId: like(orderId),
          },
        })
        .willRespondWith({
          status: 201,
          body: {
            reservationId: uuid(),
            expiresAt: like('2025-01-30T12:00:00Z'),
          },
        });

      await provider.executeTest(async (mockServer) => {
        const client = new InventoryServiceClient(mockServer.url);
        const result = await client.reserveInventory(productId, 2, orderId);

        expect(result.reservationId).toBeDefined();
        expect(result.expiresAt).toBeDefined();
      });
    });
  });
});

AI-Assisted Deployment Strategies

AI can help generate Kubernetes manifests, Helm charts, and CI/CD pipelines for microservices deployment.

// Prompt for Kubernetes deployment
`
Generate Kubernetes manifests for the Order Service with:
1. Deployment with 3 replicas and rolling update strategy
2. HorizontalPodAutoscaler based on CPU and memory
3. Service with internal ClusterIP
4. ConfigMap for non-sensitive configuration
5. Secret references for sensitive data
6. Readiness and liveness probes
7. Resource limits and requests
8. Pod disruption budget
`

# kubernetes/order-service/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: ecommerce
  labels:
    app: order-service
    version: v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: order-service
        version: v1
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9090"
        prometheus.io/path: "/metrics"
    spec:
      serviceAccountName: order-service
      containers:
        - name: order-service
          image: registry.example.com/order-service:latest
          imagePullPolicy: Always
          ports:
            - name: http
              containerPort: 3000
            - name: metrics
              containerPort: 9090
          env:
            - name: NODE_ENV
              value: "production"
            - name: PORT
              value: "3000"
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: order-service-secrets
                  key: database-url
            - name: KAFKA_BROKERS
              valueFrom:
                configMapKeyRef:
                  name: order-service-config
                  key: kafka-brokers
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              valueFrom:
                configMapKeyRef:
                  name: order-service-config
                  key: otel-endpoint
          resources:
            requests:
              cpu: 100m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health/live
              port: 3000
            initialDelaySeconds: 30
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 10"]
      terminationGracePeriodSeconds: 30
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchLabels:
                    app: order-service
                topologyKey: kubernetes.io/hostname
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
  namespace: ecommerce
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 3
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Percent
          value: 100
          periodSeconds: 15
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: order-service-pdb
  namespace: ecommerce
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: order-service

Testing Strategies for Distributed Systems

AI can help generate comprehensive tests for microservices, including integration tests with testcontainers:

// tests/integration/order-service.integration.test.ts
import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql';
import { KafkaContainer, StartedKafkaContainer } from '@testcontainers/kafka';
import { GenericContainer, StartedTestContainer } from 'testcontainers';
import { OrderService } from '../../src/domain/services/OrderService';
import { PostgresOrderRepository } from '../../src/infrastructure/persistence/PostgresOrderRepository';
import { KafkaEventPublisher } from '../../src/infrastructure/messaging/KafkaEventPublisher';
import { Kafka } from 'kafkajs';

describe('Order Service Integration Tests', () => {
  let postgresContainer: StartedPostgreSqlContainer;
  let kafkaContainer: StartedKafkaContainer;
  let orderService: OrderService;
  let orderRepository: PostgresOrderRepository;
  let eventPublisher: KafkaEventPublisher;

  beforeAll(async () => {
    // Start PostgreSQL container
    postgresContainer = await new PostgreSqlContainer('postgres:15')
      .withDatabase('orders_test')
      .withUsername('test')
      .withPassword('test')
      .start();

    // Start Kafka container
    kafkaContainer = await new KafkaContainer('confluentinc/cp-kafka:7.4.0')
      .withExposedPorts(9093)
      .start();

    // Initialize repository
    orderRepository = new PostgresOrderRepository({
      connectionString: postgresContainer.getConnectionUri(),
    });
    await orderRepository.migrate();

    // Initialize Kafka
    const kafka = new Kafka({
      clientId: 'test-client',
      brokers: [kafkaContainer.getBootstrapServers()],
    });
    eventPublisher = new KafkaEventPublisher(kafka);
    await eventPublisher.connect();

    // Initialize service
    orderService = new OrderService(orderRepository, eventPublisher);
  }, 120000); // 2 minute timeout for container startup

  afterAll(async () => {
    await eventPublisher.disconnect();
    await postgresContainer.stop();
    await kafkaContainer.stop();
  });

  describe('createOrder', () => {
    it('should create order and publish event', async () => {
      // Arrange
      const orderInput = {
        customerId: 'cust_123',
        items: [
          {
            productId: 'prod_456',
            productName: 'Test Product',
            quantity: 2,
            unitPrice: 29.99,
            currency: 'USD' as const,
          },
        ],
        shippingAddress: {
          street: '123 Test St',
          city: 'Test City',
          state: 'TS',
          postalCode: '12345',
          country: 'US',
        },
      };

      // Act
      const order = await orderService.createOrder(orderInput);

      // Assert
      expect(order.id).toBeDefined();
      expect(order.status).toBe('PENDING');
      expect(order.totalAmount).toBe(59.98);

      // Verify persistence
      const retrieved = await orderRepository.findById(order.id);
      expect(retrieved).toBeDefined();
      expect(retrieved?.customerId).toBe('cust_123');
    });

    it('should reject order with empty items', async () => {
      const orderInput = {
        customerId: 'cust_123',
        items: [],
        shippingAddress: {
          street: '123 Test St',
          city: 'Test City',
          state: 'TS',
          postalCode: '12345',
          country: 'US',
        },
      };

      await expect(orderService.createOrder(orderInput))
        .rejects
        .toThrow('Order must contain at least one item');
    });
  });

  describe('cancelOrder', () => {
    it('should cancel pending order and publish event', async () => {
      // Create an order first
      const order = await orderService.createOrder({
        customerId: 'cust_456',
        items: [{
          productId: 'prod_789',
          productName: 'Cancellable Product',
          quantity: 1,
          unitPrice: 19.99,
          currency: 'USD' as const,
        }],
        shippingAddress: {
          street: '456 Cancel St',
          city: 'Cancel City',
          state: 'CC',
          postalCode: '67890',
          country: 'US',
        },
      });

      // Cancel the order
      const cancelled = await orderService.cancelOrder(
        order.id,
        'Customer requested cancellation'
      );

      expect(cancelled.status).toBe('CANCELLED');
    });
  });
});

Key Takeaways

Remember These Points

  • Use AI for domain analysis: Provide business context and let AI identify bounded contexts and aggregates using DDD principles
  • Always include resilience patterns: Prompt AI to add circuit breakers, retries, and timeouts to all inter-service communication
  • Implement distributed tracing: Use AI to set up OpenTelemetry with proper context propagation across services
  • Generate service contracts: AI excels at creating OpenAPI specs, AsyncAPI contracts, and Pact tests
  • Test with containers: Use testcontainers for realistic integration testing of microservices
  • Validate AI output: Always review generated code for missing error handling, proper typing, and production concerns
  • Use structured prompts: Provide clear requirements including patterns, libraries, and constraints for better results
  • Iterate on feedback: Refine AI-generated code through multiple prompt iterations rather than accepting first output

Conclusion

AI assistants have become invaluable partners in microservices development, dramatically accelerating everything from domain analysis to deployment configuration. By using structured prompts that specify patterns, libraries, and constraints, you can generate production-quality code that includes the resilience patterns and observability features that distributed systems require.

The key is knowing what to ask for. AI models are trained on vast amounts of code but need guidance to produce microservices-specific patterns like circuit breakers, distributed tracing, and saga implementations. Always review generated code for production concerns: proper error handling, timeout configuration, and observability integration.

As we have seen throughout this guide, AI can help with service boundary design using DDD principles, generate resilient service clients with circuit breakers and retries, set up comprehensive distributed tracing with OpenTelemetry, create service contracts and contract tests, and produce deployment manifests for Kubernetes. Combined with thorough testing using tools like testcontainers and Pact, AI-assisted microservices development offers a significant productivity boost while maintaining the reliability that distributed systems demand.

In the next article, we will explore AI for GraphQL Schema and Resolver Generation, examining how to leverage AI for building type-safe GraphQL APIs with efficient resolver patterns.