← Voir tous les articlesJoris PISSONJoris PISSON - 14 févr. 2023

La programmation orientée aspect - Decorators

Cette suite d’articles explore les concepts de programmations orientées aspect (POA) à travers une présentation de cas d’usages.

Précédemment, nous avons abordé une première partie théorique, puis des exemples basés sur les Proxy. Aujourd’hui, mise au point sur les Decorators avec des exemples plus avancés.

La programmation orientée aspect appliqué au Frontend

TLDR;

La programmation orientée aspect est très utile pour séparer les problématiques techniques et fonctionnelles d’une application, éviter la duplication de code de façon élégante et enrichir le comportement de vos Class métier. En particulier avec les Decorators qui s’appliquent aux Class ou membres d’une Class de façon intuitive.

Voici le lien vers le projet d’exemple (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).

Quelques rappels sur les Decorators

Les Decorators sont récemment passés à l’étape 3 de validation par le TC39 et sont également expérimentaux sur Typescript.

Ce sont des fonctions relatives aux Class ou aux membres d’une Class (champs, méthodes, accesseurs)

  1. Ils peuvent remplacer la valeur qui est décorée par une valeur correspondante avec la même sémantique (par exemple, un décorateur peut remplacer une méthode par une autre méthode, un champ par un autre champ, une Class par une autre Class, et ainsi de suite).
  2. Ils peuvent fournir un accès à la valeur qui est décorée via des fonctions d’accès qu’ils peuvent ensuite choisir de partager.
  3. Ils peuvent initialiser la valeur qui est décorée, en exécutant du code supplémentaire après que la valeur ait été entièrement définie. Dans les cas où la valeur est un membre d’une Class, l’initialisation se produit une fois par instance.
  4. Les décorateurs sont appelés (en tant que fonctions) pendant la définition d‘une Class, après que les méthodes aient été évaluées, mais avant que le constructeur et le prototype aient été assemblés.
  5. L’évaluation se fait de gauche à droite, de haut en bas.

Limitation par quotas

Premier exemple de programmation orientée aspect basé sur les Decorators : qu’est-ce que la “limitation par quotas” ?

La limitation des quotas permet à un utilisateur de déclencher une action, un nombre limité de fois pour une période de temps (ou de façon permanente).

Il peut y avoir des limitations concurrentes comme :

  • 50 fois par minute
  • 500 fois par heure
  • 2000 fois par jour

Pourquoi la “limitation par quotas” ?

D’un point de vue technique, principalement pour éviter une sur-utilisation des API backend en limitant le trafic entrant ; d’un point de vue métier, ils peuvent servir à gérer des niveaux de compte utilisateur (Freemium/Premium) ou pour limiter la complexité de calcul (exemple : limiter le nombre d’éléments sélectionnés pour calculer un problème NP-complet).

Les limitations de quotas devraient être associées à une fonctionnalité ou, lorsque cela est possible, d’avoir une approche plus fine, une action d’une fonctionnalité.

Comment procéder à la mise en place de quotas ?

Tout d’abord, les éléments configuration à mettre en place qui décrivent une liste de quotas par action et par fonctionnalité :

Github code

Si vous êtes plus intéressé par la limitation par quotas, que par la programmation orientée aspect elle-même, je vous invite à jeter un coup d’œil au QuotaService ; j’ai utilisé l’inversion de contrôle avec un générateur pour synchroniser les évènements propres à une action (c’est le générateur qui maintient l’état quant aux différents compteurs pour les quotas). QuotaService est exposé en tant que Singleton et consomme également FeatureService qui se base sur une configuration ayant la même structure que celle présentée ci-dessus.

Dans l’article précédent, nous parlions des Proxy mais aujourd’hui on se jette à l’eau avec les Decorators, ce qui donne :

Github code

Le décorateur est encapsulé par une fonction afin de le rendre réutilisable et configurable (featureName). Un test (ligne 8; context.kind) prévient de son utilisation pour tout autre chose qu’une méthode de classe. Et ensuite, l’exécution de la méthode “augmentée” d’une classe, sera appelée si le résultat du générateur le permet.

Dernier exemple : RBAC

Le contrôle d’accès basé sur les rôles (RBAC — Role-based access control) fait référence à l’idée d’attribuer des autorisations aux utilisateurs en fonction de leur rôle au sein d’une organisation. Lorsque vous utilisez une solution de type RBAC, vous analysez les besoins de vos utilisateurs et les regroupez dans des rôles basés sur des responsabilités communes. Vous attribuez ensuite un ou plusieurs rôles à chaque utilisateur et une ou plusieurs autorisations à chaque rôle.

N.B : Vous devez toujours sécuriser vos appels API avec, à minima, un token (OAuth 2.0, OpenID, SAML, …).

En tant que développeur Frontend, vous avez deux considérations différentes à l’esprit en ce qui concerne RBAC :

  • Fonctionnel : Vérifiez si votre utilisateur est autorisé à faire quelque chose avant de déclencher un calcul fastidieux ou un appel à une API (petite pensée écologique !) ;
  • Présentation : Comment lier les permissions à votre couche de présentation (composants) ? Pour désactiver ou masquer des fonctionnalités (en fonction de votre orientation commerciale et du niveau de frustration que vous souhaitez créer chez vos utilisateurs).

Nous laissons les considérations de marketing et d’UX à leurs équipes respectives, mais notez que si quelqu’un vous demande d’implémenter des variantes d’un composant qui consomme/affiche les mêmes données et possède les mêmes fonctionnalités, il y a quelque chose qui ne va pas…

Le responsable produit (P.O.), en concertation avec le marketing, devrait donc nous donner les informations suivantes :

  • Si l’utilisateur a la permission ‘Zeta’ :
    • “Ceci” doit être visible et activé
  • Si l’utilisateur n’a pas la permission ‘Zeta’ :
    • “Ceci” doit être visible, mais désactivé
    • “Ceci” doit être caché

Gardez également à l’esprit que votre composant ne doit pas contenir directement de logiques métiers, ni récupérer des données. C’est à votre couche Service ou à votre gestionnaire d’état (Store) de le faire directement sur le frontend, sinon, cela peut être réalisé pendant la génération “statique” / “hybride-statique” (également connue sous le nom de “régénération incrémentale”) / “régénération côté serveur” si vous utilisez des technologies telles que Next / Nuxt.

Par défaut, cet aspect devrait dire “Non !”, puis faire usage de logique, comme un véritable ingénieur en cybersécurité. Voici donc une proposition de mise en œuvre également liée aux fonctionnalités.

Comme l’exemple précédent, voici une proposition de configuration qui permet d’associer des permissions à des actions pour chaque fonctionnalité.

Github code

Si vous êtes également plus intéressé par une solution de RBAC que par la programmation orientée aspect, jetez un coup d’œil au RbacService, mais notez que les permissions doivent être exposées en tant qu’objet “gelé” pour des questions de sécurité.

Maintenant, vos composants d’interface peuvent déclarer des propriétés réactives basées sur l’instance du service de RBAC en utilisant rbacService.hasPerms(perm) pour masquer/désactiver du contenu.

Github code

Mais lorsqu’il s’agit de gérer les permissions relatives à une action, disons qu’un utilisateur souhaite “ajouter une tâche à une liste de choses à faire”, vous pouvez ajouter votre aspect de RBAC autour d’une méthode de Class, sans modifier la méthode de la Class, qui conserve sa logique métier.

De façon très intuitive, le Decorator décrit les actions qu’un utilisateur peut effectuer (ajouter / supprimer).

Github code

Encore une fois, vous avez vu ici qu’il est plus aisé d’utiliser les Decorators lorsque vous implémentez vos propres Class; en comparaison avec les Proxy qui peuvent être plus délicats à mettre en place. Cependant, les Proxy ont également leurs points forts, il ne faut pas les négliger.

Conclusion

La programmation orientée aspect (POA) est un outil formidable ; il faut l’utiliser avec précaution et éviter une sur-utilisation.

Les Decorators quant à eux offrent une façon élégante de faire de la méta-programmation, d’améliorer et d’enrichir votre code sans ajouter de complexité à votre logique métier. C’est simple et lisible lorsqu’on consomme des Decorators, mais ils sont limités aux Class ou membres d’une Class. Si vous avez besoin de faire à peu près la même chose avec des objets, des tableaux ou même des fonctions, vous devriez jeter un coup d’œil aux Proxy.

Rappelez-vous, 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.