Comment utiliser efficacement les design patterns dans votre développement frontend. À travers l'exemple du facade pattern, nous verrons une solution pratique pour gérer la complexité de vos applications, centraliser la logique et améliorer la maintenabilité.
Dans le monde du développement logiciel, lorsque nous parlons d'architecture de code, il nous est facile de trouver d'innombrables ressources sur internet discutant d'architecture backend. Les architectures frontend, elles, sont bien moins plébiscitées. Et pour cause, car en réalité, la majeure partie de notre logique métier se trouve effectivement du côté serveur de notre application. La partie client, elle, n'est censée représenter qu'une interface belle et intuitive auprès de l'utilisateur.
Cependant, le fait que la logique métier soit souvent moins présente côté client ne signifie pas que l'architecture de cette partie de l'application puisse être négligée. Au contraire, le frontend recèle souvent une complexité sous-estimée, incluant notamment des logiques d'affichage, de gestion des interactions ou même des fragments de logique métier. En tant que développeurs, il nous est essentiel de maintenir une réflexion critique sur cette architecture, afin d'assurer la qualité de notre application et, par conséquent, une meilleure expérience pour nos utilisateurs.
Petit disclaimer : il n'existe pas d'architecture parfaite. C'est un point très important que je me dois d'énoncer dès lors que cet article a pour but de promouvoir un (pas le) pattern d'architecture. Chaque situation est différente, amenant par essence des problématiques et des solutions qui y répondent différentes.
Cependant, nous pourrions noter quelques points sur lesquels, à force d'itérations, la communauté s'accorde à définir comme importants lorsque l'on parle d'architecture :
À noter que dans la suite de cet article, je donnerai certains exemples de code utilisant le framework Angular. Mais la plupart des concepts et exemples sont très aisément reproductibles sur d'autres environnements/langages.
Dans une application Angular classique amenée à évoluer constamment, il nous est très aisé de tomber dans le piège d'avoir des composants trop chargés, qui gèrent à la fois les appels API, la gestion de l'état des variables, la logique métier en plus de certains aspects de l'interface utilisateur. Cette surcharge rend les composants difficiles à lire, à maintenir et à tester.
S'ensuit un exemple pour illustrer ce propos :
loadTasks
et changeTaskStatus
), la gestion de l'état et des erreurs (tasks
, completedTasks
, loading
, error
) ainsi que la logique métier (le filtrage des tâches et la mise à jour de leur statut).applyFilter
et updateTaskLists
). Cette répétition rend le code non seulement long et redondant, mais cela le rend également propice à différents défauts si la mise à jour à un endroit est oubliée dans un autre !filterControl
pour vérifier que le filtrage fonctionnetasks
, filteredUncompletedTasks
, etc.).Ce niveau de complexité rend les tests plus longs à écrire et plus fragiles car ils dépendent de nombreux détails techniques.
⭐ À noter que j'ai volontairement choisi un exemple surchargé pour démontrer l'importance (dans certains cas) de découpler nos composants et de séparer les différentes responsabilités de notre application.
Le facade pattern est une approche qui permet de simplifier la gestion de la complexité dans une application frontend. Il agit comme une interface centrale entre les composants, la logique métier et les différents services nécessaires au développement de nos fonctionnalités, offrant un moyen efficace de centraliser l'état, d'interagir avec des services externes (APIs) et de découpler les composants de l'implémentation technique sous-jacente.
La façade sert de point d'entrée unique pour la gestion des données et des états. Les composants, souvent surchargés dans une architecture négligée, délèguent la logique métier à une façade et se concentrent sur leur rôle principal : la présentation de la donnée et les interactions avec l'utilisateur.
Le composant MyTaskPage est beaucoup plus simple. Tout la logique, qu'il s'agisse de gérer les tâches ou d'intéragir avec l'API, est cachée dans la façade.
Grâce à la façade, le composant se limite à deux responsabilités principales :
tasksService.filteredUncompletedTasks
).tasksService.changeTaskStatus
).Une façade regroupe toutes les opérations concernant :
TasksState
).Ici la façade :
TasksApiClientService
completedTasks
et filteredUncompletedTasks
).Un des grands avantages du façade pattern est le découplage entre l’interface utilisateur et la logique métier. Le composant n’a pas besoin de savoir d’où viennent les données, ni comment elles sont filtrées ou mises à jour. Ce découplage présente plusieurs bénéfices :
⭐ Ici, le composant ne s’occupe ni de charger les tâches ni de filtrer les données. Il délègue tout cela à la façade, réduisant considérablement la complexité et le couplage.
🔍 Créer une façade pour chaque service : Cela peut entraîner une sur-ingénierie et une augmentation de la complexité inutile. Le rôle d’une façade est de centraliser la logique complexe et de servir de point de coordination pour des composants impliquant plusieurs aspects (par exemple, un état global ou plusieurs appels API). Si notre service est simple (par exemple, il se contente de fournir une liste statique), ajouter une façade ne fait qu’alourdir inutilement l’architecture.
✅ Solution : Appliquer le façade pattern uniquement dans les endroits stratégiques, là où la logique métier ou la gestion de l’état deviennent complexes.
🔍 Créer des façades trop génériques et sans focus : Une façade trop générique devient un goulot d’étranglement et un point unique de défaillance. En centralisant trop de responsabilités dans une seule classe, nous risquons de recréer le problème que l’on essayait de résoudre : un code complexe, difficile à tester et à maintenir.
✅ Solution : Divisez les façades par “feature” ou domaine fonctionnel. Dans notre exemple :
Cela permet une séparation claire des responsabilités et améliore la lisibilité et la maintenabilité du code.
🔍 Manquer de granularité dans les selectors : Ne pas exposer de selectors dérivés dans la façade oblige les composants à manipuler eux-mêmes les données.
✅ Solution : Fournir des selectors spécifiques dans la façade pour répondre aux besoins du composant. Dans notre exemple, nous fournissons les selectors filteredUncompletedTasks, uncompletedTasks, etc.
🔍 Confusion entre façade et logique métier complexe : Ajouter dans la façade l’ensemble de la logique métier peut la rendre compliquée à maintenir.
✅ Solution : La logique métier complexe devrait rester dans des services métiers dédiés. La façade ne fait que coordonner l’accès à ces services.
💡 Remarque : Vous vous dites peut-être qu’avec l’exemple que je vous ai donné, je ne respecte pas cette “règle” car une partie de ma logique métier est présente dans la façade (TasksService). J’ai fait le choix ici de ne pas externaliser la logique métier dans un autre service car je la considère comme assez simple pour ne pas complexifier encore davantage la lecture de la fonctionnalité.
La remarque ci-dessus est, pour moi, quelque chose que l’on doit toujours garder en tête. Il existe de nombreuses manières d’architecturer notre code, certaines plus complexes que d’autres et répondant à des besoins spécifiques. Cependant, nous devons toujours rester pragmatiques en nous demandant si, oui ou non, nous avons réellement besoin d’introduire telle ou telle complexité au sein de notre produit.
En réalité, je fais un abus de langage lorsque je présente ce pattern comme de l’architecture. La réelle notion d’architecture réside dans la décision d’implémenter (ou non) ce pattern. C’est une affaire de choix.
💬 Architecture is about the important stuff. Whatever that is.
Martin Fowler -
La définition et le design d’un produit répondent à certaines problématiques qui viennent avec leurs propres contraintes. Ainsi, si ce modèle mental/pattern ne convient pas à nos besoins (les choses importantes), il n’est absolument pas impératif de le mettre en place. Mais je tenais quand même à vous le présenter : ça vous fait un nouvel outil dans votre boîte à outils 😉.