Transactions distribuées pour des bases de données hétérogènes dans MS .NET

Récemment, dans une interview, on m'a demandé si je travaillais avec des transactions distribuées, dans le sens où je devais faire l'insertion / la mise à jour de ces enregistrements, à condition:

  1. Transaction unique.
  2. Il peut s'agir de plusieurs bases de données telles qu'Oracle, MS SQL Server et PostgreSQL.
  3. La réponse à une opération CRUD peut être significative.
  4. La séquence d'insertion n'est pas importante.

Qu'est-ce que mes collègues ont accompli en posant cette question? Vérifiez mon expérience ou obtenez une solution toute faite? En fait, ce n'est pas important pour moi, mais ce qui est important - le problème dans la théorie de la question m'a semblé intéressant, et j'ai décidé d'écrire un article sur la façon dont ce problème pourrait être résolu.

Avant de décrire ma solution, rappelons comment une transaction distribuée typique est implémentée en utilisant la plate-forme Microsoft comme exemple.

Numéro d'option 1. Application C ++ et pilote ODBC (appartenant à Microsoft Distributed Transaction Coordinator (MSDTC) pour SQL Server)

Microsoft Distributed Transaction Coordinator (MSDTC) permet aux applications d'étendre ou de distribuer des transactions sur deux ou plusieurs instances de SQL Server. Une transaction distribuée fonctionne même si deux instances se trouvent sur des ordinateurs différents.

MSDTC fonctionne uniquement localement pour Microsoft SQL Server et n'est pas disponible pour le service de base de données cloud Microsoft Azure SQL.

MSDTC est appelé par le pilote SQL Server Native Client pour Open Database Connectivity (ODBC) lorsque votre programme C ++ gère une transaction distribuée. Le pilote ODBC client natif dispose d'un gestionnaire de transactions compatible XA Open Distributed Transaction Processing (DTP). Cette conformité est requise par MSDTC. En règle générale, toutes les commandes de gestion des transactions sont envoyées via ce pilote ODBC pour le client natif. La séquence est la suivante:

Une application ODBC pour un client C ++ natif démarre une transaction en appelant SQLSetConnectAttr avec la validation automatique désactivée.
L'application met à jour certaines données sur SQL Server X sur l'ordinateur A.
L' application met à jour certaines données sur SQL Server X sur l'ordinateur B.
Si la mise à jour échoue sur SQL Server Y, toutes les mises à jour non validées dans les deux instances de SQL Server sont annulées.
Enfin, l'application termine la transaction en appelant SQLEndTran (1) avec l'option SQL_COMMIT ou SQL_ROLLBACK.

(1) MSDTC peut être appelé sans ODBC. Dans ce cas, MSDTC devient un gestionnaire de transactions et l'application n'utilise plus SQLEndTran.

Une seule transaction distribuée.

Supposons que votre application ODBC pour un client C ++ natif soit inscrite dans une transaction distribuée. Ensuite, l'application est créditée à la deuxième transaction distribuée. Dans ce cas, le pilote ODBC SQL Server Native Client quitte la transaction distribuée d'origine et est inclus dans la nouvelle transaction distribuée.

En savoir plus sur MSDTC ici..

Numéro d'option 2. L'application C #, en tant qu'alternative à la base de données SQL dans le cloud Azur,

MSDTC n'est pas prise en charge pour Azure SQL Database ou Azure SQL Data Warehouse.

Cependant, vous pouvez créer une transaction distribuée pour une base de données SQL si votre programme C # utilise la classe .NET System.Transactions.TransactionScope.

L'exemple suivant montre comment utiliser la classe TransactionScope pour définir un bloc de code pour participer à une transaction.

static public int CreateTransactionScope(
    string connectString1, string connectString2,
    string commandText1, string commandText2)
{
    //        StringWriter   
   // .
    int returnValue = 0;
    System.IO.StringWriter writer = new System.IO.StringWriter();

    try
    {
        //  TransactionScope   , 
        //            
        //  .
        using (TransactionScope scope = new TransactionScope())
        {
            using (SqlConnection connection1 = new SqlConnection(connectString1))
            {
                //      
                // TransactionScope   .
                connection1.Open();

                //   SqlCommand    .
                SqlCommand command1 = new SqlCommand(commandText1, connection1);
                returnValue = command1.ExecuteNonQuery();
                writer.WriteLine("   command1: {0}", returnValue);

                //      ,  ,  
                //  command1  .  
                //    using  connection2   connection1, 
                //         connection2 
                //      .   
                using (SqlConnection connection2 = new SqlConnection(connectString2))
                {
                    //       
                    //   connection2 .
                    connection2.Open();

                    //      .
                    returnValue = 0;
                    SqlCommand command2 = new SqlCommand(commandText2, connection2);
                    returnValue = command2.ExecuteNonQuery();
                    writer.WriteLine("    command2: {0}", returnValue);
                }
            } 
            scope.Complete();
        }
    }
    catch (TransactionAbortedException ex)
    {
        writer.WriteLine("   : {0}", ex.Message);
    }

    Console.WriteLine(writer.ToString());

    return returnValue;
}

Numéro d'option 3. Applications C # et Entity Framework Core pour les transactions distribuées.

Par défaut, si le fournisseur de base de données prend en charge les transactions, toutes les modifications apportées en un seul appel à SaveChanges () sont appliquées à la transaction. Si aucune des modifications n'est effectuée, la transaction est annulée et aucune des modifications n'est appliquée à la base de données. Cela signifie que SaveChanges () est garanti de se terminer avec succès ou de laisser la base de données inchangée en cas d'erreur.

Pour la plupart des applications, ce comportement par défaut est suffisant. Vous devez contrôler manuellement les transactions uniquement si les exigences de votre application le jugent nécessaire.

Vous pouvez utiliser l'API DbContext.Databasepour démarrer, valider et annuler des transactions. L'exemple suivant montre deux opérations SaveChanges () et une requête LINQ exécutées en une seule transaction.

Tous les fournisseurs de bases de données ne prennent pas en charge les transactions. Certains fournisseurs peuvent ou non émettre des transactions lors de l'appel d'une API de transaction.

using (var context = new BloggingContext())
{
    using (var transaction = context.Database.BeginTransaction())
    {
        try
        {
            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            context.SaveChanges();

            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
            context.SaveChanges();

            var blogs = context.Blogs
                .OrderBy(b => b.Url)
                .ToList();

           transaction.Commit();
        }
        catch (Exception)
        {
             
        }
    }
}

Si vous utilisez EF Core dans le .NET Framework , l'implémentation de System.Transactions prend en charge les transactions distribuées.

Comme vous pouvez le voir dans la documentation dans le cadre de la plate-forme et de la gamme de produits Microsoft, les transactions distribuées ne sont pas un problème: MSDTC, le cloud lui-même est tolérant aux pannes, un tas d'ADO .NET + EF Core pour les combinaisons exotiques (lire ici ).

Comment l'examen des transactions sur la plateforme Microsoft peut-il nous aider à résoudre le problème? Très simple, il est clair qu'il n'y a tout simplement pas de boîte à outils standard pour implémenter une transaction distribuée sur des bases de données hétérogènes dans une application .NET.

Et si c'est le cas, nous fabriquons notre propre coordinateur de transactions distribuées coordonnées (CRT).

L'une des options d'implémentation est les transactions distribuées basées sur des microservices (voir ici ). Cette option n'est pas mauvaise, mais elle nécessite un raffinement sérieux de l'API Web pour tous les systèmes impliqués dans la transaction.

Par conséquent, nous avons besoin d'un mécanisme légèrement différent. Ce qui suit est une approche générale du développement du MCT.



Comme vous pouvez le voir sur le diagramme, l'idée principale est de créer et de stocker avec le statut mis à jour l'enregistrement de transaction stocké dans la base de données principale (ou table). Ce modèle de CRT vous permet d'implémenter des transactions distribuées qui répondent aux exigences de:

  • atomicitĂ©
  • la cohĂ©rence
  • isolement
  • longĂ©vitĂ©

- peut faire partie de l'application dans laquelle l'utilisateur a créé un enregistrement, ou peut être une application complètement distincte sous la forme d'un service système. peut contenir un sous-processus spécial qui génère des transactions dans un pool pour exécution lorsqu'une panne de courant se produit (non illustré dans le diagramme). Les règles métier de conversion (mappage) des données sources générées par l'utilisateur pour les bases de données associées peuvent être ajoutées et configurées dynamiquement via le format XML / JSON et stockées dans le dossier d'application local ou dans un enregistrement de transaction (en option). En conséquence, dans ce cas, il est conseillé d'unifier la conversion en bases de données liées au niveau du code en mettant en œuvre la modularité des convertisseurs sous forme de DLL. (Et oui, SRT implique un accès direct à la base de données sans la participation de l'API Web.)

Ainsi, SRT sous une forme simple peut être implémenté avec succès dans le cadre de l'application, ou en tant que solution distincte, personnalisable et indépendante (ce qui est mieux).

Je précise une fois de plus qu’une transaction distribuée est, par définition, un stockage momentané d’informations sans les modifier, mais avec la possibilité de mapper diverses bases de données dans des schémas de données. Donc, si vous devez enregistrer des données dans d'autres bases, par exemple au ministère de l'Intérieur, au FSB et dans les compagnies d'assurance, lors de l'enregistrement des incidents dans la salle d'urgence de l'hôpital (blessure par balle), cette approche vous aidera certainement. Le même mécanisme peut parfaitement fonctionner dans les institutions financières.

J'espère que cette approche vous a semblé intéressante, écrivez ce que vous en pensez, collègues et amis!

All Articles