Architecture pragmatique : Superposition
Paru le 27 novembre 2006

Ted Neward

Octobre 2006

S'applique à : .NET Framework

Résumé : Pourquoi, exactement, construisons-nous des systèmes qui réutilisent le modèle architectural à n niveaux ? Lorsqu'un nouveau projet nous est proposé, nous appliquons automatiquement un principe acquis concernant les logiciels qui consiste à diviser avec précision le système en trois niveaux : le niveau de présentation, le niveau de logique métier et le niveau d'accès aux données. Faire les choses « juste parce qu'elles ont toujours été faites de cette façon » mérite qu'on y réfléchisse d'un peu plus près. (6 pages imprimées)

Pragmatic Architecture: Layering

Certaines choses sont tellement ancrées dans la culture populaire que rien, mis à part un évènement catastrophique, ne pourra jamais les ébranler, même si elles sont depuis longtemps obsolètes. De tels exemples sont particulièrement faciles à trouver dans le monde juridique. Les villes, les états et même le gouvernement fédéral américain ont des lois en vigueur qui n'ont aucun sens à notre époque, ou à n'importe quelle autre époque d'ailleurs (selon la rumeur, dans une petite ville d'Arizona, il est illégal de nager le dos crawlé au milieu de l'autoroute). Cela rappelle un peu cette vieille blague :

un homme, récemment marié, regarde sa femme préparer un rôti pour le dîner. À son grand étonnement, juste avant de mettre le rôti dans le plat, elle coupe une tranche à chaque extrémité du morceau de viande et les jette. Alors qu'il se montre surpris, elle répond : « Le rôti est meilleur quand il est préparé ainsi. En plus, ma mère a toujours fait comme cela. » Curieux, il appelle sa belle-mère au téléphone et lui demande si, elle aussi, coupe les deux extrémités des rôtis avant de les mettre au four et pourquoi. « Parce que le rôti est meilleur cuit ainsi. En plus, ma mère a toujours fait comme cela. » Déterminé à découvrir l'origine de cette habitude, il appelle la mère de sa belle-mère au téléphone, et lorsqu'elle lui confirme qu'elle aussi coupe les deux extrémités des rôtis avant de les mettre au four, il lui demande pourquoi. Aussitôt, elle répond : « Parce que mon plat est trop petit. »

Voici une illustration parfaite de la notion selon laquelle faire les choses « juste parce qu'elles ont toujours été faites ainsi » nécessite une remise en question. Malheureusement, d'autres situations ne sont pas aussi transparentes.

Ainsi, pourquoi, exactement, construisons-nous des systèmes qui réutilisent le modèle architectural à n niveaux ? Lorsqu'un nouveau projet nous est proposé, nous appliquons automatiquement un principe acquis concernant les logiciels qui consiste à diviser avec précision le système en trois niveaux : le niveau de présentation, où nous nous occupons de toutes les données de l'utilisateur ainsi que les questions d'affichage de données ; le niveau de la logique métier, où nous entrons tout ce qui touche à la « logique métier » (ce qui est en soi un terme plutôt neutre qui peut signifier pratiquement tout ce que l'orateur veut qu'il signifie) ; et le niveau d'accès aux données ou ressource, où nous entrons tous les codes qui récupèrent, modifient ou conservent des données. Et, lorsqu'on nous demande pourquoi nous faisons les choses de cette manière, beaucoup d'entre nous n'ont rien d'autre à dire que la réponse passe-partout, « parce que mon mentor (ou dernier chef d'équipe, ou le dernier livre que j'ai lu) faisait toujours de cette façon. » La communication inter-niveaux implique des coûts de performance significatifs, trois à six fois plus importants qu'un appel de méthode in-process normal. Au fond, cela revient au même que de comparer un tour à l'épicerie du coin avec un voyage sur Pluton (voir Java Entreprise Efficace, article 17). En fin de compte, nous jetons de la bonne viande (dans notre cas, des cycles processeurs) juste parce que c'est ainsi que maman a toujours fait !

Après tout, gardez à l'esprit que construire des applications selon une approche nivelée n'est pas la seule option à la disposition des développeurs. Considérez, par exemple, quelque chose que les développeurs d'UNIX connaissent depuis des années et que les développeurs de .NET commencent seulement à découvrir, grâce au prochain Windows Powershell de Microsoft : structurer de petites parties en pièces qui peuvent « se nourrir » l'une de l'autre peut créer des outils composites incroyablement puissants, sans avoir la complexité liée à la maintenance d'une application complexe. Par exemple, créer un outil qui peut chercher dans des dossiers textes une séquence chaîne particulière et la remplacer pourrait sembler une tâche insignifiante pour un développeur quelconque C# ou Visual Basic, mais c'est encore plus insignifiant pour un développeur expérimenté PowerShell, dans la mesure où il s'agit d'une simple concaténation de plusieurs « cmdlets » : un pour effectuer une itération dans les dossiers, un autre pour explorer les contenus des dossiers et remplacer le texte souhaité par le nouveau texte et un troisième pour écrire les nouveaux contenus sur le disque. C'est un beau modèle, notamment car chaque composant peut se concentrer sur une tâche spécifique (itération, recherche, écriture) simplifiant ainsi leur conception et maintenance.

Avant d'émettre un jugement hâtif sur le modèle à niveaux, laissons-lui une chance de se défendre. Tout d'abord, nous pouvons préciser notre utilisation du terme un peu trop passe-partout « n niveau » et remarquer la distinction importante qu'il y a entre niveaux et couches. (Lire Patterns of Enterprise Application Architecture (en anglais) de Martin Fowler pour un traité complet du sujet.)

Une couche est une séparation logique de logiciel, une séparation élémentaire des responsabilités du développeur, afin de pouvoir fractionner plus facilement les responsabilités du système. Ceci est plus longuement documenté dans [POSA1], où le schéma des couches établit que l'utilisation des couches « aide à structurer des applications qui peuvent être décomposées en groupes de sous-tâches dans lesquelles chaque groupe de sous-tâches se situe à un niveau particulier d'abstraction ». En d'autres termes, il s'agit d'une séparation classique des responsabilités : séparer les diverses tâches impliquées dans un système d'entreprise (l'extraction de données, le stockage de données, l'exécution des règles métier contre ces données, l'affichage des données, la collection des entrées et ainsi de suite) en composants ou sous-sections, afin de pouvoir suivre plus facilement ce qui est en train de se produire sous un aspect géographique et temporel. Naturellement, la division du travail la plus commune se trouve dans les couches présentation, logique et accès aux données. Remarquez toutefois que nous ne faisons pas de supposition immédiate sur l'endroit où chacune de ces couches s'exécutera. (du moins pas encore)

Un niveau est une couche physique du matériel, généralement n'importe quel ordinateur, sur lequel certains ou tous nos systèmes peuvent s'exécuter. Le calcul traditionnel client/serveur (écrire un programme qui exécute des instructions SQL contre une base de données en cours d'exécution sur un serveur séparé) est un système à deux niveaux. Le Web, que nous apprécions tous de pouvoir utiliser quotidiennement, est également construit selon une approche à deux niveaux, le premier étant la machine cliente, installée dans la maison ou le bureau d'un particulier et qui accède à distance à un deuxième niveau, installé quelque part dans une salle des serveurs. Et ainsi de suite.

Certains trouveront qu'il s'agit là d'une série de questions non pertinentes. Après tout, le niveau de présentation ne sera-t-il pas toujours installé sur une machine cliente, le niveau accès aux données sur un serveur de base de données, le niveau logique métier sur une machine quelque part entre les deux précédentes ? Examinez de plus près le modèle « classique » à trois niveaux d'une application Web :

  • La couche présentation dans une application Web est un navigateur Web et donc hors de notre contrôle. Le code HTML que le navigateur affichera doit être produit et envoyé au navigateur, généralement depuis un composant code quel qu'il soit (ASP ou ASP.NET) en cours d'exécution dans le serveur. Ceci signifie que la couche « présentation » est désormais divisée en deux niveaux.

  • De la même façon, la couche accès aux données n'est pas entièrement sur le niveau de la base de données, car les commandes nécessaires pour accéder et manipuler les données (c'est-à-dire, SQL) doivent être générées et envoyées depuis l'extérieur du niveau même de la base de données. Ceci signifie que, à l'instar de la couche de présentation, le code couche d'accès aux données est maintenant en train d'être divisé au moins en deux niveaux.

Il ne nous reste plus qu'à prendre conscience que même si cela semble naturel de considérer que la couche de présentation s'exécute au niveau du client, en réalité ceci n'est vrai que pour ce que nous appelons désormais les applications « Client complet » ou « Client intelligent ». Au-delà de tout ceci, la connexion entre « niveau » et « couche » est surtout accidentelle et absolument pas la correspondance un à un que l'on croyait au début.

Les principes et raisons cachés derrière la superposition des conceptions de nos logiciels sont bien connus et, pour la plupart, acceptés par le monde de l'architecture de logiciels. (Si vous n'êtes pas convaincu, ou si vous souhaitez vous informer davantage sur le sujet, lisez l'article « Design Patterns » (en anglais) dans MSDN Magazine du mois d'août 2006, qui aborde en détails le schéma modèle-vue-présentateur, décrivant les intentions et le raisonnement derrière la séparation entre l'interface utilisateur et la logique.) Toutefois, la mise en niveaux reste un point de discussion ouvert ; étant donné le coût de l'envoi de données sur le réseau, cela vaut la peine de s'assurer que c'est véritablement nécessaire.

D'un côté, il est assez facile de reconnaître pourquoi nous voudrions au moins deux niveaux, puisqu'en général nous ne voulons pas mettre des machines de type serveur devant des utilisateurs, pour des raisons à la fois d'efficacité en matière de coût et de centralisation de données. La plupart des discussions sur le système à n niveaux évoquent la mise en place d'un troisième niveau, notamment les composants métier ou la logique qui les hébergent. Sur le diagramme canonique des applications Web, il existe parfois un quatrième niveau, fournissant un niveau client, le niveau serveur Web, le niveau logique métier et le niveau base de données. Pourquoi quatre ? Pourquoi plus de deux, d'ailleurs ?

Historiquement, deux forces se sont rassemblées pour créer l'approche à n niveaux. La première était la nécessité d'évolutivité : Avec le développement d'Internet et l'approche du Web davantage orientée vers l'utilisateur final, les entreprises prirent conscience qu'elles pouvaient mettre à disposition leurs systèmes à leurs clients individuels et partager une grande part du travail réalisé en interne (par des opérateurs de centres d'appel) à l'extérieur de l'entreprise et plus précisément sur le Web. Par exemple, en 1980, un client devait appeler une compagnie de transport et demander un représentant du service clients au sein duquel un colis en particulier était en transit. Le représentant du service clients demandait alors le numéro de suivi du colis et ensuite, à l'aide d'un logiciel interne, cherchait à savoir où se situait le colis. En 2005, le même client saisit simplement sur le navigateur Web de son choix l'adresse du site Internet de la compagnie de transport et tape lui-même le numéro de suivi de son colis. Les mêmes algorithmes back-end cherchent dans les mêmes systèmes back-end de stockage de données mais désormais, la saisie est directement faite par le client et non plus par un employé en interne. Cependant, cette « disponibilité » supplémentaire du système de l'entreprise a entraîné un coût proportionnel : Là où le système interne n'était accessible qu'à quelques centaines d'utilisateurs potentiels (les représentants du service clients), désormais le système peut accueillir des centaines de milliers d'utilisateurs potentiels (les clients). Et c'est ici que se trouve le goulet d'étranglement, en ce sens que la plupart des serveurs de base de données peuvent supporter plusieurs centaines de connexions différentes, mais plusieurs centaines de milliers de connexions différentes peuvent mettre à genoux assez rapidement une base de données.

Toutefois, une propriété intéressante concernant des connexions différentes est finalement apparue : Pour la plupart des applications client/serveur, la connexion établie sur la base de données passait le plus clair de son temps (plus de 95 %) à être inactive, attendant que les requêtes soient exécutées dans la base de données. Ceci signifiait que le goulet d'étranglement se situait dans le nombre de connexions et non dans le travail en cours de réalisation. Ceci impliquait que dans le but d'augmenter l'évolutivité de la base de données, il était nécessaire d'une manière ou d'une autre d'augmenter la quantité de travail effectuée à chacune de ces connexions. Donc, un niveau intérimaire fut créé et les clients se sont connectés à ce niveau intérimaire afin de multiplier les requêtes à la base de données. Pour parler simplement, si la base de données ne peut supporter que 100 connexions et que chaque connexion client n'est utilisée que 1 % du temps, il est possible d'augmenter l'évolutivité de la base de données avec 100 clients connectés à un serveur intermédiaire, lequel utilise ensuite 1 connexion (100 % du temps, 1 % pour chaque client) pour agir sur la base de données. Et voilà : une évolutivité multipliée par cent. Pas mal.

Ceci dit, combien d'applications ont-elles réellement besoin d'une telle évolutivité ? Il est certain que de rendre les applications d'entreprise accessibles à votre nouveau client utilisateur final permettrait à un grand nombre d'applications, basées sur le Web ou non, de rester déployées entièrement en interne, là où moins de 100 (et parfois moins de 10) clients accèderont au système en même temps. Y a-t-il toujours un besoin pour l'approche à n niveaux dans les applications internes à petits nombres d'utilisateurs ?

Des facteurs de sécurité entrent en jeu ici. Si nous prenons une application fonctionnant sur une machine d'utilisateur final (qu'elle soit basée sur le Web ou « Client Complet »), il est improbable qu'un administrateur système ou un consultant en sécurité quel qu'il soit, recommande qu'une base de données contenant des données sensibles soit installée directement derrière un pare-feu, opérant en dehors du périmètre de sécurité, par exemple. L'installation d'une machine intermédiaire, avec un autre pare-feu derrière, crée ce qu'on appelle communément une zone démilitarisée, ou ZDM, à partir de laquelle l'accès à la base de données peut être encore plus restreint. Une telle ZDM renforce de manière significative l'infrastructure de sécurité et réduit la probabilité de pénétrations. Ceci protège non seulement les données du vol, mais aide aussi à protéger les serveurs (et ainsi le reste de l'application ou système) de toute attaque réussie de refus de service.

Un deuxième facteur, qui a rendu les systèmes à n niveaux séduisants aux yeux de nombreux propriétaires de systèmes volumineux, concerne le déploiement, c'est-à-dire le fait de mettre physiquement le logiciel sur une machine accessible aux clients. Sous un environnement traditionnel client/serveur, la logique métier inextricablement liée à la logique de présentation et à la logique d'accès aux données conduisait les programmeurs à un constat inconfortable : chaque fois qu'une nouvelle mise à jour était nécessaire (tels que des changements dans la façon dont le métier traite les données, ou un nouvel affichage souhaité de ces données), le « client lourd » installé sur le bureau de l'utilisateur devait être remplacé et/ou augmenté au moyen d'un nouveau code. Ce qui voulait dire, au moins à l'époque, que quelqu'un, en général, n'importe quel développeur ou administrateur système de la hiérarchie, devait faire le tour des machines pour installer le nouveau code. Il fallait, autrement, que les utilisateurs téléchargent le dernier code à partir du réseau. La plupart d'entre eux n'y prêtait naturellement pas attention ou ne le faisait pas correctement. Aucun de ces scénarios n'amena qui que ce soit à faire véritablement confiance à la prudence des fréquentes sorties de nouvelles versions. Les déploiements prirent du temps et pendant ce temps le système dut s'arrêter pour éviter toute sorte de corruption sémantique de données causée par des versions mélangées d'applications frappant constamment sur la base de données.

Ce facteur de déploiement contribua de façon significative à la vitesse d'adoption du modèle à n niveaux et plus spécifiquement à l'application basée sur le Web. Maintenant, au lieu de devoir déployer des codes vers les bureaux d'utilisateurs individuels, les codes pourraient être déployés vers le (seul) serveur Web et le navigateur de l'utilisateur final irait simplement chercher les modifications sans qu'il soit nécessaire de faire quoi que ce soit de plus. En soi, le déploiement n'est pas une raison pour introduire un système à n niveaux ; plusieurs alternatives, qui n'étaient pas disponibles à l'époque des applications traditionnelles client/serveur, sont maintenant ajoutées à la liste des possibilités de déploiement, comprenant No-Touch Deployment (in .NET 1.x) et ClickOnce (in .NET 2.0), sans oublier de mentionner l'intérêt grandissant pour AJAX et diverses combinaisons hybrides. En fait, il est devenu commun de publier une application client complet qui se met à jour toute seule au démarrage, tel que ce que nous pouvons voir avec le iTunes Software Manager, Windows Media Player, ou même Reflector, l'outil de développement courant de .NET.

Une troisième raison de considérer que le système à n niveaux était couramment cité, mais pas fréquemment mis en oeuvre : l'idée du niveau intermédiaire servant de point de ralliement pour la logique de présentation-agnostique auquel on pourrait accéder par de multiples niveaux de présentation. L'exemple canonique de ceci est l'application de la combinaison intranet/extranet, dans laquelle les employés en interne utilisent une application WinForms (ou, dans un avenir proche Windows Presentation Foundation) pour accéder à un système niveau intermédiaire lequel, à son tour, accède à la base de données, tandis que les clients externes (partenaires et/ou clients) utilisent un ASP.NET ou, peut-être, un site web Sharepoint pour faire la même chose : accéder au niveau intermédiaire, lequel à son tour accède à la base de données.

Cette idée, bien qu'apparemment banale dans son concept, se révèle être plus difficile qu'il n'y paraît à réaliser d'une manière architecturale solide. Voici également le moment où il devient crucial de faire la différence entre niveaux et couches : S'il y a une distinction claire entre la couche de présentation, la couche logique métier et le niveau intermédiaire, il devient alors possible de graver la couche logique métier dans le niveau client (dans le cas de l'application frontale client complet) et obtenir des économies de performance significatives en évitant l'accès au réseau.

Concevoir cette couche logique métier afin qu'elle soit utilisée dans deux niveaux différents peut toutefois se révéler délicat. Cela veut dire que la couche logique métier doit éviter toute sorte d'hypothèses pour savoir si la couche de présentation ou la couche d'accès aux données co-existent dans le même niveau et ainsi doit assumer que ni l'une ni l'autre n'existent. En particulier, l'endroit où cela peut supprimer une application se trouve lorsque s'effectue la liaison des données avec les objets Business qui sont en réalité des objets distants (par l'intermédiaire de .NET Remoting) s'exécutant sur le serveur de niveau intermédiaire. Maintenant, chaque propriété et chaque appel de méthode constituent une traversée réseau et la performance chutera plus vite que la crédibilité d'un architecte qui construit un système défectueux.

Heureusement, des architectes du monde entier ont commencé à prendre conscience des dangers de l'approche « objets distribués » et maintenant le mantra du jour dit « couplage lâche » et « communication à gros grain », le tout sous l'égide de l'approche orientée service vers l'architecture. Comme tout ce qui est lié au développement de logiciels, l'orientation de service possède ses propres pièges, mais nous verrons cela une autre fois.

À propos de l'auteur

Ted Neward est consultant indépendant, spécialiste des systèmes d'entreprise à haute échelle. Sa clientèle s'étend des entreprises les plus puissantes apparaissant dans Fortune 500 aux petits magasins de 10 employés. Il est une autorité reconnue en matière de technologies Java et .NET, particulièrement dans les domaines d'intégration Java/.NET (à la fois in-process et au moyen d'outils d'intégration tels que les services Web), les systèmes logiciels d'entreprise back-end et les éléments de moteur machine/exécution virtuelle.

Il est l'auteur et le co-auteur de plusieurs ouvrages, parmi lesquels Effective Enterprise Java, C# In a Nutshell, SSCLI Essentials et Server-Based Java Programming et il contribue également à de nombreux journaux spécialisés en technologie. Ted est aussi MVP Architecture chez Microsoft, Directeur Technique BEA, conférencier ITEA, formateur Pluralsight et membre de plusieurs JSR. Il vit sur la côte nord-ouest des États-Unis avec sa femme, leurs deux fils, deux chats et ses huit PC.

Contactez-le à l'adresse suivante : ted@tedneward.com, ou visitez le blog de Ted.

Page view tracker