MFormations
Modern Design Patterns

Chapitre 17

17. Integration Architecture Patterns

17. Integration Architecture Patterns

Integration Architecture Patterns - Cours Détaillé

Introduction

L'architecture d'intégration traite de la façon dont les différents services et systèmes communiquent entre eux. Dans un environnement distribué, les problèmes de couplage, de résilience, de cohérence des données, et de migration deviennent centraux. Ce chapitre présente les patterns éprouvés pour résoudre ces défis.

1. API Gateway

Définition

Un API Gateway est un point d'entrée unique qui agit comme proxy inverse, acceptant les appels API, les routant vers les services appropriés, et agrégeant les réponses.

Responsabilités

  • Routage des requêtes
  • Agrégation de réponses
  • Authentification / Autorisation
  • Rate limiting
  • Load balancing
  • Transformation de protocole
  • Cache
  • Logging / Monitoring

Implémentation

// Express Gateway simplifié
class ApiGateway {
  private routes: Map<string, RouteConfig> = new Map();
  private rateLimiter: RateLimiter;
  private authService: AuthService;

  constructor() {
    this.rateLimiter = new RateLimiter({ windowMs: 60000, max: 100 });
  }

  registerRoute(path: string, config: RouteConfig): void {
    this.routes.set(path, config);
  }

  async handleRequest(req: Request, res: Response): Promise<void> {
    try {
      // 1. Rate limiting
      await this.rateLimiter.check(req.ip);

      // 2. Authentication
      const user = await this.authService.authenticate(req);
      if (!user) {
        res.status(401).json({ error: 'Unauthorized' });
        return;
      }

      // 3. Route matching
      const route = this.matchRoute(req.path);
      if (!route) {
        res.status(404).json({ error: 'Not found' });
        return;
      }

      // 4. Authorization
      if (!this.checkPermissions(user, route)) {
        res.status(403).json({ error: 'Forbidden' });
        return;
      }

      // 5. Route to service
      const response = await this.routeToService(route, req);

      // 6. Response transformation
      const transformed = this.transformResponse(response, route);

      res.json(transformed);
    } catch (error) {
      this.handleError(error, res);
    }
  }

  private async routeToService(route: RouteConfig, req: Request): Promise<any> {
    const startTime = Date.now();
    try {
      const response = await axios({
        method: req.method,
        url: `${route.serviceUrl}${req.path}`,
        data: req.body,
        headers: this.filterHeaders(req.headers),
        timeout: route.timeout || 5000,
      });
      this.recordMetrics(route, Date.now() - startTime, 200);
      return response.data;
    } catch (error) {
      this.recordMetrics(route, Date.now() - startTime, error.response?.status || 500);
      throw error;
    }
  }
}

API Gateway vs BFF

CritèreAPI GatewayBFF
Point d'entréeUniqueUn par client
AudienceTous les clientsClient spécifique
ComplexitéÉlevéeFaible
ÉquipePlateformeFeature team
CouplageCentraliséDistribué

2. Backend for Frontend (BFF)

Définition

Le pattern BFF consiste à créer un backend dédié pour chaque type de client (mobile, web, IoT), optimisant les APIs pour les besoins spécifiques de chaque client.

Pourquoi BFF ?

// BFF Mobile : API légère pour bande passante limitée
class MobileBFF {
  async getProductFeed(userId: string): Promise<MobileProductFeed> {
    const [products, preferences, location] = await Promise.all([
      productService.getProducts(),
      userService.getPreferences(userId),
      locationService.getCurrentLocation(userId),
    ]);

    // Agrégation spécifique mobile
    return {
      items: products
        .filter(p => this.isRelevant(p, preferences))
        .slice(0, 20) // Pagination mobile
        .map(p => ({
          id: p.id,
          name: p.name,
          price: p.price,
          thumbnail: p.images[0], // Une seule image
          inStock: p.stock > 0,
        })),
      totalCount: 20,
      nextCursor: this.encodeCursor(products[19]?.id),
    };
  }
}

// BFF Web : API plus riche pour desktop
class WebBFF {
  async getProductFeed(userId: string, page: number, pageSize: number): Promise<WebProductFeed> {
    const [products, categories] = await Promise.all([
      productService.getProductsPaginated(page, pageSize),
      categoryService.getAllCategories(),
    ]);

    // Agrégation riche pour le web
    return {
      items: products.items.map(p => ({
        id: p.id,
        name: p.name,
        description: p.description,
        price: p.price,
        originalPrice: p.originalPrice,
        discount: p.discount,
        images: p.images,
        variants: p.variants,
        specs: p.specifications,
        rating: p.averageRating,
        reviewCount: p.reviewCount,
        inStock: p.stock > 0,
        category: categories.find(c => c.id === p.categoryId),
        relatedProducts: p.relatedIds,
      })),
      pagination: {
        page,
        pageSize,
        totalItems: products.total,
        totalPages: Math.ceil(products.total / pageSize),
      },
    };
  }
}

Cas d'usage

  • Mobile (bande passante limitée, écran small)
  • Web (richesse, desktop)
  • IoT (protocoles légers)
  • Third-party (API publique dédiée)

3. Circuit Breaker

Définition

Le Circuit Breaker est un pattern de résilience qui détecte les défaillances et empêche les appels répétés à un service défaillant, donnant le temps au service de récupérer.

États

[CLOSED] → (failures > threshold) → [OPEN]
[OPEN] → (timeout elapsed) → [HALF_OPEN]
[HALF_OPEN] → (success) → [CLOSED]
[HALF_OPEN] → (failure) → [OPEN]

Implémentation (TypeScript)

type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';

class CircuitBreaker {
  private state: CircuitState = 'CLOSED';
  private failureCount = 0;
  private successCount = 0;
  private lastFailureTime: number = 0;
  private readonly failureThreshold: number;
  private readonly successThreshold: number;
  private readonly timeout: number;

  constructor(
    private readonly serviceName: string,
    options?: Partial<CircuitBreakerOptions>
  ) {
    this.failureThreshold = options?.failureThreshold ?? 5;
    this.successThreshold = options?.successThreshold ?? 3;
    this.timeout = options?.timeout ?? 30000;
  }

  async call<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime >= this.timeout) {
        this.state = 'HALF_OPEN';
      } else {
        throw new CircuitBreakerOpenError(this.serviceName);
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess(): void {
    this.failureCount = 0;
    if (this.state === 'HALF_OPEN') {
      this.successCount++;
      if (this.successCount >= this.successThreshold) {
        this.state = 'CLOSED';
        this.successCount = 0;
      }
    }
  }

  private onFailure(): void {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
      console.warn(`Circuit breaker OPEN for ${this.serviceName}`);
    }
  }

  getState(): CircuitState { return this.state; }
}

// Resilience4j-like decorator
function circuitBreaker(options?: Partial<CircuitBreakerOptions>) {
  const breakers = new Map<string, CircuitBreaker>();

  return function <T>(
    target: any,
    propertyKey: string,
    descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise<T>>
  ) {
    const originalMethod = descriptor.value!;
    const serviceName = `${target.constructor.name}.${propertyKey}`;

    if (!breakers.has(serviceName)) {
      breakers.set(serviceName, new CircuitBreaker(serviceName, options));
    }

    descriptor.value = async function (...args: any[]): Promise<T> {
      const breaker = breakers.get(serviceName)!;
      return breaker.call(() => originalMethod.apply(this, args));
    };
  };
}

Métriques et monitoring

interface CircuitBreakerMetrics {
  state: CircuitState;
  failureCount: number;
  successCount: number;
  failureRate: number;
  lastFailure: Date | null;
  uptime: number;
}

class MonitoredCircuitBreaker extends CircuitBreaker {
  private metrics: CircuitBreakerMetrics = {
    state: 'CLOSED',
    failureCount: 0,
    successCount: 0,
    failureRate: 0,
    lastFailure: null,
    uptime: 0,
  };

  getMetrics(): CircuitBreakerMetrics {
    return { ...this.metrics };
  }
}

4. Saga Pattern

Définition

Le pattern Saga gère les transactions distribuées en les décomposant en une séquence de transactions locales, avec des actions de compensation en cas d'échec.

Choreography Saga

// Choreography : chaque service publie des événements et réagit aux événements
interface OrderEvent {
  type: 'OrderCreated' | 'OrderConfirmed' | 'OrderCancelled';
  orderId: string;
  data: any;
}

// Order Service
class OrderService {
  async createOrder(order: OrderInput): Promise<void> {
    const orderId = uuid();
    await this.orderRepo.save({ id: orderId, ...order, status: 'PENDING' });
    await this.eventBus.publish({
      type: 'OrderCreated',
      orderId,
      data: { items: order.items, total: order.total },
    });
  }

  @Subscribe('PaymentProcessed')
  async onPaymentProcessed(event: OrderEvent): Promise<void> {
    await this.orderRepo.update(event.orderId, { status: 'CONFIRMED' });
    await this.eventBus.publish({
      type: 'OrderConfirmed',
      orderId: event.orderId,
      data: {},
    });
  }

  @Subscribe('PaymentFailed')
  async onPaymentFailed(event: OrderEvent): Promise<void> {
    await this.orderRepo.update(event.orderId, { status: 'CANCELLED' });
    // Compensation : annuler la réservation de stock
  }
}

// Inventory Service
class InventoryService {
  @Subscribe('OrderCreated')
  async onOrderCreated(event: OrderEvent): Promise<void> {
    try {
      await this.reserveStock(event.data.items);
      await this.eventBus.publish({
        type: 'StockReserved',
        orderId: event.orderId,
        data: {},
      });
    } catch (error) {
      await this.eventBus.publish({
        type: 'StockReservationFailed',
        orderId: event.orderId,
        data: { reason: error.message },
      });
    }
  }

  @Subscribe('OrderCancelled')
  async onOrderCancelled(event: OrderEvent): Promise<void> {
    await this.releaseStock(event.data.items); // Compensation
  }
}

Orchestration Saga

// Orchestrator : un coordinateur central gère la saga
class OrderSagaOrchestrator {
  async executeSaga(orderInput: OrderInput): Promise<void> {
    const sagaId = uuid();
    const context = new SagaContext(sagaId);

    try {
      // Étape 1 : Créer la commande
      const order = await this.orderService.createOrder(orderInput);
      context.set('orderId', order.id);

      // Étape 2 : Réserver le stock
      const stock = await this.inventoryService.reserveStock(orderInput.items);
      context.set('reservationId', stock.reservationId);

      // Étape 3 : Traiter le paiement
      const payment = await this.paymentService.processPayment(orderInput.total);
      context.set('paymentId', payment.id);

      // Étape 4 : Confirmer la commande
      await this.orderService.confirmOrder(order.id);

      // Étape 5 : Envoyer la notification
      await this.notificationService.sendOrderConfirmation(order.id);

    } catch (error) {
      await this.compensate(context, error);
    }
  }

  private async compensate(context: SagaContext, error: Error): Promise<void> {
    console.error(`Saga ${context.sagaId} failed:`, error);

    // Compensation inversée
    if (context.has('paymentId')) {
      await this.paymentService.refund(context.get('paymentId'));
    }
    if (context.has('reservationId')) {
      await this.inventoryService.releaseStock(context.get('reservationId'));
    }
    if (context.has('orderId')) {
      await this.orderService.cancelOrder(context.get('orderId'));
    }
  }
}

Choreography vs Orchestration

CritèreChoreographyOrchestration
CouplageFaible (événements)Plus fort (orchestrator)
VisibilitéDiffuseCentralisée
ComplexitéÉlevée (debug)Moyenne
ScalabilitéExcellenteLimitée (orchestrator)
TransitionsComplexesSimples
ResponsabilitéChaque serviceOrchestrator

5. Strangler Fig Pattern

Définition

Le pattern Strangler Fig permet de migrer progressivement un système monolithique vers des microservices en remplaçant des fonctionnalités une par une.

Stratégie

class StranglerProxy {
  private legacyBaseUrl: string;
  private newServiceRoutes: Map<string, string>;

  constructor(legacyBaseUrl: string) {
    this.legacyBaseUrl = legacyBaseUrl;
    this.newServiceRoutes = new Map();
  }

  migrateRoute(path: string, newServiceUrl: string): void {
    this.newServiceRoutes.set(path, newServiceUrl);
  }

  async handleRequest(req: Request): Promise<Response> {
    const newServiceUrl = this.newServiceRoutes.get(req.path);

    if (newServiceUrl) {
      // Route vers le nouveau service
      try {
        const response = await axios.post(newServiceUrl, req.body);
        return response;
      } catch (error) {
        // Fallback vers le legacy si le nouveau service échoue
        console.warn(`Falling back to legacy for ${req.path}`);
        return this.forwardToLegacy(req);
      }
    }

    // Route vers le legacy par défaut
    return this.forwardToLegacy(req);
  }

  private async forwardToLegacy(req: Request): Promise<Response> {
    return axios({
      method: req.method,
      url: `${this.legacyBaseUrl}${req.path}`,
      data: req.body,
    });
  }
}

Phases de migration

// Phase 1 : Proxy initial
const proxy = new StranglerProxy('https://legacy-app.com/api');

// Phase 2 : Migrer une route
proxy.migrateRoute('/api/users', 'https://users-service.com/api');

// Phase 3 : Migrer plus de routes
proxy.migrateRoute('/api/products', 'https://products-service.com/api');
proxy.migrateRoute('/api/orders', 'https://orders-service.com/api');

// Phase 4 : Supprimer le proxy quand la migration est terminée

6. Sidecar Pattern

Définition

Le Sidecar est un conteneur auxiliaire attaché à l'application principale, partageant le même cycle de vie et les mêmes ressources.

Cas d'utilisation

  • Proxy / Load balancing (Envoy, Linkerd)
  • Logging collecteur (Fluentd)
  • Monitoring (Prometheus exporter)
  • Configuration reload
  • TLS termination
  • Authentication proxy

Configuration Docker Compose

version: '3.8'
services:
  app:
    image: my-app:latest
    ports:
      - "3000:3000"
    depends_on:
      - sidecar

  sidecar:
    image: envoyproxy/envoy:v1.28
    volumes:
      - ./envoy.yaml:/etc/envoy/envoy.yaml
    ports:
      - "9901:9901" # Admin
    network_mode: "service:app"

Kubernetes Sidecar

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-with-sidecar
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: main-app
        image: my-app:latest
        ports:
        - containerPort: 3000
      - name: sidecar-proxy
        image: envoyproxy/envoy:v1.28
        ports:
        - containerPort: 9901
        volumeMounts:
        - name: envoy-config
          mountPath: /etc/envoy
      volumes:
      - name: envoy-config
        configMap:
          name: envoy-config

7. Ambassador Pattern

Définition

L'Ambassador est un proxy qui se place entre le client et les services externes pour gérer la connectivité, la résilience, et la sécurité.

Différence avec Sidecar

  • Sidecar : Compagnon local, même pod/host
  • Ambassador : Proxy dédié, souvent séparé
class Ambassador {
  private circuitBreaker: CircuitBreaker;
  private retryPolicy: RetryPolicy;
  private cache: Cache;

  constructor(private serviceUrl: string) {
    this.circuitBreaker = new CircuitBreaker(serviceUrl);
    this.retryPolicy = new RetryPolicy({ maxRetries: 3, backoff: 'exponential' });
    this.cache = new Cache({ ttl: 300000 }); // 5 min
  }

  async call<T>(request: AmbassadorRequest): Promise<T> {
    const cacheKey = this.buildCacheKey(request);
    const cached = this.cache.get(cacheKey);
    if (cached) return cached as T;

    return this.circuitBreaker.call(async () => {
      return this.retryPolicy.execute(async () => {
        const response = await axios({
          method: request.method,
          url: `${this.serviceUrl}${request.path}`,
          data: request.body,
          headers: {
            'X-Request-Id': uuid(),
            'X-Ambassador-Version': '1.0',
            ...request.headers,
          },
          timeout: request.timeout || 5000,
        });

        if (request.cacheable) {
          this.cache.set(cacheKey, response.data);
        }

        return response.data;
      });
    });
  }
}

8. Anti-Corruption Layer (ACL)

Définition

L'Anti-Corruption Layer est une couche de traduction entre deux systèmes qui empêche la propagation des modèles de données et des concepts d'un système à l'autre.

// Legacy System Model
interface LegacyCustomer {
  CUST_ID: string;
  CUST_NAME: string;
  CUST_EMAIL: string;
  CUST_ADDR1: string;
  CUST_ADDR2: string;
  CUST_CITY: string;
  CUST_ZIP: string;
  CUST_CREDIT_LIMIT: number;
}

// New System Model
interface Customer {
  id: string;
  name: string;
  email: string;
  address: Address;
  creditLimit: CreditLimit;
}

interface Address {
  street: string;
  complement?: string;
  city: string;
  zipCode: string;
  country: string;
}

// Anti-Corruption Layer
class LegacyCustomerTranslator {
  // Mapping : Legacy → New
  toNew(legacy: LegacyCustomer): Customer {
    return {
      id: legacy.CUST_ID,
      name: legacy.CUST_NAME.trim(),
      email: legacy.CUST_EMAIL.toLowerCase(),
      address: {
        street: [legacy.CUST_ADDR1, legacy.CUST_ADDR2]
          .filter(Boolean)
          .join(', '),
        city: legacy.CUST_CITY,
        zipCode: legacy.CUST_ZIP,
        country: 'FR',
      },
      creditLimit: {
        amount: legacy.CUST_CREDIT_LIMIT,
        currency: 'EUR',
      },
    };
  }

  // Mapping : New → Legacy
  toLegacy(customer: Customer): LegacyCustomer {
    const [addr1, addr2 = ''] = customer.address.street.split(', ');
    return {
      CUST_ID: customer.id,
      CUST_NAME: customer.name.toUpperCase(),
      CUST_EMAIL: customer.email,
      CUST_ADDR1: addr1,
      CUST_ADDR2: addr2,
      CUST_CITY: customer.address.city,
      CUST_ZIP: customer.address.zipCode,
      CUST_CREDIT_LIMIT: customer.creditLimit.amount,
    };
  }
}

// ACL Facade
class LegacyCustomerService {
  private translator = new LegacyCustomerTranslator();
  private legacyApi = new LegacySoapClient();

  async getCustomer(id: string): Promise<Customer> {
    const legacyCustomer = await this.legacyApi.getCustomer(id);
    return this.translator.toNew(legacyCustomer);
  }

  async saveCustomer(customer: Customer): Promise<void> {
    const legacyCustomer = this.translator.toLegacy(customer);
    await this.legacyApi.saveCustomer(legacyCustomer);
  }
}

9. Domain Event

Définition

Un Domain Event capture quelque chose qui s'est passé dans le domaine, permettant aux autres services de réagir de manière découplée.

// Base Domain Event
interface DomainEvent {
  eventId: string;
  aggregateId: string;
  eventType: string;
  occurredOn: Date;
  version: number;
}

// Événements spécifiques
class OrderPlacedEvent implements DomainEvent {
  readonly eventId: string = uuid();
  readonly occurredOn: Date = new Date();
  readonly version: number = 1;

  constructor(
    readonly aggregateId: string,
    readonly customerId: string,
    readonly items: OrderItem[],
    readonly total: Money
  ) {}

  readonly eventType = 'order.placed';
}

class PaymentReceivedEvent implements DomainEvent {
  readonly eventId: string = uuid();
  readonly occurredOn: Date = new Date();
  readonly version: number = 1;

  constructor(
    readonly aggregateId: string,
    readonly paymentId: string,
    readonly amount: Money,
    readonly method: PaymentMethod
  ) {}

  readonly eventType = 'payment.received';
}

// Event Bus
interface EventBus {
  publish(event: DomainEvent): Promise<void>;
  subscribe(eventType: string, handler: EventHandler): void;
}

// Event Store (pour traçabilité)
class EventStore {
  private events: DomainEvent[] = [];

  async append(event: DomainEvent): Promise<void> {
    this.events.push(event);
    await this.persist(event);
  }

  async getEvents(aggregateId: string): Promise<DomainEvent[]> {
    return this.events.filter(e => e.aggregateId === aggregateId);
  }

  async replay(aggregateId: string): Promise<void> {
    const events = await this.getEvents(aggregateId);
    for (const event of events) {
      await this.notifyHandlers(event);
    }
  }
}

10. Outbox Pattern

Définition

L'Outbox Pattern garantit la livraison fiable des événements en stockant d'abord les événements dans une table de sortie (outbox) avec la transaction métier, puis en les publiant de manière asynchrone.

Problème résolu

[Service A] → DB (transaction OK) → Message Broker → [Service B]
                ↓
          Crash avant envoi ! → Message perdu

Solution Outbox

// Outbox Repository
interface OutboxMessage {
  id: string;
  aggregateType: string;
  aggregateId: string;
  eventType: string;
  payload: string; // JSON serialized event
  status: 'PENDING' | 'PUBLISHED' | 'FAILED';
  createdAt: Date;
  publishedAt?: Date;
  retryCount: number;
}

// Transactional Outbox
class OutboxService {
  constructor(
    private db: Database,
    private eventBus: EventBus
  ) {}

  async saveAndPublish<T extends DomainEvent>(
    aggregateId: string,
    event: T,
    saveAction: () => Promise<void>
  ): Promise<void> {
    const outboxMessage: OutboxMessage = {
      id: uuid(),
      aggregateType: event.constructor.name,
      aggregateId,
      eventType: event.eventType,
      payload: JSON.stringify(event),
      status: 'PENDING',
      createdAt: new Date(),
      retryCount: 0,
    };

    // Même transaction métier + outbox
    await this.db.transaction(async (tx) => {
      await saveAction();          // 1. Business logic
      await tx.save(outboxMessage); // 2. Outbox message
    });

    // Publication asynchrone
    await this.publishMessage(outboxMessage);
  }

  private async publishMessage(message: OutboxMessage): Promise<void> {
    try {
      const event = JSON.parse(message.payload);
      await this.eventBus.publish(event);
      await this.db.update(message.id, {
        status: 'PUBLISHED',
        publishedAt: new Date(),
      });
    } catch (error) {
      await this.db.update(message.id, {
        status: 'FAILED',
        retryCount: message.retryCount + 1,
      });
    }
  }

  // Relance des messages échoués
  async retryFailedMessages(): Promise<void> {
    const failedMessages = await this.db.find({
      status: 'FAILED',
      retryCount: { $lt: 5 },
      createdAt: { $gt: new Date(Date.now() - 24 * 60 * 60 * 1000) },
    });

    for (const message of failedMessages) {
      await this.publishMessage(message);
    }
  }
}

// Usage
class OrderService {
  constructor(private outbox: OutboxService) {}

  async placeOrder(orderInput: OrderInput): Promise<Order> {
    const order = new Order(orderInput);
    const event = new OrderPlacedEvent(order.id, orderInput.customerId, orderInput.items, orderInput.total);

    await this.outbox.saveAndPublish(
      order.id,
      event,
      async () => {
        await this.orderRepository.save(order);
        await this.inventoryRepository.reserve(orderInput.items);
      }
    );

    return order;
  }
}

Polling Publisher

class OutboxPublisher {
  private running = false;

  constructor(
    private db: Database,
    private eventBus: EventBus,
    private pollInterval: number = 1000 // 1 seconde
  ) {}

  async start(): Promise<void> {
    this.running = true;
    while (this.running) {
      const messages = await this.db.find({
        status: 'PENDING',
        createdAt: { $lt: new Date() },
      });

      for (const message of messages) {
        try {
          const event = JSON.parse(message.payload);
          await this.eventBus.publish(event);
          await this.db.update(message.id, { status: 'PUBLISHED', publishedAt: new Date() });
        } catch (error) {
          console.error(`Failed to publish message ${message.id}:`, error);
          await this.db.update(message.id, { status: 'FAILED', retryCount: message.retryCount + 1 });
        }
      }

      await sleep(this.pollInterval);
    }
  }

  async stop(): Promise<void> {
    this.running = false;
  }
}

11. Résumé et comparaison

PatternProblème résoluForceFaiblesse
API GatewayPoint d'entrée uniqueCentralisationSPOF, complexité
BFFAPI adaptée au clientPerformance, découplageDuplication
Circuit BreakerRésilienceProtection en cascadeComplexité ajoutée
SagaTransactions distribuéesCohérence sans 2PCCompensation complexe
Strangler FigMigration progressiveFaible risqueLongue durée
SidecarFonctionnalités auxiliairesDécouplage, polyglotteConsommation ressources
AmbassadorConnectivité externeRésilience, sécuritéPoint de défaillance
ACLProtection modèleIsolationMaintenance traduction
Domain EventDécouplage temporelScalabilitéDebug complexe
OutboxLivraison fiableGarantie "at least once"Stockage supplémentaire

12. Conclusion

Les patterns d'intégration sont essentiels dans les architectures modernes. Le choix du bon pattern dépend du contexte, des contraintes, et de l'architecture existante. L'important est de comprendre chaque pattern, ses forces et ses faiblesses, pour faire le bon compromis.