Executive summary : il y a plusieurs façons de faire de l’AOP en .NET, et Unity n’est pas nécessairement celle la plus mise en avant dans cette session, qui fait une démonstration plus longue sur PostSharp. Les tisseurs peuvent être de type statique (modification de l’IL généré, que ce soit avant, pendant ou après la compilation) ou dynamique (modification du comportement de l’application à la runtime). PostSharp est une solution du premier type. Un aspect est composé d’un greffon, soit la partie qui réalise le travail, et des points d’attache, c’est-à-dire les endroits du code où on vient appliquer le code de l’aspect.
Cette session est présentée par Romain Verdier de FastConnect, et une autre personne dont je n’ai pas eu le temps de noter le nom. Heureusement, le premier commentaire sur ce blog est de Romain, et il me précise qu’il s’agit de Yann Schwartz. Merci à lui et désolé à Yann de ne pas avoir été assez rapide pour noter son nom dès le début.
On commence par une introduction sur l’abstraction croissante des langages informatiques : on est parti du langage machine pour abstraire vers du structurel, du procédural, de l’objet, bref de plus en plus modulaire et de haut niveau.
Concepts de l’AOP
Le principe de Separation Of Concerns se dérive en principe de Single Responsibility (une classe fait une chose et une seule). C’est le cheval de bataille de l’AOP : une classe doit s’occuper d’un aspect du code, et d’un seul. Si on pollue une classe avec des traces, du transactionnel, etc., ça diminue la lisibilité et la facilité de maintenance.
L’AOP est très pratique pour ce qui est traces de code. C’est même le cas standard d’exemple, car c’est du code qui pollue le métier, donc c’est très intéressant de l’extraire. Mais il y a aussi la nécessité de gérer la sécurité qui est une bonne candidate à l’AOP. De manière générale, tous les besoins transversaux sont concernés.
Typiquement, sur une fonction métier avec une gestion transactionnelle et des traces, on se retrouve avec une méthode potentiellement longue où le fonctionnel ne représente finalement qu’une portion légère du code, et surtout mélangé dans tout le reste.
Le speaker insiste sur le fait que l’AOP ne va pas à l’encontre de l’OOP (Programmation Orientée Objet) mais plutôt la complémente si on trouve des cas transversaux qui s’applique.
L’aspect est composé d’un Advice (greffon, qui contient la partie fonctionnelle, bref ce que l’aspect est censé réaliser), et de pointcuts (points d’action, soit les endroits où l’aspect doit être injecté, et qui ne sont pas n’importe quel endroit du code, mais seuls ceux bien identifiés par le langage).
Mise en pratique
On reprend bien sûr le bon vieil exemple des traces. C’est sûr, ce n’est pas le plus original, mais ça a le mérite de bien exposer le problème. Une classe Log est appelée à chaque début de méthode. C’est gênant non seulement parce qu’on pollue le code, mais aussi parce qu’il y a risque de les oublier. L’advice, dans ce cas, est le fait de tracer le nom de la méthode, et le pointcut utilisé le début de toute méthode.
Une fois ces deux caractéristiques définies, il manque un moyen de mettre tout ceci en fonction : c’est le tisseur, qui permet d’injection les greffons aux points d’action définis. Il existe de nombreux tisseurs pour .NET, et l’inconnue principale de cette session, pour moi, était de savoir si Microsoft allait supporter un tisseur particulier, ou proposer le sien.
Certains tisseurs sont basés sur un véritable langage, d’autres sur des décorateurs de code (lorsque le langage supporte l’introspection), d’autres encore sur des fichiers tiers (XML, typiquement) ou des DSL (Domain Specific Language, avec des fichiers externes utilisant un mini-langage de définition). Quasiment tous les frameworks en .NET utilisent une modélisation objet des aspects dans le même langage qu’ils ciblent. C’est intéressant car un développeur C# continue à utiliser son langage de prédilection, même si cela peut parfois induire un peu de confusion, car on se retrouve avec un code qui n’est pas directement exécuté.
Le tissage peut être dynamique (exécuté lors de la runtime) ou statique (en précompilation, en compilation, et le plus souvent en post-compilation, avec des tisseurs qui interviennent directement sur le code IL), voire du tissage hybride (c’est souvent un choix de points d’entrée qui, lui, est statique, puis l’accroche de code sur ces hooks qui, elle, est statique).
On passe à la démo
Le framework de Microsoft pour l’AOP est Unity. Mais avant ça, il existait un moyen standard de faire de l’appel distant d’objet, et c’est le remoting. Ce concept est de moins en moins utilisé (on a plus tendance à faire des applications multitiers avec des services web, par exemple), mais il peut servir pour mettre en place de l’AOP. Par contre, il faut du coup dériver de MarshallByRefObject. Ensuite, on pose toutes les fonctions sur lesquelles on veut passer dans l’objet Remote, de façon qu’il implémente la même interface que l’objet d’origine, mais en ajoutant ce que l’aspect doit réaliser en plus du code métier. C’est la méthode du pauvre, mais qui peut être intéressante pour des besoins simples.
Une deuxième philosophie consiste à créer dynamiquement une classe qui hérite de la cible tout en surchargeant les fonctions avec les aspects entremêlés. Par exemple, Castle utilise cette méthode. Unity également, mais ce dernier framework peut également utiliser la précédente. Le tisseur se charge de générer automatiquement un proxy vers cette nouvelle classe. Au passage, il est intéressant de constater que ce genre d’appel est finalement très proche du principe d’appel des mocks et des stubs pour les tests : on génère dynamiquement des objets avec un comportement déduit de manière logique. La limite de cette approche est que la fonction ciblée doit être virtuelle.
Bref, on a le choix entre deux méthodes avec chacune sa limitation. C’est pourquoi Unity propose les deux méthodes, en fonction de ce qu’on souhaite réaliser. Il y a d’ailleurs un autre problème pour l’instant : on n’a plus la possibilité d’appeler directement la construction de l’instance, et il faut passer par une factory. Du coup, on retrouve des frameworks d’IOC (Inversion Of Control) dans les candidats à l’AOP. Un container Unity est typiquement utilisé pour faire de l’inversion de dépendances, et du coup, de l’AOP. La configuration peut être faite par code, ou par des fichiers XML (Spring.NET, par exemple).
Et pour un vrai exemple
Le but de la fin de la session est de faire voir, après les exemples simples, un cas un peu plus industriel. C’est le problème de toutes les sessions, surtout qu’elles ne durent qu’une heure : comment faire voir les concepts, mais pas avec des exemples trop simplistes ?
Romain Verdier prend la main pour présenter PostSharp et le tissage statique. Cet outil est dédié à l’AOP, contrairement à Castle Windsor ou Unity qui sont des frameworks d’inversion de dépendances, de manière plus large. Le tissage statique est moins souple, mais peut aller beaucoup plus loin que le tissage à la runtime, car il peut aller n’importe où dans le code.
C’est parti pour l’exemple : une grille bindée sur une BindingList<Instrument>, où Instrument est un POCO, qui doit du coup implémenter INotifyPropertyChanged, ce qui force à ajouter un évènement qui sera déclenché à chaque fois qu’une propriété est modifiée. On crée une fonction OnPropertyChanged qui vérifie que l’event est bien associé à des listeners (en testant la nullité) et en l’appelant en passant le nom de la propriété qui a changé. Du coup, on se retrouve avec autant d’appels que de propriétés, ce qui pollue le code. De plus, l’association à la chaîne de propriété modifiée étant manuelle, on n’est pas à l’abri d’une erreur de recopie, ou d’un oubli de mise en place de l’évènement lors de l’ajout d’une nouvelle propriété.
Remarque : dès que j’ai une connexion internet, je télécharge PostSharp et je vous poste les exemples de code si j’arrive à les retrouver. Sinon, ils seront certainement sur le site de Microsoft. De manière générale, je n’ai pas les moyens sur ces compte-rendus de faire des captures de code ou d’écran.
L’idée est de créer un aspect, qui est un attribut dans PostSharp. On place l’attribut, ici NotifyPropertyChanged, sur la classe ciblée. On n’est pas obligé de faire directement sur la classe : on peut instruire PostSharp pour qu’il applique ceci à toutes les classes correspondant à un filtre donné. Cet attribut hérite de InstanceLevelAspect, qui vient de l’API PostSharp. On le décore avec un attribut [IntroduceInterface(typeof(INotifyPropertyChanged)] et [Serializable]. L’aspect lui-même implémente l’interface INotifyPropertyChanged à la place de la classe cible. On crée le même helper de levée d’évènement, sauf que le sender n’est pas this, mais this.Instance, qui est l’objet tissé. L’event est décoré avec [IntroduceMember]. Un autre décorateur est [OnLocationSetValueAdvice, MulticastPoints(Targets = …)] qui est placé sur OnSetValue, qui contient l’implémentation de l’aspect.
On voit bien dans le code généré le code ajouté par PostSharp : ça alourdit un peu l’IL, mais on est également plus rapide que si on était dynamique.
Un deuxième exemple, plus complexe, est lancé, mais il est un peu dommage de passer du temps dessus, car la session est en retard, et du coup, l’explication est un peu expédiée.
Je me rends compte que c’est difficile à lire sans exemple de code, mais vu que je ne peux pas télécharger PostSharp, ça n’est pas possible pour l’instant. Je vais quand même voir si je peux choper un wifi public quelque part…
[Edit]
Je reprends les commentaires manuellement, suite à la migration de mon blog sur cette nouvelle plateforme.
Romain Verdier :
Et bien, quel compte rendu précis ! Merci.
L’autre personne, c’est le fabuleux Yann Schwartz (http://blog.polom.com/)
En ce qui concerne les exemples de PostSharp, il y en a pas mal dans le répertoire “samples” de l’installation. Suffit de s’inscrire sur postsharp.org pour télécharger les bits de la version 2.0.