25 février 2025
Dans un système d'information moderne, la communication entre différents services est souvent orchestrée via un Enterprise Service Bus (ESB), facilitant l'échange d'événements et la répartition des tâches. Cependant, garantir l'atomicité des transactions métiers lorsqu'elles doivent publier des événements sur un bus de messages est un défi majeur. Une transaction partiellement validée ou une incohérence entre la base de données et la file de messages peut entraîner des comportements inattendus et des erreurs difficiles à détecter.
Dans cet article, nous verrons comment RabbitMQ et MassTransit peuvent être utilisés pour assurer l'atomicité de ces transactions dans un service .NET.
Problématique : La Dualité Base de Données et Bus de Messages
Lorsque nous effectuons une opération métier impliquant une modification de la base de données et l'envoi d'un message à un bus de messages comme RabbitMQ, nous avons deux opérations distinctes :
- Enregistrement d'une entité en base de données.
- Publication d'un événement sur RabbitMQ.
Le problème survient si l'une de ces opérations réussit et l'autre échoue. Par exemple :
- Si nous validons l'enregistrement en base mais échouons à publier le message, les autres services ne seront pas informés du changement.
- Si nous publions le message avant la validation de la transaction, un rollback en base rendra l'événement invalide.
Solution : Le Modèle Outbox
Une solution courante pour assurer la cohérence est le modèle Outbox. L'idée est d'enregistrer les messages à publier dans une table de base de données dans la même transaction que l'opération métier. Un processus asynchrone se charge ensuite d'envoyer les messages au bus de messages.
Implémentation avec MassTransit et RabbitMQ
1. Configuration de MassTransit avec RabbitMQ
Dans un projet .NET, nous configurons MassTransit pour utiliser RabbitMQ comme transport.
services.AddMassTransit(x =>
{
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host("rabbitmq://localhost");
});
});
2. Définition de l'Outbox
Nous créons une entité OutboxMessage
pour stocker les événements à publier.
public class OutboxMessage
{
public Guid Id { get; set; }
public string MessageType { get; set; }
public string Content { get; set; }
public bool Processed { get; set; }
}
Lorsqu'une transaction métier est effectuée, nous stockons un événement dans cette table :
using (var transaction = await _dbContext
.Database.BeginTransactionAsync())
{
_dbContext.Add(new Order { Id = Guid.NewGuid(),
Amount = 100 });
_dbContext.Add(new OutboxMessage
{
Id = Guid.NewGuid(),
MessageType = "OrderCreated",
Content = JsonConvert.SerializeObject(
new { OrderId = order.Id,
Amount = order.Amount }),
Processed = false
});
await _dbContext.SaveChangesAsync();
await transaction.CommitAsync();
}
3. Processus Asynchrone de Publication
Un job régulier vérifie les messages non traités et les publie.
var messages = await _dbContext.OutboxMessages
.Where(m => !m.Processed).ToListAsync();
foreach (var message in messages)
{
var eventType = Type.GetType(message.MessageType);
var eventData = JsonConvert
.DeserializeObject(message.Content,
eventType);
await _publishEndpoint.Publish(eventData);
message.Processed = true;
}
await _dbContext.SaveChangesAsync();
Comparaison avec d'Autres Solutions
1. Transactions Distribuées (Two-Phase Commit - 2PC)
Une autre approche est l'utilisation de transactions distribuées avec un coordinateur (comme un Transaction Manager). Cependant, cette solution est souvent plus complexe à mettre en place et peut impacter la scalabilité des systèmes.
2. Sagas et Choregraphie
L'approche par Sagas est une alternative efficace pour gérer les transactions distribuées. Elle repose sur une séquence d'étapes compensables plutôt qu'une transaction unique. MassTransit prend en charge ce modèle, qui est préférable lorsque plusieurs microservices sont impliqués.
3. Stockage Temporaire dans Redis
Certaines architectures utilisent un cache comme Redis pour stocker temporairement les messages à publier, assurant ainsi une forme de persistance légère en cas de panne temporaire.
Conclusion
L'atomicité des transactions métiers est critique dans un système d'information, surtout lorsque des événements doivent être publiés. L'utilisation du modèle Outbox permet d'assurer la cohérence entre la base de données et RabbitMQ tout en conservant la résilience du système. Cependant, d'autres solutions comme les transactions distribuées, les sagas ou le stockage temporaire offrent des alternatives selon le contexte et les besoins de scalabilité.
En appliquant cette approche, vous garantissez que vos événements sont publiés de manière fiable, évitant ainsi des incohérences critiques dans votre architecture logicielle.
Besoin d’aide ou de plus d'infos à ce sujet ?
Contactez-nous