🔑 Concepts Clés : Exchange, Queue, Binding

Introduction

RabbitMQ implémente le protocole AMQP 0-9-1 (Advanced Message Queuing Protocol), un standard ouvert qui définit un modèle de messaging sophistiqué et interopérable. Ce protocole va bien au-delà des simples queues point-à-point : il offre un modèle architectural complet pour construire des systèmes de messagerie complexes et flexibles.

La compréhension profonde des concepts d'Exchange, Queue et Binding est absolument cruciale car ils forment le trinity conceptuel sur lequel repose toute l'architecture RabbitMQ. Ces trois éléments travaillent ensemble pour créer un système de routage intelligent qui peut s'adapter à pratiquement tous les patterns de communication imaginables.

🧠 Fondements Théoriques AMQP

Philosophie de Conception

L'AMQP a été conçu avec une philosophie claire : séparer la logique de routage de la logique de stockage. Cette séparation fondamentale permet une flexibilité architecturale impossible avec des systèmes plus simples.

Principle de responsabilité unique :

  • Les Exchanges sont responsables du routage intelligent des messages
  • Les Queues sont responsables du stockage fiable et de la livraison ordonnée
  • Les Bindings définissent les règles de connexion entre ces deux mondes

Composabilité : Ces concepts peuvent se combiner de manière quasi-infinie pour créer des topologies de messaging complexes. Un message peut être routé vers plusieurs queues, transformé en chemin, dupliqué pour différents consommateurs, tout cela de manière déclarative.

Modèle Mental

Imaginez un système postal sophistiqué :

  • Les Exchanges sont comme des centres de tri intelligents qui examinent l'adresse et décident du routage
  • Les Queues sont comme des boîtes aux lettres qui stockent le courrier jusqu'à ce que le destinataire vienne le chercher
  • Les Bindings sont comme les règles postales qui définissent quel courrier va dans quelle boîte selon l'adresse

Cette analogie aide à comprendre pourquoi AMQP est si puissant : il sépare clairement le "comment router" du "où stocker", permettant des optimisations indépendantes de chaque aspect.

🏗️ Architecture AMQP

Rendu du diagramme en cours...

📬 Exchange (Point d'Entrée)

L'Exchange est le cœur intellectuel de RabbitMQ. C'est un routeur programmable qui examine chaque message entrant et décide de sa destination selon des algorithmes sophistiqués. Contrairement aux systèmes de queue simples où un message va directement dans une queue, AMQP introduit cette couche d'indirection qui démultiplie les possibilités architecturales.

Théorie du Routage Intelligent

Séparation des préoccupations : L'exchange libère les producteurs de la connaissance des destinations finales. Un producteur publie vers un exchange logique (user.events) et l'exchange décide si ce message doit aller vers la queue des emails, celle des SMS, celle des analytics, ou toutes à la fois.

Polymorphisme de routage : Chaque type d'exchange implémente un algorithme de routage différent, permettant d'adapter la stratégie de distribution aux besoins métier sans changer le code des producteurs.

Évolution sans rupture : Ajouter une nouvelle destination ne nécessite qu'un nouveau binding, pas de modification du code existant. Cette propriété est fondamentale pour les architectures évolutives.

L'Exchange reçoit les messages des publishers et les route vers les queues selon des règles de routing sophistiquées :

Types d'Exchanges

import pika

# Établir la connexion
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# 1. Direct Exchange
channel.exchange_declare(
    exchange='logs_direct',
    exchange_type='direct',
    durable=True
)

# 2. Topic Exchange
channel.exchange_declare(
    exchange='logs_topic',
    exchange_type='topic',
    durable=True
)

# 3. Fanout Exchange
channel.exchange_declare(
    exchange='news_fanout',
    exchange_type='fanout',
    durable=True
)

# 4. Headers Exchange
channel.exchange_declare(
    exchange='headers_ex',
    exchange_type='headers',
    durable=True
)

Exchange par Défaut

// Exchange par défaut (empty string)
// Route directement vers la queue nommée par routing_key
await channel.publish(
  '',              // Exchange par défaut
  'my_queue',      // Routing key = nom de queue
  Buffer.from('Direct to queue')
);

// Équivalent à :
await channel.sendToQueue('my_queue', Buffer.from('Direct to queue'));

Propriétés d'Exchange

# Déclaration complète d'exchange
channel.exchange_declare(
    exchange='my_exchange',
    exchange_type='topic',
    passive=False,        # Créer s'il n'existe pas
    durable=True,         # Survit au redémarrage
    auto_delete=False,    # Ne pas supprimer automatiquement
    internal=False,       # Accessible aux clients
    arguments={           # Arguments personnalisés
        'x-delayed-type': 'topic'  # Pour delayed message plugin
    }
)

📥 Queue (Stockage des Messages)

Les Queues représentent l'aspect "persistance" et "ordre" du système de messagerie. Elles sont bien plus que de simples buffers : ce sont des structures de données sophistiquées optimisées pour le stockage durable, la livraison fiable et le contrôle de flux.

Théorie des Queues Durables

Garanties ACID adaptées : Bien que RabbitMQ ne soit pas une base de données, les queues offrent des garanties similaires à ACID pour les messages :

  • Atomicité : Un message est soit totalement écrit, soit pas du tout
  • Cohérence : L'ordre FIFO est maintenu par queue
  • Isolation : Les opérations sur différentes queues sont isolées
  • Durabilité : Les messages persistants survivent aux pannes

Contrôle de flux avancé : Les queues implémentent des mécanismes sophistiqués de backpressure. Quand une queue devient pleine, elle peut bloquer les producteurs, activer des alternatives de routage, ou déclencher des alertes. Ce contrôle empêche l'effondrement en cascade du système.

Optimisations de performance : RabbitMQ optimise automatiquement le comportement des queues selon leur usage. Les queues vides sont gardées en RAM pour une livraison rapide, tandis que les queues avec beaucoup de messages migrent vers le disque pour économiser la mémoire.

Sémantiques de livraison : Chaque queue peut être configurée avec des sémantiques de livraison spécifiques (at-least-once, exactly-once simulé par idempotence) selon les besoins métier.

Les Queues stockent les messages en attente de consommation avec des garanties de fiabilité configurables :

Déclaration de Queue

class QueueManager:
    def __init__(self, channel):
        self.channel = channel
    
    def create_work_queue(self):
        """Queue pour traitement de tâches"""
        return self.channel.queue_declare(
            queue='work_queue',
            durable=True,      # Survit au redémarrage
            exclusive=False,   # Accessible par multiple connexions
            auto_delete=False, # Ne pas supprimer quand plus de consumers
            arguments={
                'x-max-length': 10000,           # Limite de messages
                'x-message-ttl': 3600000,        # TTL 1 heure
                'x-dead-letter-exchange': 'dlx', # DLX pour échecs
                'x-dead-letter-routing-key': 'failed'
            }
        )
    
    def create_temp_queue(self):
        """Queue temporaire pour RPC"""
        return self.channel.queue_declare(
            queue='',          # Nom auto-généré
            exclusive=True,    # Exclusive à cette connexion
            auto_delete=True   # Suppression automatique
        )
    
    def create_priority_queue(self):
        """Queue avec priorités"""
        return self.channel.queue_declare(
            queue='priority_queue',
            durable=True,
            arguments={
                'x-max-priority': 10  # Priorité 0-10
            }
        )

Types de Queues Avancées

// Lazy Queue - Stockage sur disque
await channel.assertQueue('lazy_queue', {
  durable: true,
  arguments: {
    'x-queue-mode': 'lazy'  // Messages stockés sur disque
  }
});

// Quorum Queue - Consensus distribué (RabbitMQ 3.8+)
await channel.assertQueue('quorum_queue', {
  durable: true,
  arguments: {
    'x-queue-type': 'quorum',
    'x-quorum-initial-group-size': 3
  }
});

// Stream Queue - Pour event streaming (RabbitMQ 3.9+)
await channel.assertQueue('stream_queue', {
  durable: true,
  arguments: {
    'x-queue-type': 'stream',
    'x-max-age': '7D',        // Retention 7 jours
    'x-stream-max-segment-size-bytes': 500000000
  }
});

🔗 Binding (Règles de Routage)

Les Bindings représentent le système nerveux de RabbitMQ : ils définissent comment l'information circule dans votre architecture. Un binding est bien plus qu'une simple connexion : c'est une règle intelligente qui encode la logique métier de routage directement dans l'infrastructure.

Théorie du Routage Déclaratif

Programmation déclarative : Plutôt que d'écrire du code impératif pour router les messages, vous déclarez des règles de routage via les bindings. Cette approche déclarative rend le routage visible, auditable et modifiable sans redéploiement.

Routing en tant que donnée : Les bindings transforment la logique de routage en données configurables. Cela permet de modifier le comportement du système en changeant la configuration plutôt qu'en modifiant le code, un principe fondamental de l'infrastructure as code.

Compositions de règles : Plusieurs bindings peuvent exister entre le même exchange et la même queue avec des routing keys différentes. Cette composition permet de créer des logiques de routage complexes (OR, AND implicite via headers exchange) de manière élégante.

Dynamicité : Les bindings peuvent être créés et détruits à l'exécution, permettant des topologies qui s'adaptent dynamiquement aux besoins. Par exemple, un consommateur temporaire peut créer ses propres bindings puis les nettoyer à sa fermeture.

Patterns de Binding Avancés

Hierarchical routing : Avec les topic exchanges, les bindings peuvent encoder des hiérarchies (user..created, order.payment.) permettant un routage basé sur la taxonomie métier.

Content-based routing : Les headers exchanges permettent un routage basé sur le contenu du message plutôt que sur sa destination, ouvrant la voie à des architectures orientées contenu.

Conditional routing : Les bindings avec arguments permettent un routage conditionnel, créant des règles métier complexes directement dans l'infrastructure.

Les Bindings lient les exchanges aux queues avec des règles de routage sophistiquées :

Bindings Simples

# Binding direct
channel.queue_bind(
    exchange='logs_direct',
    queue='error_queue',
    routing_key='error'
)

# Binding topic avec pattern
channel.queue_bind(
    exchange='logs_topic',
    queue='all_errors',
    routing_key='*.error'  # Wildcard
)

# Binding fanout (pas de routing key)
channel.queue_bind(
    exchange='news_fanout',
    queue='subscriber_queue'
    # Pas de routing key pour fanout
)

Bindings avec Arguments

# Headers exchange binding
channel.queue_bind(
    exchange='headers_ex',
    queue='pdf_processor',
    arguments={
        'x-match': 'all',  # Tous les headers doivent matcher
        'format': 'pdf',
        'type': 'document'
    }
)

# Binding conditionnel
channel.queue_bind(
    exchange='smart_router',
    queue='high_priority',
    routing_key='urgent',
    arguments={
        'priority': 'high',
        'region': 'eu-west'
    }
)

🔄 Lifecycle et Gestion

Messages et Propriétés

// Publication avec propriétés complètes
await channel.publish(
  'my_exchange',
  'routing.key',
  Buffer.from(JSON.stringify(messageData)),
  {
    // Basic Properties
    persistent: true,           // Message persistant
    priority: 5,               // Priorité (0-255)
    expiration: '60000',       // TTL 60 secondes
    messageId: uuid(),         // ID unique
    timestamp: Date.now(),     // Timestamp
    
    // Headers personnalisés
    headers: {
      'x-retry-count': 0,
      'source-service': 'api-gateway',
      'user-id': '123'
    },
    
    // Correlation pour RPC
    correlationId: 'corr-456',
    replyTo: 'response_queue',
    
    // Content type
    contentType: 'application/json',
    contentEncoding: 'utf-8'
  }
);

Gestion du Cycle de Vie

class MessageHandler:
    def __init__(self, channel):
        self.channel = channel
        
    def publish_with_confirms(self, exchange, routing_key, message):
        """Publication avec confirmation"""
        # Activer les confirmations
        self.channel.confirm_delivery()
        
        try:
            published = self.channel.basic_publish(
                exchange=exchange,
                routing_key=routing_key,
                body=message,
                properties=pika.BasicProperties(delivery_mode=2),
                mandatory=True  # Retourner si non routable
            )
            
            if published:
                print("✅ Message confirmé par le broker")
                return True
            else:
                print("❌ Message rejeté par le broker")
                return False
                
        except pika.exceptions.UnroutableError:
            print("❌ Message non routable")
            return False
    
    def consume_with_recovery(self, queue):
        """Consommation avec recovery"""
        def callback(ch, method, properties, body):
            try:
                # Traiter le message
                result = self.process_message(body)
                
                if result.success:
                    # Acknowledge
                    ch.basic_ack(delivery_tag=method.delivery_tag)
                else:
                    # Reject et requeue
                    ch.basic_nack(
                        delivery_tag=method.delivery_tag,
                        requeue=True
                    )
                    
            except CriticalError:
                # Reject sans requeue (DLX)
                ch.basic_nack(
                    delivery_tag=method.delivery_tag,
                    requeue=False
                )
            except TemporaryError:
                # Reject avec requeue
                ch.basic_nack(
                    delivery_tag=method.delivery_tag,
                    requeue=True
                )
        
        # QoS pour contrôler le débit
        self.channel.basic_qos(prefetch_count=10)
        
        self.channel.basic_consume(
            queue=queue,
            on_message_callback=callback
        )
        
        print(f"Consuming from {queue}...")
        self.channel.start_consuming()

🏢 Virtual Hosts

Isolation logique des ressources :

# Gestion des Virtual Hosts
class VirtualHostManager:
    def __init__(self):
        # Connexion admin
        self.params = pika.ConnectionParameters(
            host='localhost',
            credentials=pika.PlainCredentials('admin', 'password')
        )
    
    def setup_application_vhost(self, app_name):
        """Setup complet pour une application"""
        connection = pika.BlockingConnection(self.params)
        channel = connection.channel()
        
        try:
            # Déclarer exchanges pour l'app
            exchanges = [
                ('commands', 'direct'),
                ('events', 'topic'),
                ('notifications', 'fanout'),
                ('dlx', 'direct')
            ]
            
            for name, ex_type in exchanges:
                channel.exchange_declare(
                    exchange=f'{app_name}.{name}',
                    exchange_type=ex_type,
                    durable=True
                )
            
            # Queues standards
            queues = [
                f'{app_name}.tasks',
                f'{app_name}.emails', 
                f'{app_name}.dead_letters'
            ]
            
            for queue_name in queues:
                channel.queue_declare(
                    queue=queue_name,
                    durable=True
                )
            
            # Bindings par défaut
            channel.queue_bind(
                exchange=f'{app_name}.dlx',
                queue=f'{app_name}.dead_letters'
            )
            
            print(f"Virtual host setup completed for {app_name}")
            
        finally:
            connection.close()

# Usage
manager = VirtualHostManager()
manager.setup_application_vhost('ecommerce')
manager.setup_application_vhost('analytics')

🔍 Inspection et Debug

Introspection du Système

import pika
import json
from datetime import datetime

class RabbitInspector:
    def __init__(self, connection_params):
        self.params = connection_params
    
    def inspect_queue(self, queue_name):
        """Inspecter une queue sans consommer"""
        connection = pika.BlockingConnection(self.params)
        channel = connection.channel()
        
        try:
            # Informations sur la queue
            method = channel.queue_declare(
                queue=queue_name,
                passive=True  # Juste vérifier, ne pas créer
            )
            
            print(f"Queue: {queue_name}")
            print(f"Messages: {method.method.message_count}")
            print(f"Consumers: {method.method.consumer_count}")
            
            # Peek un message sans le consommer
            method_frame, header_frame, body = channel.basic_get(
                queue=queue_name,
                auto_ack=False
            )
            
            if method_frame:
                print(f"Next message: {body[:100]}...")
                # Reject pour remettre en queue
                channel.basic_nack(
                    delivery_tag=method_frame.delivery_tag,
                    requeue=True
                )
            else:
                print("Queue is empty")
                
        except pika.exceptions.ChannelClosedByBroker:
            print(f"Queue {queue_name} does not exist")
        finally:
            connection.close()
    
    def trace_message_flow(self, exchange, routing_key):
        """Tracer le chemin d'un message"""
        connection = pika.BlockingConnection(self.params)
        channel = connection.channel()
        
        # Message de test avec trace
        test_message = {
            'trace_id': f'trace-{datetime.now().timestamp()}',
            'data': 'test message for tracing'
        }
        
        # Activer le tracing (plugin nécessaire)
        channel.basic_publish(
            exchange=exchange,
            routing_key=routing_key,
            body=json.dumps(test_message),
            properties=pika.BasicProperties(
                headers={'x-trace': True}
            )
        )
        
        print(f"Test message sent to {exchange}/{routing_key}")
        connection.close()

💻 Exemples Pratiques Complets

Système de Notification E-Commerce

class ECommerceMessaging:
    def __init__(self):
        self.connection = pika.BlockingConnection(
            pika.ConnectionParameters('localhost')
        )
        self.channel = self.connection.channel()
        self.setup_infrastructure()
    
    def setup_infrastructure(self):
        """Setup complet de l'infrastructure messaging"""
        
        # === EXCHANGES ===
        # Commands - direct pour routing précis
        self.channel.exchange_declare(
            exchange='ecommerce.commands',
            exchange_type='direct',
            durable=True
        )
        
        # Events - topic pour pattern matching
        self.channel.exchange_declare(
            exchange='ecommerce.events',
            exchange_type='topic',
            durable=True
        )
        
        # Notifications - fanout pour broadcast
        self.channel.exchange_declare(
            exchange='ecommerce.notifications',
            exchange_type='fanout',
            durable=True
        )
        
        # Dead Letter Exchange
        self.channel.exchange_declare(
            exchange='ecommerce.dlx',
            exchange_type='direct',
            durable=True
        )
        
        # === QUEUES ===
        # Queue de traitement des commandes
        self.channel.queue_declare(
            queue='order.processing',
            durable=True,
            arguments={
                'x-dead-letter-exchange': 'ecommerce.dlx',
                'x-dead-letter-routing-key': 'order.failed',
                'x-message-ttl': 1800000  # 30 minutes
            }
        )
        
        # Queue de paiement
        self.channel.queue_declare(
            queue='payment.processing',
            durable=True,
            arguments={
                'x-max-priority': 10,  # Paiements prioritaires
                'x-dead-letter-exchange': 'ecommerce.dlx'
            }
        )
        
        # Queue d'inventaire
        self.channel.queue_declare(
            queue='inventory.updates',
            durable=True
        )
        
        # Queues de notification
        notification_queues = ['email', 'sms', 'push']
        for ntype in notification_queues:
            self.channel.queue_declare(
                queue=f'notifications.{ntype}',
                durable=True
            )
        
        # Dead Letter Queue
        self.channel.queue_declare(
            queue='dead.letters',
            durable=True
        )
        
        # === BINDINGS ===
        # Commands routing
        self.channel.queue_bind(
            exchange='ecommerce.commands',
            queue='order.processing',
            routing_key='order.process'
        )
        
        self.channel.queue_bind(
            exchange='ecommerce.commands',
            queue='payment.processing',
            routing_key='payment.process'
        )
        
        # Events routing avec patterns
        self.channel.queue_bind(
            exchange='ecommerce.events',
            queue='inventory.updates',
            routing_key='order.*.completed'  # Tous les completed
        )
        
        # Notifications broadcast
        for ntype in notification_queues:
            self.channel.queue_bind(
                exchange='ecommerce.notifications',
                queue=f'notifications.{ntype}'
            )
        
        # Dead Letter Queue
        self.channel.queue_bind(
            exchange='ecommerce.dlx',
            queue='dead.letters',
            routing_key='#'  # Tous les messages DLX
        )
        
        print("✅ E-commerce messaging infrastructure setup completed")

Usage du Système

class OrderProcessor:
    def __init__(self, messaging):
        self.messaging = messaging
        self.channel = messaging.channel
    
    def create_order(self, order_data):
        """Créer une commande et déclencher le workflow"""
        
        # 1. Envoyer commande de traitement
        self.channel.basic_publish(
            exchange='ecommerce.commands',
            routing_key='order.process',
            body=json.dumps(order_data),
            properties=pika.BasicProperties(
                delivery_mode=2,  # Persistant
                priority=order_data.get('priority', 0)
            )
        )
        
        # 2. Publier événement de création
        event = {
            'type': 'OrderCreated',
            'orderId': order_data['id'],
            'customerId': order_data['customer_id'],
            'timestamp': datetime.now().isoformat()
        }
        
        self.channel.basic_publish(
            exchange='ecommerce.events',
            routing_key='order.created.new',
            body=json.dumps(event)
        )
        
        print(f"Order {order_data['id']} processing initiated")
    
    def handle_payment_completed(self, payment_data):
        """Gérer paiement terminé"""
        
        # Publier événement
        event = {
            'type': 'PaymentCompleted',
            'orderId': payment_data['order_id'],
            'amount': payment_data['amount'],
            'timestamp': datetime.now().isoformat()
        }
        
        self.channel.basic_publish(
            exchange='ecommerce.events',
            routing_key='order.payment.completed',
            body=json.dumps(event)
        )
        
        # Notification broadcast
        notification = {
            'title': 'Paiement confirmé',
            'message': f'Votre paiement de {payment_data["amount"]}€ a été traité',
            'orderId': payment_data['order_id']
        }
        
        self.channel.basic_publish(
            exchange='ecommerce.notifications',
            routing_key='',  # Fanout ignore routing key
            body=json.dumps(notification)
        )

🔧 Patterns de Configuration

Configuration par Environnement

import os
from dataclasses import dataclass

@dataclass
class RabbitConfig:
    host: str = 'localhost'
    port: int = 5672
    virtual_host: str = '/'
    username: str = 'guest'
    password: str = 'guest'
    heartbeat: int = 600
    connection_attempts: int = 3
    retry_delay: float = 5.0

class ConfigurableRabbitMQ:
    def __init__(self, env='development'):
        self.config = self.load_config(env)
        self.connection = None
        self.channels = {}
    
    def load_config(self, env):
        """Charger config selon environnement"""
        configs = {
            'development': RabbitConfig(),
            'staging': RabbitConfig(
                host=os.getenv('RABBITMQ_HOST', 'staging-rabbit'),
                username=os.getenv('RABBITMQ_USER', 'staging'),
                password=os.getenv('RABBITMQ_PASS', 'staging123')
            ),
            'production': RabbitConfig(
                host=os.getenv('RABBITMQ_HOST'),
                port=int(os.getenv('RABBITMQ_PORT', 5672)),
                virtual_host=os.getenv('RABBITMQ_VHOST', '/prod'),
                username=os.getenv('RABBITMQ_USER'),
                password=os.getenv('RABBITMQ_PASS'),
                heartbeat=int(os.getenv('RABBITMQ_HEARTBEAT', 300))
            )
        }
        return configs.get(env, configs['development'])
    
    def connect(self):
        """Établir connexion avec retry"""
        params = pika.ConnectionParameters(
            host=self.config.host,
            port=self.config.port,
            virtual_host=self.config.virtual_host,
            credentials=pika.PlainCredentials(
                self.config.username,
                self.config.password
            ),
            heartbeat=self.config.heartbeat,
            connection_attempts=self.config.connection_attempts,
            retry_delay=self.config.retry_delay
        )
        
        self.connection = pika.BlockingConnection(params)
        return self.connection
    
    def get_channel(self, name='default'):
        """Obtenir un channel nommé"""
        if not self.connection:
            self.connect()
        
        if name not in self.channels:
            self.channels[name] = self.connection.channel()
        
        return self.channels[name]

📊 Monitoring des Concepts

// Monitoring en temps réel
class ConceptMonitor {
  async monitorSystem() {
    const mgmt = new ManagementAPI('localhost', 15672, 'admin', 'password');
    
    // Surveiller les exchanges
    const exchanges = await mgmt.getExchanges();
    exchanges.forEach(ex => {
      console.log(`Exchange: ${ex.name}`);
      console.log(`  Type: ${ex.type}`);
      console.log(`  Message rate in: ${ex.message_stats?.publish_in || 0}/s`);
      console.log(`  Message rate out: ${ex.message_stats?.publish_out || 0}/s`);
    });
    
    // Surveiller les queues
    const queues = await mgmt.getQueues();
    queues.forEach(q => {
      console.log(`Queue: ${q.name}`);
      console.log(`  Messages: ${q.messages}`);
      console.log(`  Consumers: ${q.consumers}`);
      console.log(`  Memory: ${q.memory} bytes`);
      
      if (q.messages > 10000) {
        console.warn(`⚠️  Queue ${q.name} has high message count!`);
      }
    });
    
    // Surveiller les bindings
    const bindings = await mgmt.getBindings();
    console.log(`Total bindings: ${bindings.length}`);
  }
}

✅ Bonnes Pratiques

  1. Naming Convention
# Convention de nommage recommandée
exchanges = {
    'commands': 'app.commands',      # Direct
    'events': 'app.events',          # Topic
    'broadcast': 'app.broadcast'     # Fanout
}

queues = {
    'tasks': 'app.tasks.high',
    'emails': 'app.notifications.email',
    'dead': 'app.dead.letters'
}
  1. Resource Management
// Toujours nettoyer les ressources
class ResourceManager {
  constructor() {
    this.connections = [];
  }
  
  async createConnection() {
    const conn = await amqp.connect(config);
    this.connections.push(conn);
    return conn;
  }
  
  async cleanup() {
    for (const conn of this.connections) {
      await conn.close();
    }
    this.connections = [];
  }
}

// Graceful shutdown
process.on('SIGINT', async () => {
  await resourceManager.cleanup();
  process.exit(0);
});

🎯 Exercice Pratique

Créez une infrastructure complète pour un système de blog :

# TODO: Implémenter
# 1. Exchanges pour commands, events, notifications
# 2. Queues pour articles, commentaires, moderation
# 3. Bindings pour router selon le type de contenu
# 4. Dead Letter Exchange pour gestion d'erreurs
# 5. Script de monitoring des métriques

Maîtriser ces concepts fondamentaux est crucial avant d'aborder les patterns avancés et l'optimisation.

📝 Testez vos connaissances !

Répondez à 10 questions pour valider ce cours