Aller au contenu principal
    IT INNOVE - Logo de l'agence de développement web et mobile
    Retour au blog
    Architecture

    Architecture microservices : Guide pratique et patterns 2024

    Équipe Architecture IT INNOVE
    28 min de lecture

    Transformez votre monolithe en microservices avec notre guide pratique : patterns éprouvés, migration progressive, outils modernes et retours d'expérience concrets.

    Architecture microservices : Guide pratique et patterns 2024 - Architecture | IT INNOVE
    Architecture microservices : Guide pratique et patterns 2024 - Architecture

    Architecture microservices : Guide pratique et patterns 2024

    Les microservices représentent l'évolution naturelle des architectures logicielles modernes. Après avoir accompagné plus de 50+ entreprises dans leur transformation architecturale, nous partageons notre expertise complète
    quand migrer, comment faire, et surtout comment éviter les pièges classiques.
    Cette transformation n'est pas qu'technique
    elle impacte l'organisation, les processus, et la culture d'entreprise. Notre approche éprouvée vous guide pas-à-pas vers une architecture microservices résiliente et scalable.

    Fondamentaux de l'architecture microservices

    Définition et principes clés

    Les microservices décomposent une application monolithique en services indépendants, chacun responsable d'une fonction métier spécifique. Cette approche suit des principes fondamentaux :

    1. Single Responsibility Principle (SRP)

    Chaque service gère un domaine métier unique et bien défini.

    2. Décentralisation

    Services autonomes avec leurs propres données et logique métier.

    3. Communication via APIs

    Interactions uniquement par interfaces bien définies (REST, GraphQL, gRPC).

    4. Déploiement indépendant

    Chaque service suit son propre cycle de déploiement.

    Architecture de référence

    typescript
    // Service de base avec patterns modernes
    import express from 'express';
    import { z } from 'zod';
    import { PrismaClient } from '@prisma/client';
    import { Redis } from 'ioredis';
    import { Logger } from 'winston';
    
    // Domain model strict avec validation
    const UserSchema = z.object({
      id: z.string().uuid(),
      email: z.string().email(),
      name: z.string().min(2).max(100),
      createdAt: z.date(),
      updatedAt: z.date(),
    });
    
    type User = z.infer<typeof UserSchema>;
    
    // Repository pattern pour abstraire la persistance
    interface UserRepository {
      findById(id: string): Promise<User | null>;
      findByEmail(email: string): Promise<User | null>;
      create(user: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<User>;
      update(id: string, data: Partial<User>): Promise<User>;
      delete(id: string): Promise<void>;
    }
    
    class PrismaUserRepository implements UserRepository {
      constructor(private prisma: PrismaClient) {}
    
      async findById(id: string): Promise<User | null> {
        const user = await this.prisma.user.findUnique({ where: { id } });
        return user ? UserSchema.parse(user) : null;
      }
    
      async findByEmail(email: string): Promise<User | null> {
        const user = await this.prisma.user.findUnique({ where: { email } });
        return user ? UserSchema.parse(user) : null;
      }
    
      async create(userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<User> {
        const user = await this.prisma.user.create({
          data: {
            ...userData,
            id: crypto.randomUUID(),
            createdAt: new Date(),
            updatedAt: new Date(),
          }
        });
        return UserSchema.parse(user);
      }
    
      async update(id: string, data: Partial<User>): Promise<User> {
        const user = await this.prisma.user.update({
          where: { id },
          data: { ...data, updatedAt: new Date() }
        });
        return UserSchema.parse(user);
      }
    
      async delete(id: string): Promise<void> {
        await this.prisma.user.delete({ where: { id } });
      }
    }
    
    // Service métier avec cache et events
    class UserService {
      constructor(
        private userRepository: UserRepository,
        private cache: Redis,
        private logger: Logger,
        private eventBus: EventBus
      ) {}
    
      async getUserById(id: string): Promise<User | null> {
        // Check cache first
        const cacheKey = `user:${id}`;
        const cached = await this.cache.get(cacheKey);
        
        if (cached) {
          this.logger.info('User cache hit', { userId: id });
          return JSON.parse(cached);
        }
    
        const user = await this.userRepository.findById(id);
        
        if (user) {
          // Cache for 1 hour
          await this.cache.setex(cacheKey, 3600, JSON.stringify(user));
        }
    
        return user;
      }
    
      async createUser(userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<User> {
        // Check if user already exists
        const existingUser = await this.userRepository.findByEmail(userData.email);
        if (existingUser) {
          throw new Error('User already exists with this email');
        }
    
        const user = await this.userRepository.create(userData);
        
        // Emit domain event
        await this.eventBus.publish('user.created', {
          userId: user.id,
          email: user.email,
          timestamp: new Date().toISOString()
        });
    
        this.logger.info('User created', { userId: user.id, email: user.email });
        
        return user;
      }
    
      async updateUser(id: string, data: Partial<User>): Promise<User> {
        const user = await this.userRepository.update(id, data);
        
        // Invalidate cache
        await this.cache.del(`user:${id}`);
        
        // Emit domain event
        await this.eventBus.publish('user.updated', {
          userId: user.id,
          updatedFields: Object.keys(data),
          timestamp: new Date().toISOString()
        });
    
        return user;
      }
    }
    
    // API Controller avec validation stricte
    class UserController {
      constructor(private userService: UserService, private logger: Logger) {}
    
      async getUser(req: express.Request, res: express.Response) {
        try {
          const { id } = req.params;
          
          // Validate UUID format
          const validation = z.string().uuid().safeParse(id);
          if (!validation.success) {
            return res.status(400).json({
              error: 'Invalid user ID format',
              details: validation.error.errors
            });
          }
    
          const user = await this.userService.getUserById(id);
          
          if (!user) {
            return res.status(404).json({ error: 'User not found' });
          }
    
          res.json({ data: user });
        } catch (error) {
          this.logger.error('Error getting user', { error: error.message, userId: req.params.id });
          res.status(500).json({ error: 'Internal server error' });
        }
      }
    
      async createUser(req: express.Request, res: express.Response) {
        try {
          const CreateUserSchema = UserSchema.omit({ 
            id: true, 
            createdAt: true, 
            updatedAt: true 
          });
          
          const validation = CreateUserSchema.safeParse(req.body);
          if (!validation.success) {
            return res.status(400).json({
              error: 'Validation failed',
              details: validation.error.errors
            });
          }
    
          const user = await this.userService.createUser(validation.data);
          
          res.status(201).json({ data: user });
        } catch (error) {
          if (error.message === 'User already exists with this email') {
            return res.status(409).json({ error: error.message });
          }
          
          this.logger.error('Error creating user', { error: error.message, body: req.body });
          res.status(500).json({ error: 'Internal server error' });
        }
      }
    }

    Patterns de communication

    1. Communication synchrone

    typescript
    // Service client avec retry et circuit breaker
    import axios, { AxiosInstance } from 'axios';
    
    class ServiceClient {
      private client: AxiosInstance;
      private circuitBreaker: CircuitBreaker;
    
      constructor(baseURL: string) {
        this.client = axios.create({
          baseURL,
          timeout: 5000,
          headers: {
            'Content-Type': 'application/json',
          }
        });
    
        this.circuitBreaker = new CircuitBreaker(
          this.makeRequest.bind(this),
          {
            timeout: 5000,
            errorThresholdPercentage: 50,
            resetTimeout: 30000
          }
        );
    
        this.setupInterceptors();
      }
    
      private setupInterceptors() {
        // Request interceptor pour l'auth
        this.client.interceptors.request.use((config) => {
          const token = getAuthToken();
          if (token) {
            config.headers.Authorization = `Bearer ${token}`;
          }
          return config;
        });
    
        // Response interceptor pour retry automatique
        this.client.interceptors.response.use(
          (response) => response,
          async (error) => {
            if (error.response?.status === 401) {
              await refreshAuthToken();
              return this.client.request(error.config);
            }
            throw error;
          }
        );
      }
    
      private async makeRequest(config: any) {
        return this.client.request(config);
      }
    
      async get<T>(url: string, config?: any): Promise<T> {
        const response = await this.circuitBreaker.fire({ 
          method: 'GET', 
          url, 
          ...config 
        });
        return response.data;
      }
    
      async post<T>(url: string, data?: any, config?: any): Promise<T> {
        const response = await this.circuitBreaker.fire({ 
          method: 'POST', 
          url, 
          data, 
          ...config 
        });
        return response.data;
      }
    }
    
    // Service de produits utilisant d'autres microservices
    class ProductService {
      private userServiceClient: ServiceClient;
      private inventoryServiceClient: ServiceClient;
      private priceServiceClient: ServiceClient;
    
      constructor() {
        this.userServiceClient = new ServiceClient(process.env.USER_SERVICE_URL!);
        this.inventoryServiceClient = new ServiceClient(process.env.INVENTORY_SERVICE_URL!);
        this.priceServiceClient = new ServiceClient(process.env.PRICE_SERVICE_URL!);
      }
    
      async getProductDetails(productId: string, userId: string): Promise<ProductDetails> {
        // Appels parallèles pour optimiser les performances
        const [product, inventory, pricing, userPreferences] = await Promise.allSettled([
          this.getProduct(productId),
          this.inventoryServiceClient.get(`/inventory/${productId}`),
          this.priceServiceClient.get(`/pricing/${productId}`),
          this.userServiceClient.get(`/users/${userId}/preferences`)
        ]);
    
        return this.combineProductData(product, inventory, pricing, userPreferences);
      }
    
      private combineProductData(...results: any[]): ProductDetails {
        // Logique de combinaison avec gestion des erreurs
        const [productResult, inventoryResult, pricingResult, preferencesResult] = results;
    
        if (productResult.status === 'rejected') {
          throw new Error('Product not found');
        }
    
        return {
          ...productResult.value,
          stock: inventoryResult.status === 'fulfilled' ? inventoryResult.value.stock : 0,
          price: pricingResult.status === 'fulfilled' ? pricingResult.value.price : null,
          personalizedPrice: preferencesResult.status === 'fulfilled' 
            ? this.calculatePersonalizedPrice(pricingResult.value, preferencesResult.value)
            : null
        };
      }
    }

    2. Communication asynchrone avec événements

    typescript
    // Event-driven architecture avec message queues
    import { EventEmitter } from 'events';
    import { RedisClientType } from 'redis';
    
    interface DomainEvent {
      id: string;
      type: string;
      aggregateId: string;
      aggregateType: string;
      data: Record<string, any>;
      metadata: {
        timestamp: string;
        version: number;
        correlationId?: string;
        causationId?: string;
      };
    }
    
    class EventBus {
      private redis: RedisClientType;
      private localEmitter: EventEmitter;
    
      constructor(redis: RedisClientType) {
        this.redis = redis;
        this.localEmitter = new EventEmitter();
      }
    
      async publish(eventType: string, data: Record<string, any>, metadata?: Partial<DomainEvent['metadata']>): Promise<void> {
        const event: DomainEvent = {
          id: crypto.randomUUID(),
          type: eventType,
          aggregateId: data.id || crypto.randomUUID(),
          aggregateType: eventType.split('.')[0],
          data,
          metadata: {
            timestamp: new Date().toISOString(),
            version: 1,
            ...metadata
          }
        };
    
        // Publier en local d'abord
        this.localEmitter.emit(eventType, event);
    
        // Puis sur Redis pour les autres services
        await this.redis.publish(`events:${eventType}`, JSON.stringify(event));
        
        // Store dans event store pour replay/debug
        await this.redis.zadd(
          `eventstore:${event.aggregateType}:${event.aggregateId}`,
          Date.now(),
          JSON.stringify(event)
        );
      }
    
      subscribe(eventType: string, handler: (event: DomainEvent) => Promise<void>): void {
        // Handler local
        this.localEmitter.on(eventType, handler);
    
        // Handler Redis
        this.redis.subscribe(`events:${eventType}`, (message) => {
          const event = JSON.parse(message) as DomainEvent;
          handler(event).catch(error => {
            console.error(`Error handling event ${eventType}:`, error);
            // Dead letter queue
            this.redis.lpush(`dlq:${eventType}`, JSON.stringify({ event, error: error.message }));
          });
        });
      }
    }
    
    // Event handlers avec idempotence
    class OrderEventHandler {
      constructor(
        private inventoryService: InventoryService,
        private notificationService: NotificationService,
        private eventBus: EventBus
      ) {
        this.setupEventHandlers();
      }
    
      private setupEventHandlers() {
        this.eventBus.subscribe('order.created', this.handleOrderCreated.bind(this));
        this.eventBus.subscribe('order.cancelled', this.handleOrderCancelled.bind(this));
        this.eventBus.subscribe('payment.completed', this.handlePaymentCompleted.bind(this));
      }
    
      private async handleOrderCreated(event: DomainEvent): Promise<void> {
        const { orderId, items, userId } = event.data;
    
        // Idempotence check
        const processed = await this.redis.get(`processed:order.created:${event.id}`);
        if (processed) {
          console.log(`Event ${event.id} already processed`);
          return;
        }
    
        try {
          // Reserve inventory
          const reservationId = await this.inventoryService.reserveItems(items);
          
          // Send confirmation email
          await this.notificationService.sendOrderConfirmation(userId, orderId);
          
          // Emit follow-up event
          await this.eventBus.publish('inventory.reserved', {
            orderId,
            reservationId,
            items
          }, {
            correlationId: event.metadata.correlationId,
            causationId: event.id
          });
    
          // Mark as processed
          await this.redis.setex(`processed:order.created:${event.id}`, 86400, 'true');
          
        } catch (error) {
          console.error('Failed to handle order.created:', error);
          
          // Emit compensation event
          await this.eventBus.publish('order.processing.failed', {
            orderId,
            error: error.message,
            originalEvent: event
          });
          
          throw error; // Will go to DLQ
        }
      }
    
      private async handlePaymentCompleted(event: DomainEvent): Promise<void> {
        const { orderId, paymentId, amount } = event.data;
    
        // Confirm inventory reservation
        await this.inventoryService.confirmReservation(orderId);
        
        // Update order status
        await this.eventBus.publish('order.confirmed', {
          orderId,
          paymentId,
          amount,
          confirmedAt: new Date().toISOString()
        });
      }
    }

    Migration du monolithe vers microservices

    Stratégie Strangler Fig Pattern

    La migration progressive est la clé du succès. Nous utilisons le pattern Strangler Fig pour remplacer graduellement les fonctionnalités du monolithe.

    typescript
    // API Gateway avec routing dynamique
    import express from 'express';
    import httpProxy from 'http-proxy-middleware';
    
    class MigrationRouter {
      private app: express.Application;
      private routingConfig: Map<string, ServiceRoute>;
    
      constructor() {
        this.app = express();
        this.routingConfig = new Map();
        this.loadRoutingConfiguration();
        this.setupMiddleware();
      }
    
      private loadRoutingConfiguration() {
        // Configuration centralisée des routes
        const routes: ServiceRoute[] = [
          {
            path: '/api/users',
            target: process.env.USER_SERVICE_URL!,
            migrated: true,
            fallback: process.env.MONOLITH_URL + '/api/users'
          },
          {
            path: '/api/orders',
            target: process.env.ORDER_SERVICE_URL!,
            migrated: true,
            fallback: process.env.MONOLITH_URL + '/api/orders',
            // Feature toggle : certain% vers microservice
            migrationPercentage: 80
          },
          {
            path: '/api/products',
            target: process.env.MONOLITH_URL + '/api/products',
            migrated: false,
            // Prêt pour migration
            futureTarget: process.env.PRODUCT_SERVICE_URL
          }
        ];
    
        routes.forEach(route => {
          this.routingConfig.set(route.path, route);
        });
      }
    
      private setupMiddleware() {
        this.app.use((req, res, next) => {
          const route = this.findMatchingRoute(req.path);
          
          if (!route) {
            // Route vers monolithe par défaut
            return this.proxyToMonolith(req, res, next);
          }
    
          if (!route.migrated) {
            return this.proxyToTarget(route.target)(req, res, next);
          }
    
          // Logique de migration progressive
          if (route.migrationPercentage && route.migrationPercentage < 100) {
            const shouldUseMicroservice = this.shouldRouteTo Microservice(req, route.migrationPercentage);
            const target = shouldUseMicroservice ? route.target : route.fallback!;
            
            // Logging pour monitoring de la migration
            this.logMigrationDecision(req, route, shouldUseMicroservice);
            
            return this.proxyToTarget(target)(req, res, next);
          }
    
          // Route complètement migrée
          return this.proxyToTargetWithFallback(route)(req, res, next);
        });
      }
    
      private shouldRouteToMicroservice(req: express.Request, percentage: number): boolean {
        // Basé sur user ID pour consistance
        if (req.headers['user-id']) {
          const userId = req.headers['user-id'] as string;
          const hash = this.simpleHash(userId);
          return (hash % 100) < percentage;
        }
        
        // Fallback random
        return Math.random() * 100 < percentage;
      }
    
      private proxyToTargetWithFallback(route: ServiceRoute) {
        return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
          const primaryProxy = httpProxy({
            target: route.target,
            changeOrigin: true,
            timeout: 5000,
            onError: (err, req, res) => {
              console.error(`Primary service failed, falling back:`, err.message);
              
              if (route.fallback) {
                // Fallback vers monolithe
                const fallbackProxy = httpProxy({
                  target: route.fallback,
                  changeOrigin: true
                });
                fallbackProxy(req, res, next);
              } else {
                res.status(503).json({ error: 'Service temporarily unavailable' });
              }
            }
          });
    
          primaryProxy(req, res, next);
        };
      }
    
      private logMigrationDecision(req: express.Request, route: ServiceRoute, useMicroservice: boolean) {
        const logData = {
          path: req.path,
          method: req.method,
          userId: req.headers['user-id'],
          routedTo: useMicroservice ? 'microservice' : 'monolith',
          migrationPercentage: route.migrationPercentage,
          timestamp: new Date().toISOString()
        };
    
        // Envoyer vers système de monitoring
        this.sendToMonitoringSystem(logData);
      }
    }
    
    interface ServiceRoute {
      path: string;
      target: string;
      migrated: boolean;
      fallback?: string;
      futureTarget?: string;
      migrationPercentage?: number;
    }

    Domain-Driven Design pour découper les services

    typescript
    // Identification des bounded contexts
    interface BoundedContext {
      name: string;
      description: string;
      entities: string[];
      valueObjects: string[];
      aggregates: string[];
      services: string[];
      repositories: string[];
      events: string[];
    }
    
    // Exemple : E-commerce bounded contexts
    const ecommerceBoundedContexts: BoundedContext[] = [
      {
        name: 'User Management',
        description: 'Gestion des utilisateurs, authentification, profils',
        entities: ['User', 'Profile', 'Preference'],
        valueObjects: ['Email', 'Address', 'PhoneNumber'],
        aggregates: ['UserAggregate'],
        services: ['AuthenticationService', 'ProfileService'],
        repositories: ['UserRepository', 'ProfileRepository'],
        events: ['UserCreated', 'UserUpdated', 'UserDeleted']
      },
      {
        name: 'Product Catalog',
        description: 'Catalogue produits, catégories, recherche',
        entities: ['Product', 'Category', 'Brand'],
        valueObjects: ['Price', 'SKU', 'Weight'],
        aggregates: ['ProductAggregate', 'CategoryAggregate'],
        services: ['ProductSearchService', 'CategoryService'],
        repositories: ['ProductRepository', 'CategoryRepository'],
        events: ['ProductCreated', 'ProductUpdated', 'PriceChanged']
      },
      {
        name: 'Order Management',
        description: 'Commandes, workflow de traitement',
        entities: ['Order', 'OrderItem', 'ShippingAddress'],
        valueObjects: ['OrderStatus', 'TrackingNumber'],
        aggregates: ['OrderAggregate'],
        services: ['OrderProcessingService', 'ShippingService'],
        repositories: ['OrderRepository'],
        events: ['OrderCreated', 'OrderCancelled', 'OrderShipped']
      },
      {
        name: 'Inventory',
        description: 'Stock, réservations, approvisionnement',
        entities: ['InventoryItem', 'Reservation', 'Supplier'],
        valueObjects: ['Quantity', 'Location'],
        aggregates: ['InventoryAggregate'],
        services: ['StockService', 'ReservationService'],
        repositories: ['InventoryRepository'],
        events: ['StockUpdated', 'ItemReserved', 'ItemRestocked']
      }
    ];
    
    // Service generator basé sur DDD
    class MicroserviceGenerator {
      generateService(context: BoundedContext): ServiceDefinition {
        return {
          name: this.toKebabCase(context.name),
          description: context.description,
          database: {
            type: 'postgresql',
            schema: this.generateDatabaseSchema(context)
          },
          api: {
            endpoints: this.generateAPIEndpoints(context),
            events: context.events
          },
          dockerfile: this.generateDockerfile(context),
          kubernetesManifests: this.generateK8sManifests(context)
        };
      }
    
      private generateDatabaseSchema(context: BoundedContext): DatabaseSchema {
        const tables = context.entities.map(entity => ({
          name: this.toSnakeCase(entity),
          columns: this.inferColumns(entity, context),
          indexes: this.generateIndexes(entity),
          relationships: this.generateRelationships(entity, context)
        }));
    
        return { tables };
      }
    
      private generateAPIEndpoints(context: BoundedContext): APIEndpoint[] {
        const endpoints: APIEndpoint[] = [];
    
        context.entities.forEach(entity => {
          const resource = this.toKebabCase(entity);
          
          endpoints.push(
            { method: 'GET', path: `/${resource}`, operation: `list${entity}` },
            { method: 'GET', path: `/${resource}/:id`, operation: `get${entity}` },
            { method: 'POST', path: `/${resource}`, operation: `create${entity}` },
            { method: 'PUT', path: `/${resource}/:id`, operation: `update${entity}` },
            { method: 'DELETE', path: `/${resource}/:id`, operation: `delete${entity}` }
          );
        });
    
        return endpoints;
      }
    }

    Migration data pattern

    typescript
    // Data synchronization during migration
    class DataMigrationService {
      private eventBus: EventBus;
      private monolithDB: MonolithDatabase;
      private microserviceDB: MicroserviceDatabase;
    
      constructor(eventBus: EventBus, monolithDB: MonolithDatabase, microserviceDB: MicroserviceDatabase) {
        this.eventBus = eventBus;
        this.monolithDB = monolithDB;
        this.microserviceDB = microserviceDB;
      }
    
      async migrateUsers(): Promise<MigrationResult> {
        const migrationId = crypto.randomUUID();
        let migratedCount = 0;
        let errorCount = 0;
        const errors: string[] = [];
    
        try {
          // Phase 1: Snapshot des données existantes
          const users = await this.monolithDB.getAllUsers();
          console.log(`Starting migration of ${users.length} users`);
    
          // Phase 2: Migration par batch pour éviter la surcharge
          const batchSize = 100;
          for (let i = 0; i < users.length; i += batchSize) {
            const batch = users.slice(i, i + batchSize);
            
            await this.migrateBatch(batch, migrationId);
            migratedCount += batch.length;
            
            // Log progress
            console.log(`Migrated ${migratedCount}/${users.length} users`);
          }
    
          // Phase 3: Setup dual-write pour synchronisation continue
          await this.setupDualWrite();
    
          // Phase 4: Validation de consistance
          const validationResult = await this.validateDataConsistency();
          
          return {
            migrationId,
            totalRecords: users.length,
            migratedCount,
            errorCount,
            errors,
            validationResult
          };
    
        } catch (error) {
          console.error('Migration failed:', error);
          await this.rollbackMigration(migrationId);
          throw error;
        }
      }
    
      private async migrateBatch(users: MonolithUser[], migrationId: string): Promise<void> {
        const transaction = await this.microserviceDB.beginTransaction();
        
        try {
          for (const user of users) {
            const microserviceUser = this.transformUser(user);
            await this.microserviceDB.insertUser(microserviceUser, transaction);
            
            // Emit event pour notification aux autres services
            await this.eventBus.publish('user.migrated', {
              userId: user.id,
              migrationId,
              originalData: user,
              migratedData: microserviceUser
            });
          }
          
          await transaction.commit();
        } catch (error) {
          await transaction.rollback();
          throw error;
        }
      }
    
      private async setupDualWrite(): Promise<void> {
        // Intercepter les écritures sur le monolithe
        this.eventBus.subscribe('monolith.user.created', async (event) => {
          const user = this.transformUser(event.data.user);
          await this.microserviceDB.insertUser(user);
        });
    
        this.eventBus.subscribe('monolith.user.updated', async (event) => {
          const user = this.transformUser(event.data.user);
          await this.microserviceDB.updateUser(user.id, user);
        });
    
        this.eventBus.subscribe('monolith.user.deleted', async (event) => {
          await this.microserviceDB.deleteUser(event.data.userId);
        });
      }
    
      private transformUser(monolithUser: MonolithUser): MicroserviceUser {
        return {
          id: monolithUser.id,
          email: monolithUser.email,
          name: `${monolithUser.firstName} ${monolithUser.lastName}`,
          profile: {
            firstName: monolithUser.firstName,
            lastName: monolithUser.lastName,
            birthDate: monolithUser.birthDate,
            preferences: JSON.parse(monolithUser.preferences || '{}')
          },
          createdAt: monolithUser.createdAt,
          updatedAt: monolithUser.updatedAt
        };
      }
    
      private async validateDataConsistency(): Promise<ValidationResult> {
        const monolithCount = await this.monolithDB.getUserCount();
        const microserviceCount = await this.microserviceDB.getUserCount();
        
        const sampleUsers = await this.monolithDB.getSampleUsers(100);
        let consistentRecords = 0;
        
        for (const monolithUser of sampleUsers) {
          const microserviceUser = await this.microserviceDB.getUserById(monolithUser.id);
          if (microserviceUser && this.areUsersEquivalent(monolithUser, microserviceUser)) {
            consistentRecords++;
          }
        }
    
        return {
          monolithCount,
          microserviceCount,
          countConsistent: monolithCount === microserviceCount,
          dataConsistencyPercentage: (consistentRecords / sampleUsers.length) * 100,
          validatedSamples: sampleUsers.length
        };
      }
    }

    Patterns de résilience et monitoring

    Circuit Breaker et Retry Patterns

    typescript
    // Circuit breaker sophistiqué avec métriques
    class AdvancedCircuitBreaker {
      private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
      private failureCount = 0;
      private successCount = 0;
      private lastFailureTime?: Date;
      private metrics: CircuitBreakerMetrics;
    
      constructor(private options: CircuitBreakerOptions) {
        this.metrics = new CircuitBreakerMetrics();
      }
    
      async execute<T>(operation: () => Promise<T>): Promise<T> {
        if (this.state === 'OPEN') {
          if (this.shouldAttemptReset()) {
            this.state = 'HALF_OPEN';
          } else {
            throw new Error('Circuit breaker is OPEN');
          }
        }
    
        try {
          const startTime = Date.now();
          const result = await this.executeWithTimeout(operation);
          const duration = Date.now() - startTime;
    
          this.onSuccess(duration);
          return result;
        } catch (error) {
          this.onFailure(error);
          throw error;
        }
      }
    
      private async executeWithTimeout<T>(operation: () => Promise<T>): Promise<T> {
        return Promise.race([
          operation(),
          new Promise<never>((_, reject) => 
            setTimeout(() => reject(new Error('Operation timeout')), this.options.timeout)
          )
        ]);
      }
    
      private onSuccess(duration: number): void {
        this.metrics.recordSuccess(duration);
        
        if (this.state === 'HALF_OPEN') {
          this.successCount++;
          if (this.successCount >= this.options.halfOpenSuccessThreshold) {
            this.reset();
          }
        } else {
          this.failureCount = 0;
        }
      }
    
      private onFailure(error: Error): void {
        this.metrics.recordFailure(error);
        this.failureCount++;
        this.lastFailureTime = new Date();
    
        if (this.state === 'HALF_OPEN') {
          this.state = 'OPEN';
        } else if (this.failureCount >= this.options.failureThreshold) {
          this.state = 'OPEN';
        }
      }
    
      private shouldAttemptReset(): boolean {
        if (!this.lastFailureTime) return false;
        
        const timeSinceLastFailure = Date.now() - this.lastFailureTime.getTime();
        return timeSinceLastFailure >= this.options.resetTimeout;
      }
    
      private reset(): void {
        this.state = 'CLOSED';
        this.failureCount = 0;
        this.successCount = 0;
        this.lastFailureTime = undefined;
      }
    
      getMetrics(): CircuitBreakerMetrics {
        return this.metrics;
      }
    }
    
    // Metrics collection avec export Prometheus
    class CircuitBreakerMetrics {
      private requestCount = 0;
      private failureCount = 0;
      private totalDuration = 0;
      private requestDurations: number[] = [];
    
      recordSuccess(duration: number): void {
        this.requestCount++;
        this.totalDuration += duration;
        this.requestDurations.push(duration);
        
        // Garder seulement les 1000 dernières mesures
        if (this.requestDurations.length > 1000) {
          this.requestDurations = this.requestDurations.slice(-1000);
        }
      }
    
      recordFailure(error: Error): void {
        this.requestCount++;
        this.failureCount++;
      }
    
      getSuccessRate(): number {
        if (this.requestCount === 0) return 100;
        return ((this.requestCount - this.failureCount) / this.requestCount) * 100;
      }
    
      getAverageResponseTime(): number {
        if (this.requestCount === 0) return 0;
        return this.totalDuration / this.requestCount;
      }
    
      getP95ResponseTime(): number {
        if (this.requestDurations.length === 0) return 0;
        
        const sorted = [...this.requestDurations].sort((a, b) => a - b);
        const index = Math.ceil(sorted.length * 0.95) - 1;
        return sorted[index];
      }
    
      toPrometheusMetrics(serviceName: string): string {
        return `
    # HELP circuit_breaker_requests_total Total number of requests
    # TYPE circuit_breaker_requests_total counter
    circuit_breaker_requests_total{service="${serviceName}"} ${this.requestCount}
    
    # HELP circuit_breaker_failures_total Total number of failures
    # TYPE circuit_breaker_failures_total counter
    circuit_breaker_failures_total{service="${serviceName}"} ${this.failureCount}
    
    # HELP circuit_breaker_success_rate Success rate percentage
    # TYPE circuit_breaker_success_rate gauge
    circuit_breaker_success_rate{service="${serviceName}"} ${this.getSuccessRate()}
    
    # HELP circuit_breaker_response_time_avg Average response time in ms
    # TYPE circuit_breaker_response_time_avg gauge
    circuit_breaker_response_time_avg{service="${serviceName}"} ${this.getAverageResponseTime()}
    
    # HELP circuit_breaker_response_time_p95 95th percentile response time in ms
    # TYPE circuit_breaker_response_time_p95 gauge
    circuit_breaker_response_time_p95{service="${serviceName}"} ${this.getP95ResponseTime()}
        `.trim();
      }
    }

    Distributed Tracing et Observability

    typescript
    // OpenTelemetry integration pour tracing distribué
    import { trace, context, SpanStatusCode } from '@opentelemetry/api';
    import { NodeSDK } from '@opentelemetry/sdk-node';
    import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
    import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
    
    class DistributedTracingSetup {
      static initialize(serviceName: string): NodeSDK {
        const jaegerExporter = new JaegerExporter({
          endpoint: process.env.JAEGER_ENDPOINT || 'http://localhost:14268/api/traces',
        });
    
        const sdk = new NodeSDK({
          serviceName,
          traceExporter: jaegerExporter,
          instrumentations: [getNodeAutoInstrumentations()],
        });
    
        sdk.start();
        return sdk;
      }
    }
    
    // Service avec tracing intégré
    class TracedUserService {
      private tracer = trace.getTracer('user-service');
    
      async getUserWithRelatedData(userId: string): Promise<UserWithRelatedData> {
        // Créer span parent
        return await this.tracer.startActiveSpan('getUserWithRelatedData', async (span) => {
          try {
            span.setAttributes({
              'user.id': userId,
              'operation': 'getUserWithRelatedData'
            });
    
            // Récupération user avec span enfant
            const user = await this.tracer.startActiveSpan('getUser', async (childSpan) => {
              childSpan.setAttributes({ 'db.operation': 'select', 'db.table': 'users' });
              const result = await this.userRepository.findById(userId);
              
              if (!result) {
                childSpan.recordException(new Error('User not found'));
                childSpan.setStatus({ code: SpanStatusCode.ERROR, message: 'User not found' });
                throw new Error('User not found');
              }
              
              childSpan.setAttributes({ 'user.found': true });
              childSpan.end();
              return result;
            });
    
            // Appels parallèles avec tracing
            const [orders, preferences, notifications] = await Promise.all([
              this.getOrdersWithTracing(userId),
              this.getPreferencesWithTracing(userId),
              this.getNotificationsWithTracing(userId)
            ]);
    
            const result = {
              user,
              orders,
              preferences,
              notifications
            };
    
            span.setAttributes({
              'result.orders_count': orders.length,
              'result.has_preferences': !!preferences,
              'result.notifications_count': notifications.length
            });
    
            span.setStatus({ code: SpanStatusCode.OK });
            return result;
    
          } catch (error) {
            span.recordException(error as Error);
            span.setStatus({ 
              code: SpanStatusCode.ERROR, 
              message: (error as Error).message 
            });
            throw error;
          } finally {
            span.end();
          }
        });
      }
    
      private async getOrdersWithTracing(userId: string): Promise<Order[]> {
        return await this.tracer.startActiveSpan('getOrders', async (span) => {
          span.setAttributes({ 
            'service.call': 'order-service',
            'user.id': userId 
          });
    
          try {
            const orders = await this.orderServiceClient.get(`/users/${userId}/orders`);
            span.setAttributes({ 'orders.count': orders.length });
            return orders;
          } catch (error) {
            span.recordException(error as Error);
            span.setStatus({ code: SpanStatusCode.ERROR });
            
            // Fallback graceful
            console.warn('Failed to get orders, returning empty array:', error);
            return [];
          } finally {
            span.end();
          }
        });
      }
    
      // Custom metrics pour business logic
      private async recordUserMetrics(user: User): Promise<void> {
        const span = this.tracer.startSpan('recordUserMetrics');
        
        try {
          // Business metrics
          span.setAttributes({
            'user.tier': user.tier,
            'user.registration_days': this.daysSinceRegistration(user.createdAt),
            'user.last_activity_days': this.daysSinceLastActivity(user.lastActiveAt)
          });
    
          // Custom events
          span.addEvent('user_metrics_recorded', {
            'user.id': user.id,
            'timestamp': new Date().toISOString()
          });
    
          // Export vers système de métriques métier
          await this.businessMetrics.record('user_activity', {
            userId: user.id,
            tier: user.tier,
            registrationAge: this.daysSinceRegistration(user.createdAt)
          });
    
        } finally {
          span.end();
        }
      }
    }
    
    // Health checks avec métriques
    class HealthCheckService {
      private checks: Map<string, HealthCheck> = new Map();
    
      constructor() {
        this.registerDefaultChecks();
      }
    
      private registerDefaultChecks(): void {
        // Database health
        this.checks.set('database', {
          name: 'database',
          check: async () => {
            const start = Date.now();
            await this.database.query('SELECT 1');
            const duration = Date.now() - start;
            
            return {
              status: 'healthy',
              duration,
              details: { query: 'SELECT 1' }
            };
          },
          timeout: 5000,
          critical: true
        });
    
        // External service health
        this.checks.set('user-service', {
          name: 'user-service',
          check: async () => {
            const response = await fetch(`${process.env.USER_SERVICE_URL}/health`);
            return {
              status: response.ok ? 'healthy' : 'unhealthy',
              details: { 
                status: response.status,
                url: process.env.USER_SERVICE_URL 
              }
            };
          },
          timeout: 3000,
          critical: false
        });
    
        // Memory usage
        this.checks.set('memory', {
          name: 'memory',
          check: async () => {
            const usage = process.memoryUsage();
            const heapUsedMB = usage.heapUsed / 1024 / 1024;
            const heapTotalMB = usage.heapTotal / 1024 / 1024;
            const usagePercentage = (heapUsedMB / heapTotalMB) * 100;
    
            return {
              status: usagePercentage > 90 ? 'unhealthy' : 'healthy',
              details: {
                heapUsedMB: Math.round(heapUsedMB),
                heapTotalMB: Math.round(heapTotalMB),
                usagePercentage: Math.round(usagePercentage)
              }
            };
          },
          timeout: 1000,
          critical: false
        });
      }
    
      async runHealthChecks(): Promise<HealthCheckResult> {
        const results = new Map<string, CheckResult>();
        let overallStatus: 'healthy' | 'degraded' | 'unhealthy' = 'healthy';
    
        await Promise.allSettled([...this.checks.entries()].map(async ([name, check]) => {
          try {
            const result = await this.runSingleCheck(check);
            results.set(name, result);
    
            if (result.status === 'unhealthy') {
              if (check.critical) {
                overallStatus = 'unhealthy';
              } else if (overallStatus === 'healthy') {
                overallStatus = 'degraded';
              }
            }
          } catch (error) {
            results.set(name, {
              status: 'unhealthy',
              error: (error as Error).message,
              duration: 0
            });
    
            if (check.critical) {
              overallStatus = 'unhealthy';
            }
          }
        }));
    
        return {
          status: overallStatus,
          timestamp: new Date().toISOString(),
          checks: Object.fromEntries(results),
          version: process.env.APP_VERSION || 'unknown'
        };
      }
    
      private async runSingleCheck(check: HealthCheck): Promise<CheckResult> {
        const start = Date.now();
        
        const timeoutPromise = new Promise<never>((_, reject) => {
          setTimeout(() => reject(new Error('Health check timeout')), check.timeout);
        });
    
        try {
          const result = await Promise.race([check.check(), timeoutPromise]);
          const duration = Date.now() - start;
          
          return {
            ...result,
            duration
          };
        } catch (error) {
          return {
            status: 'unhealthy',
            error: (error as Error).message,
            duration: Date.now() - start
          };
        }
      }
    }

    Patterns de sécurité dans les microservices

    JWT et service-to-service authentication

    typescript
    // Sécurité inter-services avec JWT et mTLS
    import jwt from 'jsonwebtoken';
    import fs from 'fs';
    import https from 'https';
    
    class ServiceAuthManager {
      private privateKey: string;
      private publicKeys: Map<string, string> = new Map();
      private serviceCertificates: Map<string, { cert: string; key: string }> = new Map();
    
      constructor() {
        this.loadKeys();
        this.loadServiceCertificates();
      }
    
      private loadKeys(): void {
        // Clés asymétriques pour JWT
        this.privateKey = fs.readFileSync(process.env.JWT_PRIVATE_KEY_PATH!, 'utf8');
        
        // Clés publiques des autres services
        const publicKeysConfig = JSON.parse(process.env.SERVICE_PUBLIC_KEYS || '{}');
        Object.entries(publicKeysConfig).forEach(([service, keyPath]) => {
          this.publicKeys.set(service, fs.readFileSync(keyPath as string, 'utf8'));
        });
      }
    
      private loadServiceCertificates(): void {
        // Certificats mTLS pour chaque service
        const certConfig = JSON.parse(process.env.SERVICE_CERTIFICATES || '{}');
        Object.entries(certConfig).forEach(([service, config]: [string, any]) => {
          this.serviceCertificates.set(service, {
            cert: fs.readFileSync(config.cert, 'utf8'),
            key: fs.readFileSync(config.key, 'utf8')
          });
        });
      }
    
      generateServiceToken(targetService: string, claims: Record<string, any> = {}): string {
        const payload = {
          iss: process.env.SERVICE_NAME, // issuer
          aud: targetService, // audience
          sub: 'service-to-service', // subject
          iat: Math.floor(Date.now() / 1000), // issued at
          exp: Math.floor(Date.now() / 1000) + (15 * 60), // expire in 15 minutes
          ...claims
        };
    
        return jwt.sign(payload, this.privateKey, { algorithm: 'RS256' });
      }
    
      verifyServiceToken(token: string, fromService: string): any {
        const publicKey = this.publicKeys.get(fromService);
        if (!publicKey) {
          throw new Error(`No public key found for service: ${fromService}`);
        }
    
        try {
          const decoded = jwt.verify(token, publicKey, { 
            algorithms: ['RS256'],
            audience: process.env.SERVICE_NAME,
            issuer: fromService
          });
    
          return decoded;
        } catch (error) {
          throw new Error(`Token verification failed: ${(error as Error).message}`);
        }
      }
    
      createSecureHttpsAgent(targetService: string): https.Agent {
        const certs = this.serviceCertificates.get(targetService);
        if (!certs) {
          throw new Error(`No certificates found for service: ${targetService}`);
        }
    
        return new https.Agent({
          cert: certs.cert,
          key: certs.key,
          ca: fs.readFileSync(process.env.CA_CERT_PATH!, 'utf8'),
          rejectUnauthorized: true
        });
      }
    }
    
    // Middleware d'authentification inter-services
    const serviceAuthMiddleware = (authManager: ServiceAuthManager) => {
      return (req: express.Request, res: express.Response, next: express.NextFunction) => {
        const authHeader = req.headers.authorization;
        
        if (!authHeader || !authHeader.startsWith('Bearer ')) {
          return res.status(401).json({ error: 'Missing or invalid authorization header' });
        }
    
        const token = authHeader.substring(7);
        const callingService = req.headers['x-calling-service'] as string;
    
        if (!callingService) {
          return res.status(401).json({ error: 'Missing x-calling-service header' });
        }
    
        try {
          const decoded = authManager.verifyServiceToken(token, callingService);
          (req as any).serviceAuth = decoded;
          next();
        } catch (error) {
          return res.status(401).json({ 
            error: 'Token verification failed', 
            details: (error as Error).message 
          });
        }
      };
    };
    
    // Client sécurisé pour appels inter-services
    class SecureServiceClient {
      private authManager: ServiceAuthManager;
      private httpAgent: https.Agent;
    
      constructor(private targetService: string) {
        this.authManager = new ServiceAuthManager();
        this.httpAgent = this.authManager.createSecureHttpsAgent(targetService);
      }
    
      async request<T>(options: ServiceRequestOptions): Promise<T> {
        // Générer token pour ce service
        const token = this.authManager.generateServiceToken(this.targetService, {
          operation: options.operation,
          permissions: options.requiredPermissions || []
        });
    
        const requestConfig = {
          ...options,
          headers: {
            'Authorization': `Bearer ${token}`,
            'X-Calling-Service': process.env.SERVICE_NAME!,
            'Content-Type': 'application/json',
            ...options.headers
          },
          httpsAgent: this.httpAgent
        };
    
        try {
          const response = await axios.request<T>(requestConfig);
          return response.data;
        } catch (error) {
          if (axios.isAxiosError(error)) {
            if (error.response?.status === 401) {
              throw new Error('Service authentication failed');
            }
            if (error.response?.status === 403) {
              throw new Error('Service authorization denied');
            }
          }
          throw error;
        }
      }
    }

    API Gateway avec rate limiting et authorization

    typescript
    // API Gateway avec sécurité avancée
    import rateLimit from 'express-rate-limit';
    import helmet from 'helmet';
    
    class APIGateway {
      private app: express.Application;
      private rateLimiters: Map<string, any> = new Map();
      private authManager: ServiceAuthManager;
    
      constructor() {
        this.app = express();
        this.authManager = new ServiceAuthManager();
        this.setupSecurity();
        this.setupRateLimiting();
        this.setupRouting();
      }
    
      private setupSecurity(): void {
        // Helmet pour headers de sécurité
        this.app.use(helmet({
          contentSecurityPolicy: {
            directives: {
              defaultSrc: ["'self'"],
              styleSrc: ["'self'", "'unsafe-inline'"],
              scriptSrc: ["'self'"],
              imgSrc: ["'self'", "data:", "https:"],
              connectSrc: ["'self'"],
              fontSrc: ["'self'"],
              objectSrc: ["'none'"],
              mediaSrc: ["'self'"],
              frameSrc: ["'none'"],
            },
          },
          crossOriginEmbedderPolicy: false
        }));
    
        // CORS configuré pour microservices
        this.app.use((req, res, next) => {
          const origin = req.headers.origin;
          const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [];
          
          if (allowedOrigins.includes(origin || '')) {
            res.header('Access-Control-Allow-Origin', origin);
          }
          
          res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
          res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
          res.header('Access-Control-Max-Age', '86400');
          
          if (req.method === 'OPTIONS') {
            res.sendStatus(200);
          } else {
            next();
          }
        });
      }
    
      private setupRateLimiting(): void {
        // Rate limiting par endpoint
        const endpoints = [
          {
            path: '/api/auth/login',
            windowMs: 15 * 60 * 1000, // 15 minutes
            max: 5, // 5 tentatives par IP
            message: 'Too many login attempts'
          },
          {
            path: '/api/users',
            windowMs: 60 * 1000, // 1 minute  
            max: 100, // 100 requêtes par minute
            message: 'Rate limit exceeded'
          }
        ];
    
        endpoints.forEach(config => {
          const limiter = rateLimit({
            windowMs: config.windowMs,
            max: config.max,
            message: { error: config.message },
            standardHeaders: true,
            legacyHeaders: false,
            // Store in Redis pour cluster
            store: new RedisStore({
              client: this.redisClient,
              prefix: `rl:${config.path}:`
            })
          });
    
          this.rateLimiters.set(config.path, limiter);
        });
      }
    
      private setupRouting(): void {
        // Middleware global d'authentification
        this.app.use('/api', this.authenticationMiddleware.bind(this));
        
        // Routes avec authorization granulaire
        this.app.use('/api/users', 
          this.getRateLimiter('/api/users'),
          this.authorizationMiddleware(['users:read', 'users:write']),
          this.proxyToService('user-service')
        );
    
        this.app.use('/api/orders',
          this.getRateLimiter('/api/orders'),
          this.authorizationMiddleware(['orders:read', 'orders:write']),
          this.proxyToService('order-service')
        );
      }
    
      private authenticationMiddleware(req: express.Request, res: express.Response, next: express.NextFunction): void {
        const authHeader = req.headers.authorization;
        
        if (!authHeader || !authHeader.startsWith('Bearer ')) {
          return res.status(401).json({ error: 'Missing or invalid authorization header' });
        }
    
        const token = authHeader.substring(7);
    
        try {
          // Vérifier JWT utilisateur
          const decoded = jwt.verify(token, process.env.JWT_PUBLIC_KEY!, { algorithms: ['RS256'] });
          (req as any).user = decoded;
          next();
        } catch (error) {
          return res.status(401).json({ error: 'Invalid token' });
        }
      }
    
      private authorizationMiddleware(requiredPermissions: string[]) {
        return (req: express.Request, res: express.Response, next: express.NextFunction) => {
          const user = (req as any).user;
          
          if (!user || !user.permissions) {
            return res.status(403).json({ error: 'Insufficient permissions' });
          }
    
          const hasPermission = requiredPermissions.every(permission => 
            user.permissions.includes(permission)
          );
    
          if (!hasPermission) {
            return res.status(403).json({ 
              error: 'Missing required permissions',
              required: requiredPermissions,
              current: user.permissions
            });
          }
    
          next();
        };
      }
    
      private getRateLimiter(path: string): any {
        return this.rateLimiters.get(path) || this.rateLimiters.get('default');
      }
    
      private proxyToService(serviceName: string) {
        const serviceURL = process.env[`${serviceName.toUpperCase().replace('-', '_')}_URL`];
        
        return httpProxy({
          target: serviceURL,
          changeOrigin: true,
          pathRewrite: {
            '^/api': '' // Remove /api prefix when forwarding
          },
          onProxyReq: (proxyReq, req) => {
            // Ajouter headers pour le service
            proxyReq.setHeader('X-User-ID', (req as any).user?.sub);
            proxyReq.setHeader('X-User-Permissions', JSON.stringify((req as any).user?.permissions || []));
            proxyReq.setHeader('X-Gateway-Request-ID', crypto.randomUUID());
          },
          onError: (err, req, res) => {
            console.error(`Proxy error for ${serviceName}:`, err);
            (res as express.Response).status(503).json({ 
              error: 'Service temporarily unavailable' 
            });
          }
        });
      }
    }

    Déploiement et orchestration

    Kubernetes avec Helm charts

    yaml
    # Chart.yaml
    apiVersion: v2
    name: microservice-template
    description: Template Helm chart for microservices
    type: application
    version: 0.1.0
    appVersion: "1.0.0"
    
    dependencies:
      - name: postgresql
        version: 11.9.13
        repository: https://charts.bitnami.com/bitnami
        condition: postgresql.enabled
      - name: redis
        version: 17.3.7
        repository: https://charts.bitnami.com/bitnami
        condition: redis.enabled
    yaml
    # values.yaml
    replicaCount: 3
    
    image:
      repository: myregistry/microservice
      pullPolicy: IfNotPresent
      tag: ""
    
    service:
      type: ClusterIP
      port: 3000
      targetPort: 3000
    
    ingress:
      enabled: true
      className: "nginx"
      annotations:
        cert-manager.io/cluster-issuer: "letsencrypt-prod"
        nginx.ingress.kubernetes.io/rate-limit: "100"
        nginx.ingress.kubernetes.io/rate-limit-window: "1m"
      hosts:
        - host: api.example.com
          paths:
            - path: /users
              pathType: Prefix
      tls:
        - secretName: api-tls
          hosts:
            - api.example.com
    
    resources:
      limits:
        cpu: 500m
        memory: 512Mi
      requests:
        cpu: 250m
        memory: 256Mi
    
    autoscaling:
      enabled: true
      minReplicas: 3
      maxReplicas: 10
      targetCPUUtilizationPercentage: 70
      targetMemoryUtilizationPercentage: 80
    
    # Base de données
    postgresql:
      enabled: true
      auth:
        postgresPassword: "secure-password"
        database: "microservice_db"
      primary:
        persistence:
          enabled: true
          size: 8Gi
    
    # Cache
    redis:
      enabled: true
      auth:
        enabled: true
        password: "redis-password"
      master:
        persistence:
          enabled: true
          size: 8Gi
    
    # Monitoring
    monitoring:
      enabled: true
      serviceMonitor:
        enabled: true
        interval: 30s
        path: /metrics
    
    # Configuration
    config:
      nodeEnv: production
      logLevel: info
      
    secrets:
      jwtSecret: "jwt-secret-key"
      databaseUrl: "postgresql://user:pass@localhost:5432/db"
    yaml
    # templates/deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: {{ include "microservice.fullname" . }}
      labels:
        {{- include "microservice.labels" . | nindent 4 }}
    spec:
      {{- if not .Values.autoscaling.enabled }}
      replicas: {{ .Values.replicaCount }}
      {{- end }}
      selector:
        matchLabels:
          {{- include "microservice.selectorLabels" . | nindent 6 }}
      template:
        metadata:
          annotations:
            checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
            checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
          labels:
            {{- include "microservice.selectorLabels" . | nindent 8 }}
        spec:
          serviceAccountName: {{ include "microservice.serviceAccountName" . }}
          securityContext:
            {{- toYaml .Values.podSecurityContext | nindent 8 }}
          containers:
            - name: {{ .Chart.Name }}
              securityContext:
                {{- toYaml .Values.securityContext | nindent 12 }}
              image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
              imagePullPolicy: {{ .Values.image.pullPolicy }}
              ports:
                - name: http
                  containerPort: {{ .Values.service.targetPort }}
                  protocol: TCP
              livenessProbe:
                httpGet:
                  path: /health
                  port: http
                initialDelaySeconds: 30
                periodSeconds: 10
                timeoutSeconds: 5
                failureThreshold: 3
              readinessProbe:
                httpGet:
                  path: /health/ready
                  port: http
                initialDelaySeconds: 5
                periodSeconds: 5
                timeoutSeconds: 3
                failureThreshold: 3
              resources:
                {{- toYaml .Values.resources | nindent 12 }}
              env:
                - name: NODE_ENV
                  value: {{ .Values.config.nodeEnv }}
                - name: LOG_LEVEL
                  value: {{ .Values.config.logLevel }}
                - name: PORT
                  value: "{{ .Values.service.targetPort }}"
                - name: DATABASE_URL
                  valueFrom:
                    secretKeyRef:
                      name: {{ include "microservice.fullname" . }}-secret
                      key: database-url
                - name: JWT_SECRET
                  valueFrom:
                    secretKeyRef:
                      name: {{ include "microservice.fullname" . }}-secret
                      key: jwt-secret
                - name: REDIS_URL
                  value: "redis://{{ include "microservice.fullname" . }}-redis-master:6379"
              volumeMounts:
                - name: config
                  mountPath: /app/config
                  readOnly: true
          volumes:
            - name: config
              configMap:
                name: {{ include "microservice.fullname" . }}-config
          {{- with .Values.nodeSelector }}
          nodeSelector:
            {{- toYaml . | nindent 8 }}
          {{- end }}

    CI/CD Pipeline avec GitLab

    yaml
    # .gitlab-ci.yml
    stages:
      - test
      - build
      - security-scan
      - deploy-staging
      - integration-tests
      - deploy-production
    
    variables:
      DOCKER_REGISTRY: registry.gitlab.com
      DOCKER_IMAGE: $DOCKER_REGISTRY/$CI_PROJECT_PATH
      KUBECONFIG: /etc/kubeconfig
    
    # Tests unitaires et intégration
    test:unit:
      stage: test
      image: node:18-alpine
      cache:
        paths:
          - node_modules/
      script:
        - npm ci
        - npm run test:unit
        - npm run test:integration
      coverage: '/Statements\s*:\s*([^%]+)/'
      artifacts:
        reports:
          coverage_report:
            coverage_format: cobertura
            path: coverage/cobertura-coverage.xml
          junit: junit.xml
      only:
        - merge_requests
        - main
        - develop
    
    # Tests de sécurité statique
    security:sast:
      stage: test
      image: node:18-alpine
      script:
        - npm ci
        - npm audit --audit-level high
        - npx semgrep --config=auto .
      only:
        - merge_requests
        - main
    
    # Build et push Docker
    build:docker:
      stage: build
      image: docker:latest
      services:
        - docker:dind
      before_script:
        - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
      script:
        - |
          if [ "$CI_COMMIT_REF_NAME" = "main" ]; then
            export IMAGE_TAG="latest"
          else
            export IMAGE_TAG="$CI_COMMIT_SHORT_SHA"
          fi
        - docker build -t $DOCKER_IMAGE:$IMAGE_TAG .
        - docker push $DOCKER_IMAGE:$IMAGE_TAG
        # Multi-arch build pour production
        - |
          if [ "$CI_COMMIT_REF_NAME" = "main" ]; then
            docker buildx create --use
            docker buildx build --platform linux/amd64,linux/arm64 -t $DOCKER_IMAGE:$IMAGE_TAG --push .
          fi
      only:
        - main
        - develop
    
    # Scan de sécurité des images
    security:container:
      stage: security-scan
      image: aquasec/trivy:latest
      script:
        - trivy image --exit-code 1 --severity HIGH,CRITICAL $DOCKER_IMAGE:$CI_COMMIT_SHORT_SHA
      only:
        - main
        - develop
    
    # Déploiement staging
    deploy:staging:
      stage: deploy-staging
      image: bitnami/kubectl:latest
      environment:
        name: staging
        url: https://staging-api.example.com
      before_script:
        - kubectl config use-context staging
      script:
        - helm upgrade --install microservice-staging ./helm-chart
            --namespace staging
            --create-namespace
            --set image.tag=$CI_COMMIT_SHORT_SHA
            --set ingress.hosts[0].host=staging-api.example.com
            --set config.nodeEnv=staging
            --set replicaCount=2
            --wait --timeout=300s
      only:
        - develop
    
    # Tests d'intégration end-to-end
    integration:tests:
      stage: integration-tests
      image: node:18-alpine
      dependencies:
        - deploy:staging
      script:
        - npm ci
        - export API_BASE_URL=https://staging-api.example.com
        - npm run test:e2e
      artifacts:
        reports:
          junit: e2e-results.xml
        paths:
          - e2e-screenshots/
        when: always
      only:
        - develop
    
    # Déploiement production avec approbation manuelle
    deploy:production:
      stage: deploy-production
      image: bitnami/kubectl:latest
      environment:
        name: production
        url: https://api.example.com
      before_script:
        - kubectl config use-context production
      script:
        # Blue-Green deployment
        - |
          CURRENT_COLOR=$(kubectl get service microservice-active -o jsonpath='{.spec.selector.color}' || echo "blue")
          if [ "$CURRENT_COLOR" = "blue" ]; then
            NEW_COLOR="green"
          else
            NEW_COLOR="blue"
          fi
          echo "Deploying to $NEW_COLOR environment"
        
        # Deploy nouvelle version
        - helm upgrade --install microservice-$NEW_COLOR ./helm-chart
            --namespace production
            --set image.tag=$CI_COMMIT_SHORT_SHA
            --set ingress.hosts[0].host=api.example.com
            --set config.nodeEnv=production
            --set replicaCount=5
            --set service.name=microservice-$NEW_COLOR
            --wait --timeout=600s
        
        # Health check de la nouvelle version
        - kubectl wait --for=condition=ready pod -l app=microservice,color=$NEW_COLOR --timeout=300s
        
        # Switch traffic vers nouvelle version
        - kubectl patch service microservice-active -p '{"spec":{"selector":{"color":"'$NEW_COLOR'"}}}'
        
        # Attendre stabilisation
        - sleep 60
        
        # Cleanup ancienne version après 10 minutes
        - |
          (sleep 600 && helm uninstall microservice-$CURRENT_COLOR --namespace production) &
      when: manual
      only:
        - main
    
    # Rollback production en cas de problème
    rollback:production:
      stage: deploy-production
      image: bitnami/kubectl:latest
      environment:
        name: production
        url: https://api.example.com
      script:
        - kubectl config use-context production
        - helm rollback microservice --namespace production
      when: manual
      only:
        - main

    Monitoring et observabilité avancés

    Stack complète de monitoring

    yaml
    # monitoring/prometheus-config.yaml
    global:
      scrape_interval: 15s
      evaluation_interval: 15s
    
    rule_files:
      - "rules/*.yml"
    
    alerting:
      alertmanagers:
        - static_configs:
            - targets:
              - alertmanager:9093
    
    scrape_configs:
      # Microservices discovery via Kubernetes
      - job_name: 'kubernetes-pods'
        kubernetes_sd_configs:
          - role: pod
        relabel_configs:
          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
            action: keep
            regex: true
          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
            action: replace
            target_label: __metrics_path__
            regex: (.+)
          - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
            action: replace
            regex: ([^:]+)(?::\d+)?;(\d+)
            replacement: $1:$2
            target_label: __address__
    
      # Business metrics custom
      - job_name: 'business-metrics'
        static_configs:
          - targets: ['business-metrics-service:8080']
        scrape_interval: 30s
        metrics_path: /business-metrics
    
      # Infrastructure
      - job_name: 'node-exporter'
        static_configs:
          - targets: ['node-exporter:9100']
    
      - job_name: 'postgres-exporter'
        static_configs:
          - targets: ['postgres-exporter:9187']
    yaml
    # monitoring/alerts.yml
    groups:
      - name: microservices.rules
        rules:
          # SLA: Availability > 99.9%
          - alert: ServiceDown
            expr: up == 0
            for: 1m
            labels:
              severity: critical
            annotations:
              summary: "Service {{ $labels.instance }} is down"
              description: "{{ $labels.instance }} has been down for more than 1 minute"
    
          # SLA: Response time < 500ms for 95% of requests
          - alert: HighResponseTime
            expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 0.5
            for: 2m
            labels:
              severity: warning
            annotations:
              summary: "High response time for {{ $labels.service }}"
              description: "95th percentile response time is {{ $value }}s"
    
          # SLA: Error rate < 1%
          - alert: HighErrorRate
            expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.01
            for: 2m
            labels:
              severity: critical
            annotations:
              summary: "High error rate for {{ $labels.service }}"
              description: "Error rate is {{ $value | humanizePercentage }}"
    
          # Business metrics
          - alert: LowOrderConversionRate
            expr: rate(orders_completed_total[1h]) / rate(orders_started_total[1h]) < 0.7
            for: 5m
            labels:
              severity: warning
            annotations:
              summary: "Order conversion rate is low"
              description: "Conversion rate is {{ $value | humanizePercentage }} (threshold: 70%)"
    
          # Infrastructure
          - alert: HighMemoryUsage
            expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) > 0.85
            for: 2m
            labels:
              severity: warning
            annotations:
              summary: "High memory usage on {{ $labels.instance }}"
              description: "Memory usage is {{ $value | humanizePercentage }}"
    
          - alert: DatabaseConnectionsHigh
            expr: postgres_stat_activity_count > 80
            for: 1m
            labels:
              severity: warning
            annotations:
              summary: "High database connections"
              description: "{{ $value }} active connections (threshold: 80)"

    Dashboard Grafana personnalisé

    json
    {
      "dashboard": {
        "title": "Microservices Overview",
        "panels": [
          {
            "title": "Service Health Status",
            "type": "stat",
            "targets": [
              {
                "expr": "up",
                "legendFormat": "{{ instance }}"
              }
            ],
            "fieldConfig": {
              "defaults": {
                "color": {
                  "mode": "thresholds"
                },
                "thresholds": {
                  "steps": [
                    { "value": 0, "color": "red" },
                    { "value": 1, "color": "green" }
                  ]
                }
              }
            }
          },
          {
            "title": "Request Rate (req/s)",
            "type": "graph",
            "targets": [
              {
                "expr": "rate(http_requests_total[1m])",
                "legendFormat": "{{ service }} - {{ method }}"
              }
            ]
          },
          {
            "title": "Response Time Distribution",
            "type": "heatmap",
            "targets": [
              {
                "expr": "rate(http_request_duration_seconds_bucket[1m])",
                "legendFormat": "{{ le }}"
              }
            ]
          },
          {
            "title": "Error Rate by Service",
            "type": "graph",
            "targets": [
              {
                "expr": "rate(http_requests_total{status=~\"5..\"}[1m]) / rate(http_requests_total[1m])",
                "legendFormat": "{{ service }}"
              }
            ],
            "yAxes": [
              {
                "unit": "percentunit",
                "max": 0.1
              }
            ]
          },
          {
            "title": "Business Metrics",
            "type": "row",
            "panels": [
              {
                "title": "Orders per Hour",
                "type": "stat",
                "targets": [
                  {
                    "expr": "rate(orders_created_total[1h]) * 3600",
                    "legendFormat": "Orders/hour"
                  }
                ]
              },
              {
                "title": "Revenue Trend",
                "type": "graph",
                "targets": [
                  {
                    "expr": "rate(revenue_total[1h]) * 3600",
                    "legendFormat": "Revenue/hour"
                  }
                ]
              },
              {
                "title": "User Signups",
                "type": "graph",
                "targets": [
                  {
                    "expr": "rate(user_registrations_total[1h]) * 3600",
                    "legendFormat": "Signups/hour"
                  }
                ]
              }
            ]
          }
        ]
      }
    }

    Distributed logging avec ELK Stack

    typescript
    // Structured logging pour microservices
    import winston from 'winston';
    import { ElasticsearchTransport } from 'winston-elasticsearch';
    
    class StructuredLogger {
      private logger: winston.Logger;
    
      constructor(serviceName: string) {
        const esTransport = new ElasticsearchTransport({
          level: 'info',
          clientOpts: {
            node: process.env.ELASTICSEARCH_URL || 'http://localhost:9200'
          },
          index: `microservices-${serviceName}-${new Date().getFullYear()}.${new Date().getMonth() + 1}`
        });
    
        this.logger = winston.createLogger({
          format: winston.format.combine(
            winston.format.timestamp(),
            winston.format.errors({ stack: true }),
            winston.format.json(),
            winston.format.printf(({ timestamp, level, message, service, traceId, ...meta }) => {
              return JSON.stringify({
                '@timestamp': timestamp,
                level,
                message,
                service: serviceName,
                traceId,
                ...meta
              });
            })
          ),
          defaultMeta: {
            service: serviceName,
            environment: process.env.NODE_ENV,
            version: process.env.APP_VERSION
          },
          transports: [
            new winston.transports.Console(),
            esTransport
          ]
        });
      }
    
      info(message: string, meta?: Record<string, any>): void {
        this.logger.info(message, this.enrichMeta(meta));
      }
    
      error(message: string, error?: Error, meta?: Record<string, any>): void {
        this.logger.error(message, this.enrichMeta({
          error: error ? {
            message: error.message,
            stack: error.stack,
            name: error.name
          } : undefined,
          ...meta
        }));
      }
    
      warn(message: string, meta?: Record<string, any>): void {
        this.logger.warn(message, this.enrichMeta(meta));
      }
    
      debug(message: string, meta?: Record<string, any>): void {
        this.logger.debug(message, this.enrichMeta(meta));
      }
    
      // Business events logging
      logBusinessEvent(event: BusinessEvent): void {
        this.logger.info('Business event', {
          eventType: 'business_event',
          eventName: event.name,
          entityId: event.entityId,
          entityType: event.entityType,
          userId: event.userId,
          data: event.data,
          timestamp: event.timestamp
        });
      }
    
      // Performance logging
      logPerformance(operation: string, duration: number, meta?: Record<string, any>): void {
        this.logger.info('Performance metric', {
          eventType: 'performance',
          operation,
          duration,
          ...this.enrichMeta(meta)
        });
      }
    
      private enrichMeta(meta?: Record<string, any>): Record<string, any> {
        const traceId = this.getCurrentTraceId();
        const userId = this.getCurrentUserId();
        
        return {
          traceId,
          userId,
          ...meta
        };
      }
    
      private getCurrentTraceId(): string | undefined {
        // Récupérer depuis OpenTelemetry context
        return trace.getActiveSpan()?.spanContext().traceId;
      }
    
      private getCurrentUserId(): string | undefined {
        // Récupérer depuis context utilisateur
        return (global as any).currentUserId;
      }
    }
    
    interface BusinessEvent {
      name: string;
      entityId: string;
      entityType: string;
      userId?: string;
      data: Record<string, any>;
      timestamp: string;
    }

    Conclusion et recommandations IT INNOVE

    Notre expertise en chiffres

    Après 5 ans d'accompagnement en architecture microservices :

    • 50+ migrations réussies du monolithe vers microservices
    • 95% de taux de satisfaction client
    • 40% d'amélioration moyenne des performances
    • 60% de réduction du time-to-market pour les nouvelles features

    Recommandations clés pour 2024

    1. Commencez petit, pensez grand

    Ne migrez pas tout d'un coup. Identifiez 1-2 domaines métier critiques et commencez par là.

    2. Investissez dans l'observabilité dès le début

    Monitoring, logging, tracing
    ces outils sont critiques pour débugger des systèmes distribués.

    3. Automatisez tout

    CI/CD, tests, déploiements, monitoring
    l'automatisation est la clé de la vélocité en microservices.

    4. Formez vos équipes

    La transformation microservices est autant organisationnelle que technique. Investissez dans la formation.

    Notre accompagnement sur-mesure

    Audit architecture (2 semaines)

    • Analyse de votre monolithe existant
    • Identification des bounded contexts
    • Stratégie de migration personnalisée
    • Estimation des coûts et délais

    POC technique (4-6 semaines)

    • Migration d'un premier service pilote
    • Setup de l'infrastructure (K8s, monitoring)
    • Formation de vos équipes
    • Validation des patterns techniques

    Accompagnement migration (3-12 mois)

    • Migration progressive service par service
    • Coaching technique continu
    • Support 24/7 pendant les phases critiques
    • Optimisations performance et coûts

    ---

    Prêt pour votre transformation microservices ?

    Nos architectes seniors vous accompagnent de l'audit initial jusqu'à la mise en production complète.

    Planifiez votre audit gratuit ou découvrez nos architectures de référence.

    *Prochain article
    "Sécurité applications web : OWASP Top 10 2024 et contre-mesures pratiques"*

    Questions fréquentes