La programmation orientée aspect appliquée au Frontend - Partie Théorique
Cette suite d'articles explore les concepts de programmation orientée aspect (POA) à travers une première partie théorique présentant de cas d'usages et un support de réflexion sur la composition de vos futures "aspect", les avantages/inconvénients ainsi que les alternatives.
Puis un second article plus technique avec des exemples d'implémentation d'aspects basés sur les Proxy.
Et je finirai par un troisième article contenant des exemples plus avancé de POA basés sur la dernière révision des Decorators (Javascript).
En tant que développeur Front, je suis souvent confronté aux mêmes besoins techniques d'un projet à l'autre.
J'ai pensé à une façon d'améliorer techniquement nos stacks techniques qui pourrait vous être utile lors du design de vos futures architectures front.
Il y a certainement une marge de progression dans l'usage de ces concepts et j'espère qu'en partageant cela avec vous, grâce à vos commentaires, nous pourrons les approfondir.
TLDR;
Cette semaine la théorie est à l'honneur, je vous laisse volontairement avec des pistes de réflexion avant d'aborder des cas pratiques la semaine prochaine, avec des exemples sur les Proxy et les Decorators.
La Programmation Orientée Aspect (POA) est un outil impressionnant à utiliser avec modération et discernement. Je vous recommande toutefois de vous documenter sur les Proxy, la Reflect API et sur les Decorators.
Okay, Okay, voici le lien vers le projet d'exemple pour les plus curieux d'entre vous. (C'est une application à but didactique créée pour cet article; J'y fais volontairement, un usage abusif de POA et, évidemment, cette application pourrait être améliorée).
Qu'est-ce que la POA ?
Un ancien secret légué via tradition orale ?Un cheat code universel pour réveiller votre Mojo ?
Est-ce que vous aviez déjà entendu parler de Programmation Orientée Aspect ?
Historiquement, la POA est utilisée dans les applications backend. Mais de nos jours, que nous soyons en train de développer une µService ou un µFrontend, nous sommes confrontés aux mêmes problématiques techniques transverses et redondantes.
La POA est une façon de s'abstraire de cette répétition lorsque vous avez communément à gérer des logs applicatifs, du profiling ou encore la gestion des droits/permissions de vos utilisateurs.
La beauté de la programmation orientée aspect est de pouvoir être aussi bien associée à la programmation orientée objet, qu'à la programmation fonctionnelle, sans aucune d'incidence (si c'est bien fait).
Plus concrètement, la programmation orientée aspect vous permet d'encapsuler un objet en augmentant son comportement et ce, sans modifier son code d'origine.
Once is happenstance. Twice is coincidence. Three times is enemy action.
Les bénéfices de la programmation orientée aspect
- Séparation des préoccupations techniques et fonctionnelles
- Modulaire
- Réutilisable
- Testable
- Maintenable
Quelques cas d'usages courants
- Dépréciation : Prévenir l'usage d'une Class ou d'une fonction en notifiant à l'usage de celle-ci qu'elle sera supprimée dans une version ultérieure ;
- Profilage : Mesurer le temps d'exécution d'une fonctionnalité ;
- Logs : Enregistrer le contexte d'exécution d'une fonctionnalité afin d'offrir des traces fiables lors de débogage avec des preuves d'entrée / sortie ;
- Obfuscation : masquer les données sensibles ;
- Validation de schéma : Valider les données d'entrée et leur consistance fonctionnelle ;
- Memoïzation : Servir une réponse précédemment obtenue, stockée dans un cache local ;
- Synchronisation d'état : Synchroniser un état local avec un système tiers ;
- RBAC : (Role-based access control) Gérer des rôles / permissions utilisateurs ;
- Quota : Limiter la répétition d'une action pour une durée limitée dans le temps ou définitivement ;
- Métaprogrammation : Peut-être utilisée à des fins d'introspection, d'auto-modification, d'inter-médiation ou d'intercession.
Candidats Javascript
Comment faire ça ? Où les mettre en place en gardant une structure de code propre ?
En se basant sur le langage le plus utilisé pour les développements frontend (Javascript), quel élément du langage nous permet d'augmenter un objet ou une Class sans modifier son code d'origine ?
Les [Proxy] !
Un Proxy à la capacité d'intercepter et de redéfinir une opération sur une cible sans la modifier. Ainsi, l'instance d'un Proxy peut être utilisée à la place de sa cible. Ce qui correspond aux éléments recherchés et décrits précédemment : la méta-programmation.
Voici un exemple :
Nous avons ici deux villes qui communiquent via un pont. Disons que la ville de gauche cherche à récupérer un document officiel détenu par la ville de droite. Une voiture est alors envoyée pour récupérer ce document. Cette voiture pourrait être interceptée sur le chemin par l'un des check-points de contrôle.
Traduction, la ville de gauche représente une cible "proxifiée", la route et le pont représentent le moyen de communication que vous auriez normalement mis en place pour communiquer avec la cible; une voiture représente une interaction unique et les check-points les différents aspects que vous avez encapsulés par-dessus votre cible. L'attente potentielle à un check-point représente, quant à lui, le coût computationnel de votre aspect.
Alternative aux Proxy ? Les [Decorators]
Je vous recommande également de jeter un coup d'œil à la spécification technique des Decorators, ils pourraient également vous être utiles dans un avenir proche.
Les Decorators sont récemment passés à l'étape 3/4 dans leur processus de standardisation (JavaScript) et sont également à un état expérimental pour TypeScript. Mais vous pouvez d'ores et déjà les utiliser moyennant une petite configuration avec Babel.
C'est une autre façon d'encapsuler un aspect avec une fonction. Peut-être êtes-vous familier avec les termes "functional composition" ou "higher-order functions" ?
Proxy vs. Decorator
Les Proxy s'appliquent sur une instance en opposition avec les Decorators qui ont une "approche prototypale" (ajouté lors de la définition d'une Class).Donc, chaque instance de la class héritera des aspects que vous avez ajoutés alors que l'approche des Proxy offrent plus de flexibilité, puisque vous avez la main sur la création de l'instance "proxifiée" de votre cible. Ce qui vous paraît répétitif dans cette approche peut être surmonté avec un pattern de type "création".
Les Proxy offrent également plus de flexibilité, car ils sont applicables à une instance d'un Objet / Class / Tableau / Function , là où les Decorators s'appliquent uniquement à une Class ou aux membres d'une Class.
Le revers de la médaille pour cela, c'est qu'il faut également penser la conception d'un Proxy avec un niveau d'abstraction plus élevé, là où le Decorator est plus limité, il paraît beaucoup plus intuitif à l'usage.
Étant appliqué à une instance, le Proxy est également très intéressant lorsqu'il s'agit de modifier le comportement d'une librairie externe sans la "fork".
Le Proxy s'utilise en combinaison avec un gestionnaire ("trap handler") et la Reflect API. La réflexion étant une opération complexe, vous devez vous attendre à un compromis sur les performances (ce qui demande d'être discipliné pour ne pas l'utiliser à outrance).
When you have a hammer in your hands, everything looks like a nail…
Les Decorators peuvent être utilisés en Javascript et Typescript moyennant une modification de configuration de votre projet; Tandis que les Proxy sont standardisés pour Javascript; La question est plus épineuse pour Typescript à cause de l'inférence de type.
Dans les deux cas, les Proxy et les Decorators ajoutent une couche d'indirection ce qui peut réduire la lisibilité de votre code.
Pour un usage à grande échelle, la mise en place de la configuration et sa compréhension sont des étapes à ne pas négliger.
Et pourquoi pas ceux-ci ?
Les Mixins sont utilisés pour ajouter une propriété supplémentaire à la totalité des enfants d'un arbre. Enrichir un Objet en lui ajoutant une ou plusieurs propriétés est différent de son augmentation comportementale. À titre personnel, je considère les mixins plutôt comme un antipattern (Principalement à cause de la collision potentielle de propriété).
Même chose pour le pattern Provide/Inject qui a des similitudes avec les Mixins, il s'agit également d'ajout de propriétés.Dans la plupart des cas, vous devriez préférer la composition à l'injection.
Les [Pipelines] avec leur nouvel opérateur sont à l'étape 2 de révision par le TC39, c'est une feature qui est encore à l'étape de réflexion, mais également intéressante à analyser; les pipelines ne correspondent pas à notre cas d'usage, car ils forcent l'usage du résultat de l'opération précédente dans l'opération suivante. C'est une nouvelle feature qui est clairement orientée pour la programmation fonctionnelle.
Quelques questions à vous poser lorsque vous voulez encapsuler un aspect
- Quoi ? Quel est votre besoin initial ? Quel comportement souhaitez-vous ajouter à votre cible ?
- Où ? Est-ce que cet aspect est utile partout ? Est-ce qu'il s'applique à toutes les propriétés ou à un sous-ensemble ? S'il s'agit d'un sous-ensemble, comment allez-vous les discriminer par rapport aux autres ?
- Quand ? Quand est-ce que votre comportement doit être pris en compte ? "Avant" l'accès à une propriété/méthode ? "Après" lorsqu'une valeur est retournée ou une erreur est levée ?
- Qui ? Est-ce qu'il doit être applicable à une Class, un Objet, un Tableau, une Fonction ? Avez-vous besoin d'une stratégie défensive pour restreindre son usage à un type particulier ?
- Pure/Transparent ? Est-ce que le comportement que vous ajoutez modifie le comportement original ou la composition originale de votre cible ? (Plus spécifiquement pour les Objets, vous devez vérifier s'il est scellé, extensible ou gelé. Avant d'aller plus loin référez-vous à cette liste.)
- Relation de dépendance ? Est-ce que votre implémentation est autonome ou va-t-elle créer un lien de dépendance ? Est-ce que cela vaut le coup ? Ce n'est pas un interdit, mais vous devriez considérer la chose avec précautions. Auquel cas je vous recommande d'écrire et partager au sein de vos équipes un ADR (Architecture Decision Record).
- Toujours actif ? Est-ce que vous allez avoir besoin de désactiver ce comportement ? Ne serait-ce que partiellement ? Comment allez-vous modifier sa configuration ? Une variable d'environnement, un élément de configuration associé à votre "Feature Flipping" ou bien suite à la réception d'un évènement émit par le serveur (SSE) ? Est-ce que votre infrastructure est prête pour ça ?
- Ordonnancement ? Est-ce qu'il existe un ordre ou une dépendance logique dans l'accumulation de plusieurs "aspects" ? Vos différents aspects doivent être indépendants les uns des autres et ils ne doivent pas avoir "conscience" de la présence d'un autre aspect avant ou après eux. Cependant, il peut y avoir une logique à les ordonner d'une certaine façon.
Quelques exemples de réflexions complémentaires qui pourraient vous être utiles
- Est-ce judicieux d'ajouter un aspect de "profiling" au plus haut niveau ou au plus bas niveau d'une chaîne d'aspect ? Est-ce que vous souhaitez mesurer les performances de votre cible uniquement, ou les performances de votre chaîne d'aspect complète ?
- Lorsque vous utilisez les aspects de memoïzation ou de limitation par quotas, ça peut être pour réduire le nombre d'appels à votre backend et défendre/promouvoir des valeurs écologiques. Dans quelle mesure la mémoïzation s'intègre dans votre stratégie de mise en cache ? Tout élément de cache doit être clairement identifié, c'est probablement une décision à prendre en concertation avec vos équipes d'architectes.
- Lorsque vous avez besoin de log sur vos applications Frontend pour des besoins de débogage ou de statistique, vous pouvez avoir besoin d'obfusquer des données sensibles de vos utilisateurs tels que les numéros de carte de crédit ou numéro de sécurité sociale; avant de jeter ces informations sur vos différents transporteurs de logs;
- Les limitations par quotas et les limitations par rôles / permissions modifient le comportement original d'une cible en modifiant potentiellement sa valeur de retour par une levée d'erreur. Il en va de même si votre aspect transforme une action synchrone en action asynchrone, ou que le temps de traitement de cet aspect peut être long… Si c'est le cas pour votre aspect, veillez à correctement le documenter.
Conclusion
La programmation orientée aspect (POA) est un super outil, que vous devriez utiliser précautionneusement.
Pour cela, lisez les documentations, connaissez les limites, posez les bonnes questions et impliquez les personnes concernées, écrivez un ADR lorsque vous prenez des décisions structurantes, puis implémentez votre aspect.
Vous devriez explorer ceci vous-même; c'est un peu comme explorer un donjon, parfois vous mourrez et vous recommencez; l'expérimentation est aussi une forme d'apprentissage; que vous devriez partager autour de vous