Les déboires de stratégie de Microsoft concernant l’écriture d’un bon moteur d’accès aux données

sans titre 1

Microsoft a réécrit un nombre incroyable de fois sa couche abstraite de moteur d’accès aux données et les outils correspondants de mappage (ODBC, DAO lui a succédé, ADO, ADODB, ADO.NET, Linq, LinqToSql, Entities, WCF, WCF-Entities for Silverlight, Entities code-first).

Tout cela n’est qu’insistance pour que les développeurs redéveloppent dans chacune de ces écritures successives, mais suite à de véritables échecs de conception.

De ce fait, et dû aux imperfections toujours restantes, bien des entreprises ont carrément écrits et définis autrement leur propre moteur, ainsi que l’outil de mappage correspondant) et n’en bougent plus, car le cœur de Visual Studio.Net reste, indéfiniment, de 2002 jusqu’à maintenant.

ODBC fut le premier moteur, très sommaire mais très professionnel, qui permet surtout d’exécuter des requêtes et de recevoir le résultat. Sa simplicité et son intégration à Windows fait qu’il est toujours utilisé lorsqu’on développe un logiciel un peu décalé des chantiers généraux.

Ensuite, vint DAO, qui fut meilleure couche qu’ADO, pourtant sa suivante, mais ADO était la seule couche adaptée à certaines nouveautés des bases de données, la seule couche à avoir un événement de fin de chargement, pour déjà faire du multithread, ainsi que du chargement incrémentiel ou déconnecté. Mais cette nouvelle couche ADO était complexe et technique, alors que DAO était intuitif. Les évolutions d’ADO pour s’adapter aux nouvelles bases de données ont donné ADODB, toujours complexe et technique, ce n’était pas une grande évolution. Pas de regard aux pauvres bidouilleurs qui appréciaient particulièrement DAO.

Des outils de l’interface graphique s’interfacent directement avec eux, mais dans ces versions, ils plantaient toujours beaucoup et ne permettaient aucune personnalisation, aucune gestion du moindre cas particulier ou spécial qui pourtant survenaient en permanence.

De nombreuses versions ont existé de ces trois moteurs. Des évolutions du numéro de version. Pour les utiliser, il faut lancer leur installateur, commun, qui installe toutes les versions de tous les moteurs, sur le poste du développeur comme celui de l’utilisateur. Les objets de certaines de leurs versions n’étaient liés à aucune documentation ni exploration d’objets en automatique, ni correction automatique, ni compilation très contrôlée.

Ces trois moteurs sont devenus obsolètes car Visual Studio.Net a mis du temps à être développé, il a coupé toutes les chaînes de sorties de version pendant longtemps, mais il a un tel cœur bien pensé (en plus Microsoft l’a normalisé officiellement donc sans brevet rémunérateur), que rapidement après sa sortie (vers 2005, quelques versions quand-même après), il fut devenu logiciel unique de développement Microsoft des nouvelles applications en vue, suivant les espérances de Microsoft. La première grille de Visual Studio n’était pas du tout viable. En 2005 est sorti la DataGridView, à priori suite à mes suggestions de façon non négligeable.

Sous Visual Studio.Net, ADO.Net reprenait dès le départ le flambeau des versions antérieures, car il avait été nécessaire de réécrire un moteur dans le nouveau langage auquel il appartenait, bien profitablement.

Pour la première fois, des outils sont apparus afin d’effectuer un mappage automatique de la base de données, c’est-à-dire une génération automatique de classes représentant les tables, avec leurs membres représentant les champs, et toutes les fonctions environnementales qui vont bien pour les gérer, et des fonctions de transformation et de surcharge supplémentaires à l’occasion.

Pour ADO.Net, les outils de l’interface graphique connectables ne plantent pas, contrairement aux outils avant l’an 2000, mais on ne peut pas se détourner du schéma classique si on les utilise, ce qui est extrêmement contraignant. La personnalisation possible n’est pas caduque du tout, mais linéaire, avec quand-même si on le désire des accès programmatiques. Mais ne confondons pas outil graphique connecté aux vraies données, et DataGridView personnalisable indépendant si on le désire de tout aspect extérieur et données.

Jusqu’à ADO.Net, les moteurs d’accès aux données géraient l’état modifié des données. Toujours de façon plate et brute, sans personnalisation possible des principes généraux. L’évolution, c’est qu’il y ait des principes généraux personnalisables dans ces procédés. Tel que la foison de manières d’annuler, de modifier en cascade les tables enfants, de recopier la clé créée à l’enregistrement, modifier l’ordre d’enregistrement, faire une transaction…

L’idée de Microsoft a été que pour tous les moteurs qui ont suivi, le développement du mode Modification en cours et Annulation est à notre charge.

Premier point d’achoppement. Un énorme énorme travail systématique à réaliser les moteurs qui ont suivi pour mettre au point des procédés de gestion d’état.

Autre point d’achoppement, les moteurs qui ont suivi ne gèrent pas la représentation des champs de manière archétypée, par exemple avec un objet Column. Donc un travail à faire champ par champ, y compris pour le procédé de gestion d’état. Microsoft aurait pu bien tirer les conclusions sur les principes à gérer et à personnaliser, et fournir un outil, pourquoi pas indépendant, de gestion des états.

Le stockage des données est désormais effectué dans une variable par champ, chaque cellule a son membre, plutôt que dans un tableau sans contrôle de type mais privé. Cela retire des possibilités de forçage du type de données, par exemple pour faire une transformation, dans un cas particulier par exemple, tel que stocker une erreur, mais permet des traitements rapides.

Pour autant, le principe de colonne existera toujours dans le principe de tableau, sans que pour autant on ait forcément un numéro de colonne invariable pour un champ. On n’aura pas facilement ici d’outil pour ne pas charger toutes les colonnes, pour n’enregistrer que les valeurs modifiées, pour se souvenir si une valeur a été modifiée, pour ne pas créer toutes les colonnes. C’est discutable, c’est quand-même propre, bien que je n’aie pas suivi ce principe dans la grille que j’ai développé, mais c’est sans grande importance vu les qualités et les défauts. Il est important par contre d’avoir soit un proxy dans un sens, soit un proxy dans l’autre, afin d’avoir une façade de représentation de la table au format du code et fortement typé.

Cette révolution vers LinqToSql a été nécessaire car la nouvelle idée de Microsoft est que dans notre code, nous ayons accès à la base uniquement comme de vrais objets de programmation objet, fortement typés. Le designer génère un mapping dont les DLLs de LinqToSql populent les classes elles-mêmes grâce aux noms des membres et des classes, et quelques-unes de leurs métadonnées. Le code généré automatiquement est donc tout petit, cette fois-ci.

Hélas, troisième point d’achoppement, certaines nécessités nous renvoient à la nécessité de passer par des requêtes, par exemple quand on ne connaît pas la colonne qu’on interroge ou qu’on veut interroger les nouvelles colonnes.

Une couche indépendante de requétage directement dans le code, avec contrôle fort du typage, avec gestion de la syntaxe et aide et information à la saisie, appelée Linq permet d’interroger à la manière de requêtes, ou même utiliser les fonctions de requêtes à la manière de syntaxe normale, pour interroger n’importe quelle source, donc les objets, LinqToSql, et aussi les classes WCF.

WCF est un outil pour que les programmes communiquent entre eux en simulant un partage de l’arborescence des vrais objets à l’intérieur. Un générateur permet de générer du code qui partage sur l’intranet ou internet une structure. Cela permet de faire en sorte que le programme accède à la base par des outils en tout point contrôlés et uniques sur le serveur, ces classes WCF, c’est là que se trouve l’accès à la base. Point d’achoppement, on est obligé de générer cette arborescence dans le programme destination, par un outil. On n’a pas d’outil simple pour accéder facilement à un service WCF sans le connaître.

Un genre de mélange entre LinqToSql et WCF a été aussi créé pour permettre à Silverlight de communiquer avec une base entièrement à distance. Silverlight était un genre de plugin Flash professionnel et sécurisé, fait par Microsoft, pour faire des travaux que ne permet pas le protocole html, mais qui est tombé à l’eau face aux nombreux types d’explorateurs qui n’auraient jamais pu le supporter, particulièrement les mobiles.

LinqToSql a évolué dans la même version de Visual Studio qui l’a vu naître, à partir de son premier Service Pack, vers Entity Framework. Cela n’a pas remis en cause l’intérêt de LinqToSql, qui par-ci par-là était plus simple et de même nature.

Toujours dans la même version, un second Service Pack est venu délivrer une version de Entities qui permettait de ne pas faire hériter les entités d’une classe propre à Microsoft, donc permettait d’hériter de la sienne propre. Intéressant pour gérer des choses du genre mode modification, fonctions communes et considération des champs d’une manière générique.

Cette nouvelle formule permet particulièrement la mise à jour du designer sans devoir recréer les objets, et permet de forcer le nom des membres qui seront utilisés dans le programme. Elle permet aussi d’indiquer des règles de contrôle et de translation, un peu plus de souplesse, et un peu plus de maîtrise de la partie code. Elle permet de modifier les objets dans le designer, et de propager les modifications dans la base et dans le code.

Les métadonnées sur les colonnes sont stockées dans LinqToSql dans les attributs des méthodes, et dans Entities dans des fichiers ressources. Dans les deux cas, ce n’est plus le code généré qui traite directement les cas particuliers. C’est une information traitée par un moteur, donc limitée, lente en ce qui concerne les attributs de méthode.

Point d’achoppement, forcer le nom utilisé dans le programme est très demandé, est intéressant, mais en même temps tellement inutile que beaucoup s’y sont perdus entre les deux noms. Jamais on ne change le nom en base de données, mais en même temps, rien ne sert de le corriger ou de le fixer dans le code. L’erreur restera, et l’utilisateur ne le verra pas.

Mais on se rendait compte qu’on faisait évoluer les designers pour répondre aux besoins, mais qu’en réalité, le besoin d’une classe est infini. L’intégralité des possibilités de paramétrage, de croisement, de croissance doit lui être possible. Jamais on ne pourrait tout déterminer avec un designer. Depuis Visual Studio.Net, on peut ajouter notre propre code au code auto-généré, dans les points d’action prévus par Microsoft, ou par héritage. LinqToSql ne le permet pas utilement.

Bien des entreprises se sont rendues compte de cela, et ont créé leur propre générateur. Parfois en générant la base complète, rendant inutile le passage par un designer, pour peu qu’on ait de nombreux points d’accès dans les classes ainsi générées. Utilisant tout de même le moteur DLL de LinqToSql ou Entities, ou redéveloppement le leur, ça arrive, dans ce cas, il passe souvent par des requêtes. Mais il ne faut pas se leurrer, derrière Linq, il y a toujours jusqu’à maintenant pratiquement que des requêtes.

Suivant ce constat aussi, Microsoft a écrit un nouvel Entities, Entities code-first, qui donne la priorité au code concernant les métadonnées de description des classes. C’est dans le code qu’on définit les paramètres du designer, lui ouvrant ainsi beaucoup plus de possibilités. On peut même ne passer que par le code, écrire d’abord ces classes qui correspondent à des tables directement par le code. Ensuite, à l’occasion, le designer les affichera, en lisant le code, méthode des débuts de Visual Studio.Net, et non en retenant sa propre structure. On croit souvent que code-first veut dire non database-first, mais cela fait longtemps que les outils sont tout autant designer-first. Code first veut dire non designer-first.

Cette méthodologie apporte un autre avantage, c’est que lorsqu’il y a beaucoup de tables, ou des tables dans des bases ou des univers dispersés, il fallait faire plusieurs fichiers Entities par le designer, qui sont pourtant sensés se partager certaines tables, qu’il faut donc définir plusieurs fois, afin que l’interaction ne soit pas coupée. Mais dans ce cas, il faut dupliquer les décorations de ces classes, les héritages, et le polymorphisme entre ces tables sera coupé. Très important pour un développeur pourtant. La méthode code-first permet de définir les tables sans forcément l’inclure dans un designer précis. Ce qui permettra même des lectures et des modifications inter-univers.

Il en va à conclure par les imperfections toujours existantes :

Jamais eu de procédé de gestion des états de modification parfaitement fonctionnel et adapté à tous les cas, qu’il faudrait lister (j’en possède une classe intéressante). Il est vrai que les fonctions proposées par Microsoft étaient inscrites en dur dans les classes ancêtres de base de données, et étaient fermées à toute utilisation large, nécessitant d’adapter le code à leur fonctionnement. On était obligé de passer par elles pour gérer les modifications :

Pas d’état nouveau modifié, pas de possibilité d’avoir un état d’unicité ne provenant pas de la base, pas de gestion des valeurs nulles propre, pas de gestion du readonly spécifique à la ligne, pas de gestion du readonly spécifique à l’utilisateur et pas au code, pas d’outil pour repasser sur les valeurs saisies ou les données lues, une vue en lecture seule ne peut pas être forcée en mémoire (toujours le cas dans Entities), pas de possibilité d’avoir accès à la nouvelle ligne de la grille dans la classe mappant la table en tant que ligne de la table, pas d’annulation en cascade, pas de possibilité de gérer si on écrit toute la ligne ou les champs modifiés…

Pour autant, j’ai connu un des grands déboires de logiciel en ce qui concerne la réécriture de ce procédé pour le mode Entities par un collègue.

Régression au niveau des métadonnées des colonnes : Dans les versions ADO… et DAO…, les colonnes étaient représentées par un objet héritant de DataColumn, qui stockait toutes les précisions nécessaires et plus concernant la gestion de la colonne elle-même, sans stocker sa valeur. Il est vrai qu’il n’était pas héritable et ne gérait pas les cas qu’on aurait voulu personnaliser. Mais elle permettait de traiter la colonne de façon générale, sans la connaître, par exemple dans le procédé de gestion des modifications, dans la zone de saisie (contrôle) de la colonne où on aurait accès à des métadonnées directement, ou pour gérer les nouvelles colonnes ou des colonnes dynamiquement.

Cela serait aussi un moyen d’approche pour retrouver la fonctionnalité de pourvoir accéder aux colonnes dans la base sans avoir le nom dans un mappage fortement typé.

Les métadonnées des colonnes sont stockées dans les premières versions de Entities dans les attributs des méthodes. Mais c’est d’une lourdeur ! Pas de possibilité de calcul, de paramétrabilité avec des fonctions, pas de métadonnées sur les métadonnées, métadonnées difficiles d’accès, lenteur d’accès à ces métadonnées…

Au contraire, c’est une idée à suivre que le principe de stocker les métadonnées dans des classes de métadonnées, portant le même nom que le membre associé, et transportées avec par une classe qui les stocke et qui est aussi passée en paramètre, pourquoi pas la même classe parent.

C’est le cas de Entities Framework code-first.

Frédéric Decréquy, version 3 du 31 juillet 2017.

Print | posted on Friday, July 6, 2018 12:43 AM

Copyright © Frédéric Decréquy

Design by Bartosz Brzezinski

Design by Phil Haack Based On A Design By Bartosz Brzezinski