Dossier spécial : AOP, Intérêts et Usages  par Patrick Smacchia et Sébastien Vaucouleur

 

Introduction. 28

Pourquoi cet article ?. 29

Pourquoi l’AOP ?. 32

(Bref) Glossaire de l’AOP. 45

Quelques-uns des tisseurs existants. 55

Utilisations de la programmation par aspects. 65

Logging/Tracing. 66

Gestion des exceptions. 73

Debugging et tests. 80

Assertions, Contrats et Contraintes. 87

Profiling de performance. 100

Optimisation des performances. 105

Planification de l’utilisation des ressources. 116

Détection de changements et mise à jour 125

Synchronisation des accès concurrents. 128

Globalisation de l’application. 137

Propagation de contexte. 155

Persistance & Système Transactionnel 160

Sécurité/Gestion de sessions. 178

Configuration. 189

Meilleures implémentations des design patterns GoF. 198

Utilisation d’objets distants. 207

Conclusion & Remarques sur l’AOP. 219

 

Introduction

Pourquoi cet article ?

Le but de cet article est de présenter le plus grand nombre d’applications concrètes de la programmation par aspects (AOP, Aspect-Oriented Programming). En effet, en général la plupart des articles qui traitent de l’AOP présentent les concepts accompagnés de deux ou trois exemples concrets. Or, nous pensons que pour promouvoir ces idées séduisantes, les développeurs doivent bien saisir quelles sont les nombreuses facettes de leurs activités qui seront simplifiées par l’utilisation de l’AOP. Nous ne nous focaliserons sur aucune implémentation spécifique permettant l’AOP (comme AspectJ, le futur AspectDNG ou les messages sink de .NET Remoting).

 

Pourquoi l’AOP ?

Rappelons que l’AOP permet de contourner certaines limites du modèle de programmation par objets (OOP, Object-Oriented Programming). Idéalement, chaque tache spécifique devrait être devrait être la seule responsabilité d’une classe ou d’un petit nombre de classes éventuellement rassemblées au sein d’un groupement logique. Or, il suffit de lire le code source de n’importe quelle classe écrite dans un langage objet (C++, Java, C#, VB.NET…) pour se rendre compte que beaucoup de lignes de code sont consacrées à la synchronisation des accès aux ressources, à l’optimisation de l’utilisation des ressources, à la sauvegarde de certaines données dans des bases de données ou autre (i.e la persistance), au log des états du programme, à la vérification des paramètres entrants, au traitement des exceptions etc. (le terme anglais pour designer toutes ces activités est crosscutting concerns). Ces lignes de code ont tendance à être réécrites dans la plupart des méthodes des classes d’une application. Il en résulte que les classes sont peu réutilisables telles quelles par d’autres applications (autrement dit les classes sont couplées avec l‘application). Il en résulte aussi que le changement de politique sur un de ces aspects de l’application (par exemple si on décide de ne plus loguer certaines informations) oblige la modification de nombreuses lignes de code éparpillées dans le code source, ce qu’on pourrait décrire comme un problème de localité textuelle: tout code concernant le même aspect devrait être regroupé dans un même endroit. Cela se rapproche bien sur du concept de modularité.

 

L’AOP nous permettra donc d’éviter le couplage et d’augmenter la cohérence lorsque les méthodes classiques de l’objet atteignent leurs limites. La figure suivante illustre le rapport entre la logique métier du code et les aspects qui ont pu être modularisés:

 

 

Une bonne manière de mettre en contraste la programmation traditionnelle par objet a la programmation pas aspects est de visualiser la programmation par aspect comme une méthode de développement logiciel horizontale (par couches), par opposition a la programmation par objet qui se rapproche plus a une méthode de développement et de design a tendance verticale. La programmation par aspect est particulièrement efficace quand il s’agit de modulariser une certaine logique commune à deux modèles objets qui doivent rester indépendants:

 

None

 

Nous n’allons pas écrire une nième introduction à l’AOP. Vous pouvez vous référer aux liens suivants: [DNG1], [Cheng03], [Lieberherr2], [Talby], [Shukla]. Le lien suivant écrit par Gregor Kiczales, le chef de projet d’AspectJ, est particulièrement intéressant puisqu’il décrit les contraintes que doit supporter un tisseur.

 

(Bref) Glossaire de l’AOP

Les termes suivants reviennent souvent dans les articles concernant l’AOP.

 

Terme

Description

Tangled code

Code embrouillé, code spaghetti.

Crosscutting concerns

Aspects de la programmation qui concernent plusieurs classe, et qui donc transcendent le modèle objet (synchronisation, logging, persistance…).

Weaver

Tisseur, c’est l’infrastructure mise en place pour greffer le code des aspects dans le code des méthodes des classes. Il est important de noter que selon les tisseurs cette greffe peut avoir lieu à différents moments :

·         directement sur le code source donc avant la compilation

·         durant la compilation

·         après la compilation sur le code compilé mais avant l’exécution

·         pendant l’exécution, durant la compilation JIT

·         pendant l’exécution, après la compilation JIT

Joinpoint pointcut

Infrastructure mise en place pour définir le lieu des greffes dans le code, et éventuellement quand elles doivent être appliquées ou pas

Advice

Fragment de code destiné à être greffé

 

Quelques-uns des tisseurs existants

Plusieurs tisseurs existent pour les plateformes de développements Java et .NET. A l’heure actuelle, le tisseur le plus utilisé et le plus abouti est sans conteste AspectJ, qui n’existe que pour Java. Il tisse les aspects avant l’exécution, ce qui lui confère de bonnes performances. Une bonne introduction à AspectJ est disponible dans [Laddad2]. Un des problèmes d’AspectJ est que les informations concernant la localisation des greffes (les joinpoint pointcuts) et le code java à greffer (les advices) ne sont pas dissociées. Toujours dans le monde Java, il faut également noter le projet HyperJ d’IBM. Le server applicatif JBoss permet aussi d’utiliser la programmation par aspect, notamment pour le système de persistance, tout cela est décrit dans [Burke].

 

L’idée d’intercepter les appels entre objets afin de leur rajouter des fonctionnalités d’une manière transparente pour le développeur a été implémentée très tôt dans la technologie COM+, décrite dans l’ouvrage [Transactional COM+]. Le principal défaut de COM+ est qu’on ne peut développer nos propres aspects mais seulement utiliser la quinzaine de services d’entreprises proposés dans sa dernière version. De même, dans la technologie Java, le container supportant les EJBs intercepte les messages envoyés aux Beans et peut ainsi exécuter un supplément de code. Mais comme pour COM+ le mécanisme n’est pas extensible et se limite aux quelques aspects proposés (transactions, sécurité..).

 

Comme nous allons le voir, le fait d’appeler un objet d’une manière distante est un aspect de la programmation. La technologie .NET Remoting tire son nom de l’importance de cet aspect. Cependant cette technologie permet de développer vos propres aspects et à ce titre, on peut dire que .NET Remoting est une infrastructure pour mettre en place l’AOP. Cette infrastructure a deux défauts que sont le coût de l’utilisation d’aspects (dû à un mécanisme de contexte et d’interception dynamique) et l’obligation pour les classes utilisant des aspects de dériver de la classe ContextBoundObject. Une description plus détaillée de ces deux problèmes se trouve dans [DNG1] ). Néanmoins, nous nous apercevrons que certains aspects de la programmation ne souffrent pas des conséquences de ces défauts. Une description complète de l’AOP avec .NET Remoting est exposée dans [.NET et C#].

 

La technologie des services web autorise la greffe des aspects sur les appels grâce aux SOAP extensions. Une bonne description des SOAP extensions est disponible dans [MSDN].

 

D’autres projets de recherche de tisseurs existent dans le monde .NET mais il semble que la plupart aient été stoppés ou du moins très ralentis. Voici des liens sur ces produits: [Cameo,Prasad] (tissage sur le code source C#), [AspectC#] (tissage à la compilation), [Lam] (tissage de code CIL à l’exécution). Dans ce contexte, nous espérons que le projet AspectDNG ([AspectDNG1], [AspectDNG2]) trouvera sa place dans le monde industriel .NET, au même titre qu’AspectJ dans le monde industriel Java.

Utilisations de la programmation par aspects

Logging/Tracing

Le logging est certainement une des utilisations de l’AOP les plus citée dans les articles concernant ce sujet. Nous pensons que cette mise en avant du logging est assez néfaste pour la promotion de l’AOP, car elle offre une vision un peu réductrice. La plupart des développeurs ne considèrent pas le logging d’information sur l’état d’une application comme un aspect majeur de la programmation. En outre, plusieurs infrastructures de logging très faciles d’utilisation existent quelle que soit la plateforme sur laquelle on développe (Event Viewer par exemple, ou une infrastructure propriétaire sous .NET).

 

En plus d’exposer un exemple avec AspectJ, [Kiczales1] nous explique (dans sa section 3.1) que le bénéfice de l’AOP pour la gestion des logs tient dans le fait qu’il est très facile de les désactiver lorsque l’on estime que l’application n’en a plus besoin.

 

L’article [MSDN] expose le code en C# et en VB.NET d’une infrastructure de logging orientée aspect, qui enregistre les messages envoyés et reçus par un service web.

 

Gestion des exceptions

Une infrastructure permettant l’AOP doit pouvoir produire des greffes de code capables de gérer les exceptions. Par exemple, dans la technologie .NET Remoting, si une méthode se termine à cause du lancement d’une exception, l’interface IMethodReturnMessage présente la propriété Exception pour permettre de la récupérer et éventuellement de la traiter et/ou de la propager, dans l’intercepteur de messages. AspectJ prévoit la syntaxe avec le mot clé handler spécialement pour greffer des ‘advices’ dans une méthode qui remplit le rôle d’un catch traditionnel.

 

[Kiczales3] présente un exemple de gestion des exceptions avec AspectJ sur un programme représentatif. Avant l’utilisation d’AspectJ presque 11% du code était consacré à la gestion des exceptions alors qu’avec l’utilisation de l’AOP, cette part tombe à moins de 3%. Cette réduction considérable provient de la factorisation grâce à l’AOP des nombreux traitements d’exception similaires. [Kiczales1] présente dans sa section 4.4 le log de toutes les exceptions d’une application au moyen d’AspectJ.

 

Il est intéressant de loguer toutes les exceptions avec l’AOP. En revanche nous vous conseillons de ne pas traiter toutes vos exceptions avec l’AOP. En effet, pour des raisons de lisibilité du code (lorsque l’IDE ne dispose pas de modules facilitant l’AOP) la gestion de certaines exceptions devrait rester telle quelle. Par exemple, dans une application bancaire, il est intéressant de laisser le traitement de l’exception propriétaire BalanceInsuffisanteException proche du code qui effectue le débit du compte. Cependant, si cette exception est à rattraper dans de nombreuses méthodes dans votre application, il serait judicieux de modulariser son traitement par un advice.  

 

Debugging et tests

[Lieberherr2] explique que pour résoudre un bug, on est bien souvent amené à truffer notre code de traces et d’assertions. Une fois le bug résolu, il est frustrant de devoir effacer ou commenter tout ce travail, qui pourrait nous servir pour la recherche d’autres bugs éventuels. On peut aussi faire en sorte que ce travail ne soit pas compilé pour la version en production (release) de l’application (par exemple en utilisant la macro ASSERT dans VC++). Dans ce cas, le code est fortement pollué par toutes ces vérifications. De plus, si l’on ne fait pas attention, une telle pratique peut introduire des changements de comportements entre la version debug et release d’une application. Par exemple avec VC++ la ligne ASSERT( UpdateDB(Data) = true ) provoque l’exécution de la méthode UpdateDB() en mode debug mais pas en mode release, ce qui peut influencer l’exécution du programme (et allez trouver pourquoi le comportement de l’application compilée en mode release est différent du comportement en mode debug !!!).

 

L’AOP peut aussi servir à faciliter les tests grâce à une implémentation modulaire de l’accès à certains services. Dans son excellent ouvrage [PEAA] Martin Fowler présente le design pattern service stub. Ce design pattern permet le test d’une application même lorsque l’on a pas encore accès à tous les services consommés par l’application. L’idée est de fournir des petits modules qui présentent les mêmes accès et qui simulent les services manquant. Si l’accès à ces modules est totalement modularisé dans des aspects, l’implémentation du service stub revient à utiliser d’autres aspects durant la phase de test. Une application intéressante de cette idée est de modulariser la couche de persistance dans des aspects (comme décrit un peu plus bas) de façon à pouvoir effectuer les tests indépendamment de tous SGDB et de tout état d’une base de données.

 

Un peu dans le même esprit que l’AOP, l’attribut standard .NET ConditionalAttribute permet d’autoriser ou non l’exécution des méthodes sur lesquelles il s’applique, en fonction de la définition ou non de constantes symboliques à la compilation. Le point intéressant est que les lignes de code concernées par les appels vers une de ces méthodes ne sont en rien impactées par le fait que l’exécution de la méthode est activée ou non.

 

Assertions, Contrats et Contraintes

Un autre cas de pollution du code est la vérification de la validité des paramètres avant l’exécution d’une méthode. Les vérifications rentrant dans le cadre de la logique métier (par exemple, la vérification d’une adresse mail saisie par un utilisateur) et les détections de paramètres invalides dues à un bug (détection d’un pointeur nul). L’AOP résout ces problèmes en permettant de rassembler dans un même endroit du code toutes ces assertions. Il est ainsi aisé d’annuler temporairement ou définitivement leurs effets.

 

Ainsi, la programmation avec ‘design by contracts’ a été popularisée par le langage Eiffel. Un contrat peut être par exemple une pré-condition à l’exécution d’une méthode (vérifier que l’un des arguments est non nul, qu’un entier passé en paramètre est strictement positif etc…) ou une post-condition à la fin de l’exécution d’une méthode (par exemple pour vérifier qu’une transaction a été effectuée correctement). D’autres contrats ne se limitent pas à une méthode mais s’entendent sur l’ensemble d’une classe (Eiffel utilise le mot-clé invariant). Il conviendrait par exemple de définir pour une classe LinkedList que ‘NbElement >= 0’. Tous ces contrats permettent de rajouter des contraintes sur l’utilisation possible des classes, de faciliter la projection des spécifications vers le code, de renforcer la cohérence des classes et de façon générale de rendre vos applications plus stables.

 

On peut bien sur concevoir d’autres types de contrats, beaucoup plus élaborés. La programmation par aspects vous permet de concevoir ces contraintes de façon élégante à la fois à la compilation et à l’exécution. Imaginer que vous écriviez un système sophistiqué de pooling pour un certain type d’objets (en utilisant par exemple un system de poll d’objets à multi niveaux thread local/thread globale). Vous pouvez grâce a l’AOP rajouter une contrainte qui force les autres programmeurs à utiliser le mécanisme de poll d’objets lorsque qu’ils ont besoin d’une nouvelle instance de cet objet (et éviter ainsi qu’ils créent directement l’objet dans leur code en appelant un constructeur). Ainsi la section 3.3 de [Kiczales1] montre comment utiliser AspectJ pour détecter des appels de méthodes qui ne devraient pas exister.

 

L’exemple 4 de la partie 3 de [Laddad2]  utilise AspectJ pour raffiner les contrôle d’accès aux membres d’une classe Java (normalement limité à public, private, package et protected). Tout en étend beaucoup plus puissant, cela se rapproche un peu du concept d’amitié entre classes ou méthodes de C++.

 

L’exemple 2 de la partie 3 de l’article [Laddad2]  utilise AspectJ pour garantir une contrainte d’unicité pour pallier certains problèmes de Swing MVC (Model View Controller). En effet, Swing MVC vous permet d’ajouter plusieurs listeners à une vue, ce qui n’est jamais nécessaire. En outre, avant la destruction de la vue, vous devez vérifier vous-même qu’il n’y a pas plus qu’un seul listener. Dans le cas contraire, les listeners ne seraient pas détruits et cela conduirait à des fuites de mémoire. La contrainte d’unicité implémentée par cet exemple porte donc sur le listener d’une vue Swing MVC.

 

Beaucoup plus d’informations à ce sujet sont disponibles dans le chapitre 6 de l’ouvrage AspectJ in Actionpolicy enforcement: system wide contract’ que vous pouvez télécharger avec ce lien [Laddad1].

 

Profiling de performance

La tache d’un ingénieur chargé d’améliorer les performances d’un gros logiciel n’est souvent pas facile: Les taches de profiling de performance nécessitent souvent le rajout de code dans tel ou tel module du logiciel suivant le ‘bottleneck du jour’. Hors, il se trouve que notre ingénieur n’ a souvent pas le droit de modifier cette partie du code (dans les très gros projets informatiques, chaque équipe travaillant sur le logiciel est souvent responsable pour sa partie du code et ne laisse pas souvent les autres équipes empiéter sur leur ’propriété’). Une possibilité est de travailler sur une version privée du code source, mais notre ingénieur remplie de bonne volonté aura alors en plus la tache de synchroniser régulièrement ses modifications avec les nouvelles versions du logiciel. Une autre possibilité s’offre à lui: celle d’utiliser l’AOP et de procéder à un profiling non-intrusif.

 

Nous avons développé avec succès toute une série d’aspects destinée au profiling de performance d’un large système de gestion de réseau écrit en Java, impliquant un grand nombre de lignes de code et de développeurs. L’AOP permet en quelques mots de changer la granularité du profiling mis en place (profiling d’un module, d’un thread, de toutes les instance d’un certaines classes ou d’un objet en particulier). Il est également très facile de modifier le système de stockages des informations de profiling (‘in-memory’, log sur le file system, ou même directement dans la base de données lorsque la quantité d’informations à stocker n’est pas trop importante).

 

Optimisation des performances

De nombreuses techniques existent pour optimiser les performances d’une application. Encore une fois, cet aspect du développement logiciel est bien souvent orthogonal à la logique métier des applications.

 

La technologie COM+, offre deux services d’entreprises qui optimisent l’utilisation des objets: le mécanisme de pooling d’objets et le mécanisme JITA (Just In Time Activation). Des descriptions de ses mécanismes ainsi que de leur utilisation peuvent être trouvées dans [.NET et C#] ou [Transactional COM+]. Durant l’exécution, le mécanisme de pooling d’objets permet de réduire considérablement le nombre d’appels au constructeurs et aux destructeurs de la classe sur laquelle il s’applique. L’astuce est de recycler un objet après utilisation par un client. Le mécanisme de JITA est une réponse à la problématique suivante: il est impossible de prédire l’intervalle de temps entre deux appels consécutifs par un même client à un même objet. En conséquence, les ressources mobilisées pour l’objet (une connexion à une base de données, un thread…) sont inutilisées durant tous ces intervalles de temps, d’où un réel gaspillage de ressources. Le mécanisme JITA résout ce problème en construisant un nouvel objet pour chaque appel. Bien entendu, il est nécessaire que l’objet soit sans état ou qu’un mécanisme de sauvegarde et de restauration d’état soit implémenté. De plus, vous comprendrez aisément que le mécanisme JITA et le mécanisme de pool d’objets sont souvent utilisés conjointement sur les même classes.

 

L’exemple 1 de la partie 3 de [Laddad2] expose l’implémentation d’un mécanisme de pooling de ressources avec AspectJ. En l’occurrence, les ressources concernées sont des threads dans une application de type serveur.

 

Vous pouvez utiliser l’AOP pour optimiser l’utilisation du réseau. Supposons que vous deviez utiliser un objet distant qui a besoin d’avoir quatre propriétés positionnées avant d’être utilisable. Pour éviter de faire quatre appels réseau pour positionner une à une ces propriétés, vous pourriez ajouter une méthode qui prend les quatre valeurs de ces propriétés. Cela n’est cependant pas possible si vous n’avez pas accès à la classe de cet objet. Vous pouvez cependant tisser du code qui stocke les valeurs de ces quatre propriétés et empêche l’envoi de messages réseaux lors du positionnement de ces propriétés. L’envoi de ces quatre valeurs aura effectivement lieu qu’au premier appel d’une méthode sur l’objet. L’utilisation du réseau peut ainsi être optimisée sans avoir à modifier le client ou la classe de l’objet serveur. (L’astuce décrite s’apparente au design pattern Remote Facade exposé dans [PEAA]).

 

Vous pouvez utiliser l’AOP pour optimiser le chargement de données. En effet, il est souvent efficace de repousser le chargement d’une donnée jusqu’au moment où le programme en a effectivement besoin. Par exemple, si vous avez à utiliser des données stockées dans une arborescence, il vaut mieux ne pas charger complètement l’arbre mais seulement la racine de l’arbre. Au fur et à mesure du parcours des branches par le programme, les données sont chargées puis stockées en mémoire. Au final, le programme n’aura chargé que les données dont il avait réellement besoin. Selon votre application, cette technique peut vous permettre d’économiser beaucoup de ressources. Cette technique, connue sous le nom de lazy load, peut être prise en charge par du code tissé avant chaque utilisation des données. Ce code vérifie alors si la donnée a déjà été chargée, et la charge si cela n’a pas déjà été fait. Dans le même esprit, du code tissé peut prendre en charge la gestion d’un cache de donnée, lui aussi destiné à optimiser les performances.

 

Planification de l’utilisation des ressources

Il est parfois possible d’améliorer les performances d’un programme en planifiant l’exécution des opérations coûteuses en ressource. Par exemple, avant une opération critique, il peut être judicieux de forcer une collecte du ramasse-miettes. On réduit ainsi les chances que cette opération soit perturber par une telle collecte.

 

Avec .NET, avant une opération qui d’une manière certaine va utiliser les ressources (code ou autre) d’un assemblage, il peut être utile de charger explicitement l’assemblage d’une manière non intrusive dans le domaine d’application courant, grâce à un aspect. On garde ainsi le contrôle sur le moment du chargement.

 

L’AOP est particulièrement adapté à la prise en charge de l’exécution de ces taches. Encore une fois, le principal bénéfice est de retirer du code de la logique métier ces aspects de la programmation.

 

Dans le même esprit, dans son excellent ouvrage [Essential .NET] Don Box montre comment utiliser le mécanisme de contexte de .NET Remoting pour modifier d’une manière transparente la priorité d’un thread avant l’exécution d’une méthode (et de la repositionner à sa valeur initiale après l’exécution bien sur). On a ainsi un moyen de communiquer au système d’exploitation la priorité d’une opération.

 

Détection de changements et mise à jour

Dans sa section 4.1, [Kiczales1] expose un aspect codé avec AspectJ qui permet de détecter et de signaler un changement d’état à l’aide d’un drapeau. L’utilisation de l’AOP est ici particulièrement bienvenue car le nombre de méthodes provoquant un changement peut être assez élevé.

 

Synchronisation des accès concurrents

La synchronisation des accès concurrents aux ressources d’une application est un aspect de la programmation qui se prête particulièrement bien aux principes de l’AOP. En effet, la gestion de la concurrence au moyen des diverses techniques proposées par les Framework envahit souvent la logique métier et est très compliquée à mettre au point et à maintenir.

 

Le Framework .NET propose l’attribut System.Runtime.Remoting.Contexts.SynchronizationAttribute qui peut s’appliquer à une classe qui dérive de ContextBoundObject. La présence de cet attribut garantit qu’une instance de cette classe ne sera pas accédée pas plus d’un thread à la fois. Cet attribut utilise en interne le mécanisme de .NET Remoting.

 

Notez que le Framework .NET présente un autre attribut ayant ce nom mais faisant partie d’une autre espace de noms. Cet attribut System.EntrepriseServices.Synchronization a la même finalité, mais il utilise le service d’entreprise COM+ de synchronisation. Cependant l’utilisation de l’attribut System.Runtime.Remoting.Contexts.Synchronization est préférable pour deux raisons : son utilisation est plus performante et ce mécanisme supporte les appels asynchrones, contrairement à la version COM+. Cependant le service d’entreprise COM+ de synchronisation est plus puissant dans un environnement distribué car il se base sur un concept de thread logique qui lui est propre (appelé causalité).

 

Dans sa section 4.2 [Kiczales1] expose l’implémentation du modèle de synchronisation producteur/consommateur à l’aide d’AspectJ.

 

Globalisation de l’application

Grâce aux notions d’assemblage satellite, de culture et de nom fort, le framework .NET permet une gestion cohérente de l’aspect globalisation d’une application. Cependant, l’utilisation de la classe System.Resources.ResourceManager peut vite devenir envahissante dans le code de la logique métier comme le montre l’exemple suivant :

 

using System;

using System.Resources;

 

class Politesse

{

       static void Bonjour()

       {

             ResourceManager RM = new ResourceManager("chaines.en-US",typeof(Politesse).Assembly);

             // Traduit Bonjour en anglais à l’aide d’une table de correspondance.        

             string s = RM.GetString("Bonjour");

             Console.WriteLine(s);

       }

}

 

Pour pallier ceci, une idée serait d’utiliser cette classe dans un intercepteur de messages ou dans du code greffé pour traduire les paramètres entrant et sortant d’une méthode. Pour ne pas traduire toutes les chaînes de caractères passées en paramètre, une idée serait de marquer les paramètres concernées avec un attribut .NET propriétaire. Cette pratique augmente la sémantique du code source, puisque souvent dans une application il y a bien deux types de chaînes de caractères : celles qui sont utilisées par l’application elle-même (chaîne de connexion à une base de donnée, nom des fichiers et des répertoires, arguments passés en ligne de commande…) et celles qui sont destinées à être visualisées par l’utilisateur à travers l’IHM (c’est en général celles-ci qui sont concernées par la globalisation).

 

Propagation de contexte

Une des problématiques qui revient souvent dans le développement logiciel, est d’avoir à maintenir un ensemble d’états (d’informations) sur une suite d’appel de méthode. Ces informations représentent un contexte d’exécution des méthodes. Il est notamment nécessaire d’avoir accès a ce contexte dans un milieu transactionnels (ce qu’on appel alors le contexte transactionnel). Le développeur a la possibilité de rajouter à chaque méthode concernée une référence vers un objet en paramètre (objet qui regroupe l’ensemble du contexte). Mais cette solution est assez lourde et relativement intrusive. La plupart des architectures à objets distribués modernes, tel que CORBA, supportent le concept de propagation implicite de contexte, qui nous permet de pas avoir à rajouter explicitement ce paramètre, et donne des moyens d’accès au contexte d’exécution. [Kiczales1] donne un exemple de passage implicite de contexte effectué de façon modulaire.

 

De même, la technologie .NET Remoting présente les propriétés de contexte pour maintenir des informations dans un contexte .NET. Cette technologie présente aussi les call context qui permettent de transmettre des informations d’un contexte à un autre durant un appel inter-contexte. Tout ceci est exposé dans [.NET et C#]. Notez cependant qu’un des problèmes de jeunesse de .NET est le fait que la propagation de contexte transactionnel ne peut se faire qu’avec la technologie COM+ utilisée conjointement avec DCOM. Ceci signifie que malgré ces possibilités séduisantes, .NET Remoting ne peut malheureusement pas propager de contexte transactionnel à l’heure actuelle (07/2003).

 

Persistance & Système Transactionnel

Un des thèmes récurrent de la programmation ces dernières années est le concept de persistance. De nombreux services de persistances sont proposés par les différentes plateformes de développement, que ce soit, JDO/EJB pour Java, ADO.NET/ObjectSpaces pour Microsoft, ou le service de persistance de CORBA. De nombreuses entreprises vendant des logiciels de base de données (Objet, relationnel ou Objet Relationnel) proposent  également leur propre service de persistance intégré à leur produit. Une habile couche d’abstraction peut, si elle est bien conçue, vous apporter une certaine indépendance vis à vis du système de persistance sous-jacent. Mais encore une fois, la programmation par aspect est une solution à envisager.

 

L’article [Soares] donne un exemple d’implémentation d’un système transactionnel minimum en utilisant AspectJ. Ce système minimum devrait tout d’abord définir les pointcuts qui identifient les méthodes transactionnelles. Egalement, un advice serait définit pour obtenir une instance du système de control transactionnel, et trois autres advices seront conçus pour démarrer une nouvelle transaction, la terminer avec succès ou l’avorter (abort). On utilisera bien sur l’advice créant la transaction avec le qualificatif ‘before()’, pour  s’assurer qu’une transaction est bien crée avant d’exécuter une méthode transactionnelle (terminologie d’AspectJ). De même, on utilisera ‘after() returning’ pour l’advice effectuant un commit, et ‘after() throwing’ pour l’advice devant effectuer un rollback. Il convient bien sur de s’accorder ici sur un certain protocole, car une méthode ne devrait pas renvoyer d’exception si elle ne veut pas que la transaction en cours subisse un rollback.

 

Six points spécifiques liés à la persistance et pouvant être modularisés en utilisant l’AOP, sont identifiés par [AWAIS03]:

·         la connexion et déconnexion à la base ou au système de persistance,

·         le stockage (insertion) et les mises à jour,

·         la récupération des données persistantes,

·         la destruction de données,

·         le système de transaction,

·         l’accès aux meta-données.

 

Le modèle propose l’utilisation d’une classe racine appelée PersistentRoot qui devra être héritée par toutes les classes persistantes. Un ensemble de pointcuts viennent, entre autres, intercepter les getters et les setters des instances des classes dérivant de PersistentRoot. La présence de cette classe racine, bien que bien pratique pour identifier les objets persistants, impose certaines contraintes au système, et la solution n’est pas complètement non intrusive. Un système de translation SQL est également proposé, sous la forme d’un ensemble d’aspects effectuant le mapping objets-relationnel, sous une forme réutilisable.

 

En se penchant de cette manière sur l’alliance entre l’AOP et les systèmes de persistance on pourrait naturellement se demander si l’on ne pourrait pas rassembler tous ces concepts et les intégrer directement au Système de Gestion de Base de Données (SGBD, Database Management System ou DBMS en anglais). Et bien cela semble être la direction de recherche entreprise par plusieurs universités et groupes de recherche qui travaillent actuellement sur ce qu’on appelle les Aspect Oriented Databases Management Systems (AODBMS). Même si aucun projet n’est vraiment complètement aboutit à notre connaissance, les projets de recherche actuels donnent une bonne idée sur les possibles orientations futures des bases de données les plus populaires (Oracle, SQL Server/Yukon, DB2, ou Postgres et MySql pour l’Open Source). Les AODBMS utilisent des aspects tel que le versioning, le clustering ou la sécurité pour leur propre implémentation. Une autre idée est d’utiliser ces AODBMS pour rendre certains aspects persistants, voir même pour créer un répertoire d’aspects, dans lequel le tisseur ira piocher soit de façon statique à la compilation, soit de façon dynamique à l’exécution. Ce répertoire sera bien sur accessible aux différents ayant droits dans le cadre d’un système d’aspects distribués. D’autres tel que [AWAIS02] donnent un exemple d’intégration entre un weaver (AspectJ) et une base de données objet (Jasmine).

 

Sécurité/Gestion de sessions

La sécurité fait partie des aspects de la programmation souvent mis en avant lorsque l’on cite les domaines d’application de l’AOP. En fait, sous le terme ‘sécurité’, on désigne à la fois l’authentification, l’autorisation, la gestion des sessions et la protection du code contre lui-même.

 

Rappelons que l’authentification est l’étape qui permet de vérifier si un utilisateur est bien celui qu’il prétend être alors que l’autorisation est l’étape qui alloue des permissions à un utilisateur. On utilise aussi souvent la notion de rôle pour simplifier l’étape d’autorisation. Un rôle est une catégorie d’utilisateur (administrateur, invité…). Le chapitre 10, ‘Authentification and Authorization’ de l’ouvrage ‘AspectJ in Action’ téléchargeable ([Laddad1]) expose comment prendre en charge ces aspects de la programmation avec AspectJ.

 

Précisons que sous .NET, la gestion de la sécurité au niveau utilisateur peut se faire de trois manières distinctes: soit directement avec le Framework, soit en utilisant des fonctions win32 pour utiliser la sécurité Windows, soit en exploitant un service d’entreprise COM+ spécialisé. Tout ceci est détaillé dans [.NET et C#].

 

Vous pouvez utiliser l’AOP pour implémenter la gestion dans le temps des sessions utilisateurs. Du code greffé peut avoir pour mission de vérifier la validité de la session du client appelant. Après validation, ce code greffé peut éventuellement charger des données concernant la session. En plus d’être flexible, l’aspect ‘gestion des sessions’ devient complètement transparent pour celui qui développe la logique métier de l’application. Si vous utilisez .NET Remoting, un identificateur peut éventuellement être passé à l’intercepteur de messages concerné, grâce au mécanisme de call context.

 

Un autre aspect de la sécurité consiste à protéger le code de lui-même (c’est d’ailleurs le créneau du futur palladium). Concrètement, en fonction des preuves que vous obtenez en analysant les caractéristiques de l’unité de déploiement à exécuter (un assemblage .NET par exemple) vous décidez d’allouer des permissions à l’exécution du code de l’unité de déploiement. Les preuves sont en général obtenues en analysant la provenance et la non-falsification de l’unité de déploiement (i.e la non-falsification entre le moment où elle a été créée par un compilateur, et le moment où elle est effectivement exécutée). Les permissions d’exécution sont de toutes sortes et l’on peut citer les permissions d’accéder en écriture et/ou en lecture à certains répertoires, la permission d’utiliser un SGBD, la permission d’utiliser le registre… Tout ceci est implémenté par le Framework .NET sous le nom de CAS (Code Accesss Security) et est expliqué en détail dans [.NET et C#] et dans [Essential .NET]. Le lien entre le mécanisme CAS et l’AOP est que le code requis pour demander ou contrôler les permissions que l’on a, envahit vite le code de la logique métier. Pour réduire cette intrusion sans toutefois complètement y remédier, le Framework .NET présente des attributs .NET qui peuvent s’appliquer sur les méthodes, les classes ou même sur l’assemblage entier. Un désavantage de cette pratique et l’impossibilité de rattraper les exceptions provoquer par l’échec de l’obtention d’une permission.

 

Configuration

Sous le terme de configuration, on entend à la fois la configuration d’une application et la configuration de l’AOP.

 

Sous .NET, la configuration d’une application se fait automatiquement par des fichiers d’extension .config au format XML portant les mêmes noms que les assemblages qu’ils configurent. Le chargement des informations standard contenues dans ces fichiers est prise en charge par le CLR. Vous devez cependant toujours écrire du code pour charger et exploiter vos paramètres de configuration propriétaires. Pour ne pas surcharger le code de votre logique métier avec ces taches vous pouvez utiliser du code greffé.

 

Signalons que COM+ a un service d’entreprise qui propose un mécanisme de configuration très rudimentaire qui permet de passer une chaîne de caractère à la construction d’un objet. Un avantage est que cette chaîne est configurable après compilation de vos classes. Un problème est que cette chaîne est la même quelle que soit l’instance de la classe que vous construisez. Ce service d’entreprise COM+ est souvent utilisé pour passer une chaîne de connexion localisant une base de données.

 

La section 3.4 de [Kiczales1] explique que AspectJ prévoit quelques facilités pour configurer la gestion des aspects greffés selon le type de compilation. En effet, lors du développement et des tests, on utilise souvent des aspects particuliers que l’on ne souhaite pas voir apparaître dans la version en production de l’application (debugging, assertion, log…).

 

Meilleures implémentations des design patterns GoF

Lorsque nous avons abordé l’article [Hanneman] qui traite de l’implémentation des design patterns Gof (Gang Of Four, les quatre auteurs de l’ouvrage [GoF] dont Erich Gamma avec son nom prédestiné) avec l’AOP, nous ne soupçonnions pas à quel point il allait se révéler intéressant. En effet, après tout l’AOP et les design patterns font partis des sujets ‘en vogue’ dans le monde de l’architecture logicielle et écrire un article mêlant ces deux sujets peut relever de la démagogie.

 

Il est dommage que l’idée centrale ne soit explicitement citée que dans la section 5.3 de l’article. Pour bien la saisir, il faut se rappeler que l’implémentation d’un design pattern consiste à rajouter une ou plusieurs responsabilités à une ou plusieurs classes. A partir de cette constatation, pour chaque implémentation d’un design pattern vous pouvez distinguer les classes qui ont des responsabilités en plus de celles rajoutées par le design pattern (l’article parle de responsabilités superimposed par le design pattern), des classes qui n’ont que pour responsabilités celles prévues par le design pattern (l’article parle de responsabilités defining car la classe est définit par sa responsabilité). L’idée maîtresse de l’article est que plus l’implémentation d’un design pattern implique de responsabilités superimposed, plus on a à gagner à modulariser ces responsabilités dans des aspects.

 

Ainsi sur les 23 design patterns GoF, l’article explique que 17 d’entre eux s’implémentent d’une manière plus satisfaisante en modularisant les responsabilités superimposed avec AspectJ. Le bénéfice d’une implémentation d’un design pattern est mesuré selon quatre critères expliqués dans la section 4.1.3 : La localité, la réutilisabilité, la transparence de la composition de plusieurs design patterns et l’unplugabilité (hum…). Les résultats sont exposés dans la table 1 de l’article.

 

L’article se termine sur plusieurs discutions concernant les rapports entre design patterns et langages, la classification des design patterns et les problèmes relatifs à la composition des design patterns au sein d’une application.

 

Utilisation d’objets distants

De nombreuses technologies permettent l’utilisation d’objets distants. On peut citer .NET Remoting, Java RMI, DCOM, CORBA (et les Web Services dans une certaine mesure). Précisons qu’un objet est dit distant d’un de ses clients, lorsque l’objet et le client sont dans deux processus différents (sur la même machine ou sur deux machines reliées par un réseau). La définition est un peu différente avec la technologie .NET puisque celle ci introduit un niveau d’isolation plus fin que le processus, appelé domaine d’application. Concrètement, un processus contient un ou plusieurs domaine d’application. L’isolation est garantie par le CLR et se fait au niveau de la sécurité, des types et de la gestion des exceptions. Ainsi, avec .NET, un objet et un de ses clients sont distants s’ils sont dans deux domaines d’application différents, dans le même processus ou non.

 

Ces techniques ont toutes ceci en commun qu’elles mettent en avant le fait que le code du client nécessaire pour utiliser un objet est le même que l’objet soit local ou distant. Chacune de ses technologies modularise certains aspect de la programmation qui consiste à utiliser un objet d’une manière distante (mais pas tous, par exemple il est très délicat de faire migrer un projet d’une technologie d’utilisation d’objets distants vers une autre).

 

Notez cependant qu’il vaut mieux parfois que le développeur d’un client soit conscient qu’un objet est utilisé d’une manière distante car on n’utilise pas un objet distant de la même façon qu’un objet local. En effet, chaque appel distant nécessite un allé/retour réseau (round trip en anglais) qui est extrêmement coûteux en comparaison d’un appel local (en général, plusieurs millions de fois plus lent). Il est ainsi nécessaire de limiter le nombre d’appels distants. Dans ce domaine de l’optimisation du nombre d’appels distants, l’AOP peut vous être utile comme nous avons vu dans la section optimisation des performances.

 

.NET Remoting va plus loin que les autres technologies, car elle vous permet de modulariser d’autres aspects de la programmation que l’utilisation d’objets distants, grâce à une infrastructure très élaborée d’interception d’appels à l’exécution (présentée dans [.NET et C#]). Cette infrastructure d’interception dynamique des appels est assez coûteuse en terme de performance et ceci représente un des principaux défauts de .NET Remoting vu en tant que tisseur d’aspects. Cependant, lorsque les aspects sont tissés lors d’appels où le client et l’objet ne sont pas dans le même processus, le coût de l’interception dynamique est négligeable en comparaison du coût de la traversée des frontières entre processus.

 

Dans sa section 4, [Soares] montre comment rendre l’aspect ‘utilisation distante d’un objet avec Java RMI’ complètement modulaire avec AspectJ. Dans cette étude, des aspects sont tissés à la fois du coté client et du coté serveur. Notamment, il est exposé comment un aspect du coté serveur peut fournir une interface distante RMI à la place d’une classe façade et comment traiter le problème de la sérialisation des paramètres. Du coté client, les auteurs expliquent qu’il est nécessaire d’implémenter un advice pour chaque méthode appelée d’une manière distante. Cette contrainte est très forte, aussi cet article propose une modification d’AspectJ pour pallier ce problème. Enfin, l’article explique comment utiliser Java RMI et AspectJ pour synchroniser l’état des objets distants avec leurs copies locales (à rapprocher de la synchronisation des états des objets à l’aide d’une couche de persistance).

 

Les trois aspects de la programmation souvent utilisés conjointement avec la manipulation d’objet distant sont l’encryption, la compression et le log des données transitant sur le réseau. [MSDN] expose comment implémenter ces trois aspects lors de l’utilisation de services web.

Conclusion & Remarques sur l’AOP

Cet article tente de donner un plus vaste aperçu des utilisations possibles de l’AOP. Bien que ne nous rentrons pas directement dans les détails techniques d’implémentation des aspects, nous avons référencé de nombreux autres papiers de qualité, qui pourront vous inspirer lors de la mise en pratique de ces idées.

 

Durant cette énumération nous nous sommes aperçus que l’on pouvait séparer les aspects de la programmation en deux catégories:

 

·         Les aspects d’aide aux développeurs, destinés à être utilisés seulement pendant les phases de développement/test (debugging, profiling etc.).

 

·         Les aspects architecturaux, utilisés par toutes les versions de l’application, y compris la version en production (design pattern, optimisation, sécurité, synchronisation, etc.).

 

L’AOP est encore relativement jeune comparée a l’OOP. Il reste selon nous de nombreuses zones d’ombres à éclaircir notamment en ce qui concerne la portabilité des aspects. Même si un effort de standardisation de l’AOP semble être en cours pour la plateforme Java, le temps où les aspects seront complètement portables d’un langage à un autre semble encore bien éloigné. Peut être faudrait il se pencher sur un sorte de meta approche de l’AOP en utilisant ce que l’on pourrait appeler des ‘Platform independent Aspects’, qui serait projeté façon automatique sur des ‘Platform dependant Aspects’, et de se rapprocher ainsi de la philosophie du Model Driven Architecture mise en avant par l’OMG.

 

Une autre remarque concerne l’implémentation d’un système supportant l’AOP: à quel niveau doit se faire le support ? Au niveau d’un pré-compilateur, au niveau du compilateur, au niveau de ‘la machine virtuelle / du run-time’ ? D’une combinaison de tout cela ? Aussi vous avez pu voir qu’un grand nombre d’universités et de groupes de développeurs travaillent actuellement sur l’implémentation d’un tisseur. On peut se demander si tous ces efforts ne seront pas vains lorsque les tous puissants Microsoft/Sun/IBM se décideront à mettre en pratiques leurs effort de recherche interne et d’intégrer complètement leurs systèmes d’AOP à leur produits .NET/Java (ce qui est d’ailleurs déjà le cas avec l’infrastructure des régions d’interception de .NET Remoting décrite dans [.NET et C#]).

 

A l’instar de Grady Booch dans [Booch] nous pensons que l’AOP fera certainement partie de l’évolution du monde du développement logiciel dans un future proche. Cet optimisme n’est cependant pas partagé par toute la communauté. Ainsi, Creg Andera affirme dans son blog [Andera] que l’AOP n’est pas une solution viable au problème des crosscutting-concerns à cause des effets de bord qu’elle induit. Autrement dit, sa thèse est que les aspects de la programmation ne sont intrinsèquement pas orthogonaux. Cet argument sera certainement à prendre en compte dans les futures évolutions de l’AOP. Nous signalons enfin l’article de Magnus Mickelsson [Mickelsson2] qui relate quelques commentaires intéressant sur le sujet.

 

Auteurs : Patrick Smacchia, Sébastien Vaucouleur.

Copyright © Juillet 2003

 

Ressources Livres

[.NET et C#]              Pratique de .NET et C# (O’Reilly 2003), Patrick Smacchia.

[Transact. COM+]      Transactional COM+ Building Scalable Applications (Addison Wesley 2001), Tim Ewald.

[Essential .NET]         Essential .NET Vol.I : The common language runtime (Addison Wesley 2002), Don Box.

[PEAA]                     Patterns of Enterprise Application Architecture (Addison Wesley 2002), Martin Fowler.

[GoF]                       Design Patterns, Elements of Reusable Object-Oriented Software (Addison Wesley 1994) Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides

 

Ressources Web

[Andera]            Réflexions sur le bien fondé de l’AOP , Blog de Craig Andera.

[AspectDNG1] Questions et réflexions, Thomas Gil.

[AspectDNG2] DotNetGuru Forum.

[AspectC#]     AspectC# Intro:Trinity College Dublin.

[AspectJ]        Le site Officiel d’AspectJ.

[AWAIS02]      Weaving Aspects in a Persistent Environment, Awais Rashid ACM SIGPLAN February 2002.

[AWAIS03]      Persistence as an Aspect, Awais Rashid, Ruzanna Ghitchyan, ACM AOSD 2003.

[Booch]             Grady Booch Discusses .NET and the Art of Software Development.

[Burke]              JBoss Aspect Oriented programming, Bill Burke.

[Cheng03]       AOP Tutorial, Cheng.

[DNG1]            La programmation par aspects (AOP) avec .NET et J2EE, Thomas Gil, DotNetGuru.

[DNG2]            L’instrumentation de code en .NET, Sébastien Bouchet, DotNetGuru.

[Gradecki03]    Chapitre 1 de Mastering AspectJ : Aspect-Oriented programming in Java (Wiley 2003) Joseph D. Gradecki, Nicholas Lesiecki.

[Hanneman]    Design pattern implementation in Java and AspectJ, Gregor Kickzales, Jan Hannemann.

[Herder]          Aspect Oriented Programming with AspectJ, Jorrit N. Herder.

[Kiczales1]      Getting started with AspectJ, Gregor Kiczales

[Kiczales2]      Les critères d’un bon design AOP par Gregor Kiczales.

[Kiczales3]        Using AspectJ for programming the detection and handling of exception Gregor Kiczales et Al.

[HyperJ]            Présentation de HyperJ de IBM.

[Laddad1]       Chapitres 6 et 10 d’ AspectJ in action, Ramnivas Laddad.

[Laddad2]       I want my AOP !, Part 1,2,3 Ramnivas Laddad.

[LAM]              CLAW, Aspect Oriented Programming and the CLR, Jhon Lam.

[Lieberherr1]   Why should you care about aspect-oriented, Karl Lieberherr.

[Lieberherr2]   Aspect-Oriented Software Design, Karl Lieberherr, Theo Skotiniotis.

[Mickelsson1]   AOP Compared to OOP when implementing a distributed Web based application, Magnus Mickelsson.

[Mickelsson2]   Comments on AOP, Magnus Mickelsson.

[MSDN]           Altering the SOAP Message Using SOAP Extensions.

[Prasad]          CAMEO, AOP support for C#, Devi Prasad, B.D Chaudhary.

[Shukla]             AOP Enables better code encapsulation and reuse Dharma Shaka, Simon Fell, Chris Sells (MSDN Magazine).

[Soares]          Implementing Distribution and Persistence Aspects with AspectJ, Sérgio Soares et Al. OOPSLA 2002.

[Talby]            Aspect-Oriented Programming, David Talby.

[Yang]            A brief introduction to Aspect-Oriented Programming Zhenxiao Yang, Gregor Kiczales.

 

Patrick Smacchia, email [patrick@smacchia.com]

Patrick Smacchia assure de nombreuses formations sur .NET, à la fois dans l’industrie et dans le milieu universitaire (Université de Nice). Passionné par l’architecture logicielle, il aide les entreprises à concevoir et à développer leurs applications. Ingénieur diplômé de l’ENSEEIHT, il a notamment collaboré avec Amadeus et avec les divisions espace et téléphonie mobile d’Alcatel. Son site expose plus en détail ses activités. Ses compétences ont été reconnues par Microsoft France, ce qui lui a valu la distinction MVP .NET (Most Valuable Professional sur les technologies .NET).

 

Sébastien Vaucouleur, email [sebastien at vaucouleur dot com]

Sébastien Vaucouleur, diplômé de l’ université d’ Heriot-Watt en Ecosse, a collaboré avec plusieurs centres de recherche, de développement et de formation, à la fois dans l’industrie au sein de large projets industriels (Bull/Steria, Fujitsu, Telenor etc.), notamment sur les problèmes de performance et de montée en charge de larges systèmes transactionnels à objets distribués, ainsi que dans le milieu universitaire (Université d’ Helsinki, Université de Nice). Ses intérêts de recherche actuels sont liés aux systèmes d’objets concurrents et distribués, object composition, generative programming, AOP, object persistance, virtual machine architecture et ORDBMS.

 

L’ouvrage Pratique de .NET et C# (O’Reilly 2003)

L’ ouvrage Pratique de .NET et C# (O’Reilly 2003) couvre la plupart des aspects du développement sous .NET avec le langage C# (architecture .NET sous jacente; langage C#, bibliothèques ADO.NET, XML, WinForm, GDI+, architectures distribuées avec COM+, .NET Remoting et ASP.NET etc.). Cet ouvrage contient de nombreux rappels pour le rendre accessible aux étudiants et aux débutants. Les développeurs confirmés pourront quant à eux rapidement exploiter les subtiles possibilités proposées par .NET, que sont par exemple la réflexion, la programmation orientée aspect ou le mécanisme d’attribut.