💻 Développement

dev-event-driven-architect

Conception d'architectures événementielles avec messaging et event sourcing.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-event-driven-architect --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-event-driven-architect -Launch"

🚀 Déjà installé ?

claude "/dev-event-driven-architect"

Ou tapez /dev-event-driven-architect dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

event drivenévénementielRabbitMQKafkaevent sourcingCQRSmessage brokerpub/subasync messaging

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-event-driven-architect ~/.claude/skills/

Payload du plugin : skills/dev-event-driven-architect · source éditable : dev-skills/event-driven-architect

📖 Manuel

Event-Driven Architect

Workflow

1. Identifier les événements métier

Modéliser les faits immuables nommés au passé (verbe passé + sujet) :

OrderPlaced / PaymentProcessed / UserRegistered / ShipmentDispatched

Distinguer deux types :

Pratiquer l'Event Storming pour découvrir les événements avec les parties métier avant d'écrire la moindre ligne de code.


2. Choisir le broker

CritèreRabbitMQKafkaAzure Service BusRedis Streams
DébitMoyen (50 k msg/s)Très élevé (M msg/s)MoyenFaible-moyen
RétentionNon (ack = supprimé)Oui (configurable)14 jours maxConfigurable
Ordering strictPar queuePar partitionPar sessionPar stream
RejeuNon natifOuiNon natifOui
Complexité opé.FaibleÉlevéeFaible (SaaS)Très faible

Règle de sélection :


3. Topologie topics / queues

RabbitMQ — exchange fanout + routing :

Exchange: order.events (type: topic)
  → order.placed        → queue: billing.order-placed
  → order.placed        → queue: inventory.order-placed
  → order.cancelled     → queue: billing.order-cancelled

Kafka — topics + partitions :

Topic: order-events          (partitions: 12, replication: 3)
Topic: payment-events        (partitions: 6,  replication: 3)
Consumer group: billing-svc  → offset par partition géré par Kafka

Règle : 1 consumer group = 1 responsabilité. Ne pas partager un group entre services différents.


4. Implémenter le pattern

Pub/Sub (découplage simple)

// Producteur (.NET + MassTransit + RabbitMQ)
await _publishEndpoint.Publish(new OrderPlaced
{
    OrderId   = order.Id,
    CustomerId = order.CustomerId,
    OccurredAt = DateTimeOffset.UtcNow
});

// Consommateur
public class OrderPlacedConsumer : IConsumer<OrderPlaced>
{
    public async Task Consume(ConsumeContext<OrderPlaced> context)
    {
        // logique idempotente ici
    }
}

Outbox Pattern (cohérence sans saga)

-- Table outbox dans la même base que l'entité
CREATE TABLE outbox_messages (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    type        VARCHAR(200) NOT NULL,
    payload     JSONB        NOT NULL,
    created_at  TIMESTAMPTZ  NOT NULL DEFAULT now(),
    processed_at TIMESTAMPTZ
);
// Dans la même transaction que la mutation métier
await db.OutboxMessages.AddAsync(new OutboxMessage
{
    Type    = nameof(OrderPlaced),
    Payload = JsonSerializer.Serialize(evt)
});
await db.SaveChangesAsync(); // atomique avec l'entité

// Worker séparé publie les messages non traités toutes les N secondes

Saga (orchestration de compensation)

// MassTransit StateMachine
public class OrderSaga : MassTransitStateMachine<OrderSagaState>
{
    public State PaymentPending { get; private set; }
    public State Completed      { get; private set; }
    public State Compensating   { get; private set; }

    public OrderSaga()
    {
        Initially(
            When(OrderPlaced)
                .TransitionTo(PaymentPending)
                .Publish(ctx => new RequestPayment { OrderId = ctx.Saga.CorrelationId }));

        During(PaymentPending,
            When(PaymentFailed)
                .TransitionTo(Compensating)
                .Publish(ctx => new CancelOrder { OrderId = ctx.Saga.CorrelationId }));
    }
}

CQRS + Event Sourcing (audit total)

// Aggregate reconstruit depuis les events
public class Order
{
    private readonly List<IDomainEvent> _events = new();

    public static Order Reconstitute(IEnumerable<IDomainEvent> history)
    {
        var order = new Order();
        foreach (var e in history) order.Apply(e);
        return order;
    }

    private void Apply(OrderPlaced e) { /* muter l'état */ }
}

5. Idempotence et ordering

Idempotence :

// Vérifier si l'événement a déjà été traité (stocké dans DB ou cache Redis)
if (await _processedEvents.ExistsAsync(context.Message.EventId))
    return; // skip silencieux

await ProcessAsync(context.Message);
await _processedEvents.MarkAsync(context.Message.EventId, ttl: TimeSpan.FromDays(7));

Ordering strict : utiliser une clé de partition cohérente (CustomerId, OrderId) pour que les messages d'une même entité atterrissent dans la même partition Kafka ou la même queue RabbitMQ.


6. Error handling

Tentative 1 → échec → retry après 5 s
Tentative 2 → échec → retry après 30 s
Tentative 3 → échec → Dead Letter Queue (DLQ)
// MassTransit retry + DLQ
cfg.ReceiveEndpoint("order-placed", ep =>
{
    ep.UseMessageRetry(r => r.Exponential(3, TimeSpan.FromSeconds(5),
                                             TimeSpan.FromSeconds(60),
                                             TimeSpan.FromSeconds(5)));
    ep.UseDeadLetterQueue("order-placed-dlq");
    ep.ConfigureConsumer<OrderPlacedConsumer>(context);
});

Alerter sur toute montée du DLQ (metric : dlq_message_count > 0).


7. Monitoring et observabilité

Métriques essentielles :

// Injecter le CorrelationId dans le header de chaque message
Activity.Current?.SetTag("messaging.message_id", message.EventId.ToString());
Activity.Current?.SetTag("messaging.destination",  topicName);

8. Schema evolution

Stratégie recommandée : backward + forward compatible via Confluent Schema Registry (Avro) ou JSON Schema versioned.

Règles :

// v1
{ "orderId": "...", "amount": 100 }

// v2 — ajout optionnel, rétrocompatible
{ "orderId": "...", "amount": 100, "currency": "TND" }

Garde-fous / Anti-patterns

Anti-patternConséquenceCorrection
Event as command nommé ProcessOrderCouplage fort, orchestration cachéeRenommer en OrderPlaced — le fait, pas l'ordre
Payload trop gros (embed entité complète)Overhead réseau, couplage schémaStocker l'ID + champs essentiels ; consumer fetch si besoin
Saga trop large (10+ étapes)Impossible à déboguerDécouper en sous-sagas ou passer à chorégraphie
Pas d'idempotenceMessages dupliqués → double facturationEventId unique + table processed_events
Topic générique (events)Impossible à scaler/monitorer1 topic par aggregate type ou domaine
Consumer partage l'ORMCouplage DB inter-servicesConsumer expose sa propre projection (read model)
Ignorer le DLQPerte de données silencieuseAlertes + runbook de replay obligatoires

Critères pour adopter ou non l'EDA

Adopter si :

Ne pas adopter si :