Les nouvelles transactions distribuées de .NET

Formation .NET, tutoriel & guide de travaux pratiques en pdf.

Les DTC et les Enterprise Services

Pour lever les limitations du modèle classique, Windows met à la disposition du développeur un service système appelé DTC pour Distributed Transaction Coordinator (coordinateur de transaction distribuée).
Son avantage est de porter le concept de transaction à un niveau plus élevé en gérant les transactions au travers des composants, des processus et des machines. DTC utilise le protocole OleTx (OLE Transactions).
S’il est tout à fait possible d’atteindre et de programmer DTC directement, sous .NET la façon la plus directe reste l’utilisation des services de l’espace de noms System.EnterpriseServices.
using System.EnterpriseServices;
[Transaction]
public class MonComposant : ServicedComponent
{
[AutoComplete]
public void MaMéthode()
{
/* interaction avec les ressources
protégées par la transaction */
}
}
Comme le montre le code 2, les Enterprise Services fonctionnent sur un modèle de programmation déclaratif. La classe à protéger par une transaction descend de ServicedComponent et utilise l’attribut Transaction qui garantit que dès qu’une méthode sera appelée, elle sera exécutée dans le contexte d’une transaction. La seule chose que l’objet doit faire c’est de prévenir DTC, par le biais de la classe ContextUtil, que la transaction doit être validée ou annulée.
Plus simple encore, une méthode marquée par l’attribut AutoComplete effectuera ce travail automatiquement : en cas de succès et en fin de méthode la transaction sera validée, en cas d’erreur d’exécution (exception) la transaction sera annulée.

Les limitations des Enterprise Services

Comme on peut le constater, les Enterprise Services offrent un confort et une simplicité de programmation tout à fait appréciables, surtout lorsqu’on pense à la complexité des tâches effectuées automatiquement pour maintenir le contexte transactionnel entre toutes les ressources possibles.
Néanmoins il faut admettre que ce modèle n’est pas sans imperfections ou plutôt « lourdeurs ». Par exemple le fait d’obliger l’héritage depuis ServicedComponent est assez peu pratique, cet héritage prend la place de celui que l’application est en droit d’utiliser et fausse ainsi sa modélisation objet. Il devient alors impossible de supporter les Enterprises Services dans une arborescence de classes métiers sans contorsions stylistiques nuisibles.
D’autre part, l’utilisation des Enterprise Services implique l’utilisation sous jacente de transactions DTC et cela même si un seul objet et une seule ressource sont impliqués dans celles‐ci. L’utilisation systématique du « two phase commit » impose son coût et grignote inutilement les performances de l’application et des serveurs.
Au‐delà de ces inconvénients, les Enterprise Services reposent sur COM+ et ses travers que nombre de développeurs cherchent à fuir… On pourra aussi noter que les stratégies de gestion des E.S. ne sont pas sans poser de souci dès lors qu’on n’opère plus sur des objets activés à la demande mais avec des pools d’objets. Enfin, ajoutons que les E.S. sont thread‐safe et que cela implique que plusieurs threads ne peuvent participés à la même transaction. Il s’agit le plus souvent d’une garantie de bon fonctionnement et d’une simplification du code, mais cela peut s’avérer être une sévère limitation dans certains contextes.

Les transactions sous .NET 2.0

Devant la situation dépeinte plus haut il fallait trouver une solution si ce n’est plus élégante (DTC et les Enterprises Services forment une solution qui l’est plutôt assez), au moins plus souple et ne souffrant pas des défauts que nous avons listés.
Pour ce faire Microsoft a introduit deux nouveaux gestionnaires de transaction ainsi qu’un espace de nom dédié à leur gestion.
Les nouveaux gestionnaires sont le LTM (Lightweight Transaction Manager, gestionnaire léger de transaction) et OTM (OleTx Transaction Manager).

LTM & OTM

LTM a été conçu pour gérer les transactions à l’intérieur d’un seul App Domain2 et n’utilisant qu’une seule ressource.
OTM s’occupe lui de gérer les transactions s’étendant sur plusieurs App Domains, voire sur plusieurs processus ou machines. Il est aussi activé dans le cadre de transactions vivant dans un seul App Domain mais invoquant plusieurs ressources.
Techniquement OTM utilise RPC (Remote Procedure Call, appel de procédure distante) pour les appels inter machines et se comporte finalement de façon assez proche de DTC dont nous avons parlé plus haut.
L’avantage de cette séparation entre LTM et OTM est que lorsque les transactions sont simples (un App Domain et une ressource) la totalité de la gestion réside dans l’App Domain considéré ce qui est beaucoup plus rapide que les invocations RPC. Or il se trouve que les transactions les plus courantes sont de ce type… du coup la nouvelle gestion de .NET 2.0 accélère la majorité des applications par l’utilisation de LTM, sans pour autant présenter de limite lorsqu’il s’agit de traverser les processus et les machines puisque là c’est OTM qui est utilisé.
S’occuper de tout cela pourrait rendre le développement plus complexe. Il n’en est rien puisque une couche de haut niveau a été implémentée par Microsoft, c’est justement System.Transactions.
Une des particularités de cette nouvelle gestion transactionnelle est appelée la promotion de transaction. Dans le cas le plus simple (un App 2 espace d’exécution d’une application .NET.
Domain, une ressource) c’est une transaction de type LTM qui sera utilisée, mais si une ressource supplémentaire est utilisée, si un objet distant est invoqué, alors la transaction sera promue en type OTM, automatiquement. Par défaut, ce qui est modifiable par code, toute nouvelle transaction est ainsi de type LTM. Elle ne se voit promue en OTM que si le contexte l’impose.
Au final c’est donc une gestion simplifiée et unifiée, majoritairement automatique qui est proposée, ce qui rend le développement plus aisé.

Un cas simple

Pour de nombreux développeurs les transactions utilisées par leur code sont presque uniquement des transactions simples : elles vivent au sein de l’App Domain de leur application et ne font qu’invoquer une ressource unique, par exemple une base de données Oracle ou SQL Server.
Dans un tel cas c’est une transaction LTM qui sera allouée par .NET et comme la ressource utilisée est capable de gérer elle‐même la transaction, celle créée par .NET se bornera à monitorer le travail effectué par la base de données.
Dans un tel cas de figure, utiliser les transactions de .NET au lieu de celle de la base de données peut sembler superflu. Ce n’est pas totalement faux, mais comme cette nouvelle gestion de transaction n’alourdit pas le travail à effectuer par la machine ou par le serveur de données, autant l’utiliser car elle garantit une grande cohérence au code (transaction SGBD ou transaction objet, plus aucune différence), permet de résoudre certains problèmes (voir la figure 1), et rend toute amélioration du code simple (par exemple utiliser deux bases de données dans la transaction sans pour autant avoir à en modifier le type et la gestion).

Les cas moins simples

Il y a deux événements qui déclenchent la promotion d’une transaction de type LTM en OTM : l’ouverture d’une connexion à autre base de données ou autre ressource dite « durable », et la transmission au‐delà des limites de l’App Domain de la transaction sérialisée.
Par exemple en cas de remoting il suffit d’ajouter un paramètre de type Transaction à la procédure distante auquel on passera la transaction en cours (Transaction.Current() qui retourne null si aucune transaction n’est engagée).
D’autres mécanismes ou moyen déclaratifs permettent de contrôler le type de transaction (LTM ou OTM) et même dans les cas les plus ardus le nouveau système de transaction s’avère tout aussi puissant que simple.
Il y aurait encore beaucoup à dire sur la façon exacte dont tout cela fonctionne. Mais nous risquerions de lasser ceux d’entre vous plus intéressés par la pratique que par les considérations techniques. Pour ceux que cela passionne tous les détails sur la gestion interne des transactions se trouvent sur les sites de Microsoft et dans certains articles sur le web.

Comment utiliser les transactions de .NET 2.0

Il est temps d’explorer concrètement comment utiliser cette nouvelle gestion de transaction.
Avant toute chose, rappelons que l’espace de noms à utiliser est System.Transactions. Pour y avoir accès dans un assemblage il suffit d’ajouter au projet la référence à la DLL .NET de même nom.
Le modèle déclaratif de programmation des transactions ne change pas et nous ne l’aborderons pas ici, il suffit d’utiliser les Enterprise Services, avec les avantages et défauts dont nous avons parlé en introduction. Comme ADO.NET a été modifié dans .NET 2.0 pour tirer profit des nouvelles transactions il n’y a rien à changer par rapport à du code .NET 1.1. Les LTM seront utilisées à chaque fois que cela est possible ce qui induira de meilleures performances. Ce mode déclaratif n’est pas forcément celui que le développeur devra aujourd’hui choisir.
TransactionScope
Microsoft appelle modèle explicite de programmation des transactions le fait d’utiliser volontairement dans le code les possibilités de l’espace de noms System.Transactions.
Le moyen le plus simple de tirer profit des nouveaux mécanismes passe par la classe TransactionScope, qui, comme son nom l’indique, permet de créer un espace transactionnel pour une section de code.
Lorsqu’une instance est créée une transaction est ouverte (en mode LTM par défaut) et cette instance est placée dans le champ de classe Current de la classe Transaction ce qui permet de pouvoir s’y référer à tout moment et depuis un point d’accès unique.
Comme TransactionScope est une classe qui supporte IDisposable, la transaction se terminera lorsque la méthode Dispose() sera appelée explicitement ou bien implicitement par le biais du mot clé using.
Using (TransactionScope scope = new TransactionScope())
{
/* Manipulation de la ressource durable ici */ scope.Complete(); // aucune erreur, validation
}
Code 3 ‐ TransactionScope
Le code 3 ci‐dessus montre la façon la plus simple d’utiliser les nouvelles transactions. La ressource durable sera le plus souvent une base de données dans les applications de gestion. Mais si le code utilise deux connexions demain, il marchera quand même en mode transactionnel par la magie de la promotion automatique de la transaction LTM en OTM.
Dead-lock
Si le code de la transaction prend trop de temps pour s’exécuter cela peut signifier qu’il existe un dead lock, ou verrou mortel en français. Pour ne pas se laisser piéger par une telle situation la transaction sera automatiquement annulée passé un certain temps. Ce laps de temps dédié
à la transaction est un timeout paramétrable qui est fixé à 60 secondes par défaut. On peut modifier ce temps par programmation ou par déclaration dans un fichier de configuration.
Réglage du timeout
Le timeout peut être spécifié lors de la création de l’instance de
TransactionScope :
TimeSpan timeout = TimeSpan.FromSeconds(25);
using(TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, timeout))
{…}
Code 4 ‐ Régler le timeout
Un timeout de zéro définit un timeout infini. Cela n’est à réserver qu’en mode de débogage lorsqu’il faut pouvoir inspecter le code avec des points d’arrêt sans que le timeout par défaut ne s’enclenche en raison des pauses effectuées dans le traitement.
On peut aussi être amené à poser un timeout très court, de l’ordre de quelques millisecondes, pour forcer la transaction à échouer ce qui est très pratique en débogage pour vérifier comment le code gère les exceptions en cas d’échec.
Si une transaction imbriquée rejoint l’AT3 (voir plus bas) en fixant une valeur de timeout inférieure à celle de l’AT c’est cette valeur qui devient le timeout de l’AT. Si la valeur est plus grande, cela n’a pas d’effet sur l’AT.
La modification de la valeur du timeout peut avoir d’autres motivations (comme éviter des dead‐locks) mais il faut savoir que cela ne doit pas être fait à la légère en raison des implications par toujours évidentes à cerner au premier coup d’œil…
Validation
L’objet transaction n’a en lui‐même aucun moyen de savoir si la transaction doit être annulée ou validée. Dans certains cas comme le timeout l’annulation est choisie automatiquement mais cela reste un cas particulier.
Pour marquer le succès d’une transaction l’objet TransactionScope possède un champ booléen nommé consistency qui est à false à l’initialisation de la transaction. Lorsqu’il passe à true la transaction est validée (Commit). Plutôt que de manipuler consistency il est conseillé, comme dans le code exemple 3 plus haut d’appeler la méthode Complete().
Bien entendu cet appel est unique sur une transaction donnée. L’appeler une seconde fois lèvera une exception InvalidOperation.

Mort d’une transaction

Lorsqu’un objet TransactionScope n’est pas utilisé dans un bloc using et si rien n’est fait volontairement l’objet va à un moment donné sortir de la portée et donc devenir éligible à sa destruction par le ramasse‐miettes. Si GC qui finalise l’objet ou bien si le timeout intervient avant, la transaction sera tuée. L’état des données protégées par la transaction à ce moment dépendra du champ booléen consistency. S’il est à false, la transaction sera annulée, s’il avait été placé à true, notamment par un appel à Complete(), la transaction sera validée.
Il est bien entendu déconseillé de programmer les transactions de cette manière.

Exceptions

Le fait d’appeler Complete() pour valider une transaction ne garantit en rien que cette validation va s’effectuer correctement. Les ressources engagées dans la transaction peuvent refuser la validation pour des tas de raisons. S’il s’agit d’une base de données, le Commit qui sera envoyé au serveur peut échouer car les données insérées, modifiées ou détruites violent des contraintes par exemple.
Dans un tel cas l’objet transaction lèvera une exception de type TransactionAbortedException. Une programmation correcte des transactions se doit de détecter ce cas et d’en avertir l’utilisateur ou de prendre des décisions visant à corriger la situation.

Présentation
La notion de transaction
Au‐delà des SGBD
System.Transactions
Les transactions sous .NET 1.1
Les limitations du modèle classique
Les DTC et les Enterprise Services
Les limitations des Enterprise Services
Les transactions sous .NET 2.0
LTM & OTM
Un cas simple
Les cas moins simples
Comment utiliser les transactions de .NET 2.0
TransactionScope
Dead‐lock
Réglage du timeout
Validation
Mort d’une transaction
Exceptions
Le niveau d’isolation
Les transactions imbriquées
Conclusion
Liens

Cours gratuitTélécharger le cours complet

Télécharger aussi :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *