Ou tapez /outbox-pattern-guide 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 :
Identifier le problème : dual write, perte de messages, incohérence entre services.
Choisir le pattern : Outbox pour la publication fiable, Saga pour les transactions multi-services.
Concevoir : tables, processus de polling/CDC, compensation.
Implémenter : avec le framework approprié (MassTransit, NServiceBus, custom).
Le problème du Dual Write
❌ PROBLÈME : Dual Write
Service → Save to DB ✅ Succès
Service → Publish to Broker ❌ Échec
→ DB mis à jour mais message perdu = incohérence
Pattern Outbox
Principe
✅ SOLUTION : Outbox
Service → Transaction DB {
Save entity to DB
Save event to OutboxMessages table
} → Commit atomique
Background Worker → Poll OutboxMessages
→ Publish to Broker
→ Mark as processed
Table Outbox
CREATE TABLE OutboxMessages (
Id UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
EventType NVARCHAR(256) NOT NULL,
Payload NVARCHAR(MAX) NOT NULL,
CreatedAt DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(),
ProcessedAt DATETIMEOFFSET NULL,
RetryCount INT NOT NULL DEFAULT 0,
Error NVARCHAR(MAX) NULL,
INDEX IX_OutboxMessages_Unprocessed (ProcessedAt, CreatedAt)
WHERE ProcessedAt IS NULL
);
Implémentation C#
// 1. Sauvegarder l'entité + l'événement dans la même transaction
public async Task CreateOrder(CreateOrderCommand command)
{
var order = new Order(command);
var outboxMessage = new OutboxMessage
{
EventType = nameof(OrderCreated),
Payload = JsonSerializer.Serialize(new OrderCreated
{
OrderId = order.Id,
CustomerId = command.CustomerId,
Amount = command.Amount
})
};
_context.Orders.Add(order);
_context.OutboxMessages.Add(outboxMessage);
await _context.SaveChangesAsync(); // Transaction atomique
}
// 2. Worker qui publie les messages en attente
public class OutboxProcessor : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
var messages = await _context.OutboxMessages
.Where(m => m.ProcessedAt == null)
.OrderBy(m => m.CreatedAt)
.Take(100)
.ToListAsync(ct);
foreach (var message in messages)
{
try
{
await _bus.Publish(message.EventType, message.Payload);
message.ProcessedAt = DateTimeOffset.UtcNow;
}
catch (Exception ex)
{
message.RetryCount++;
message.Error = ex.Message;
}
}
await _context.SaveChangesAsync(ct);
await Task.Delay(TimeSpan.FromSeconds(5), ct);
}
}
}
Order Service → OrderCreated
→ Payment Service → PaymentProcessed
→ Inventory Service → InventoryReserved
→ Notification Service → OrderConfirmed
Si échec à n'importe quelle étape :
→ Compensation en sens inverse