Go to Hackademy website

Architecture en trois tiers d'une application Vue.js

Tom Panier

Posté par Tom Panier dans les catégories front

Ça commence à faire un petit moment que je vous bassine avec Vue.js, et que je vous fais construire des single-page applications en s’appuyant dessus. Néanmoins, nous n’avons jamais réellement parlé de comment architecturer une telle application : comment organiser son code de façon logique ? Comment minimiser le couplage entre ses différents aspects ? Comment gagner à tous les coups à la bataille corse ? La majorité de ces questions trouveront leur réponse ci-dessous !

Arrière-analogie

S’il est un effet de mode facilement vérifiable, c’est l’empressement de nombre de développeur·euse·s à vouloir employer, en parlant de front-end, une terminologie directement issue de l’univers du back-end : pensez par exemple aux controllers de Backbone ou Ember, ou au « V dans MVC » de React. Si la pertinence de telles analogies s’est, à mon sens, avérée discutable dans la plupart des cas, nier la possibilité de tout parallèle (en termes de principe comme de vocabulaire) entre les deux mondes serait probablement tout aussi présomptueux. Tout ça pour dire qu’ici, on va parler de Vue.js à travers le prisme de l’architecture dite « en trois tiers », modèle qui définit, pour une application donnée, les trois subdivisions suivantes :

  • la couche de présentation (dans MVC, ce serait la vue)
  • la couche de traitement (le contrôleur)
  • la couche d’accès aux données (le modèle)

Pour chacune de ces couches, je vous propose donc un rapide descriptif théorique, suivi de son équivalent dans l’univers de Vue.js ; et vous allez vite vous rendre compte que ça fonctionne plutôt bien !

D’abord, on (se) présente

Commençons donc par la couche de présentation : comme son nom l’indique, cette dernière permet de présenter les données à l’utilisateur·rice. Une meilleure façon de le dire serait qu’elle sert d’interface à ces données… puisqu’il s’agit, tout simplement, de notre UI !

Dans le contexte d’une application Vue.js, cette couche correspond à nos composants : le rôle essentiel de ceux-ci est effectivement de mettre en forme leurs données, que celles-ci soient locales, héritées de leur parent, ou fournies par une solution tierce telle que Vuex. Ils offrent également à l’utilisateur·rice la possibilité de déclencher des actions via des boutons et autres event handlers, mais n’ont aucune connaissance sur leurs conséquences concrètes : en somme, ils jouent le rôle de passe-plat entre l’utilisateur·rice et l’application.

La couche de présentation a également pour rôle de mettre en forme la donnée à destination des humains : traduction de contenus ou encore formatage de date s’y dérouleront donc.

Il est tout à fait possible, et même fréquent, notamment en début de développement, de donner aux composants Vue une responsabilité débordant de ce cadre. En effet, ils sont aussi le point d’entrée de la codebase pour l’équipe de développement, en plus d’être celui de l’application pour les utilisateur·rice·s : on a généralement tendance à s’appuyer exclusivement sur les données locales de notre arbre de composants, et à y adjoindre sous forme de méthodes ou de propriétés calculées divers traitements et autres conditions nécessaires à l’implémentation du besoin. Toutefois, à mesure que l’application grossit, on commence généralement à factoriser le tout, et à extraire de plus en plus de code des composants Vue vers la seconde couche… que, excellente transition, nous allons découvrir maintenant !

Ensuite, on traite

La couche de traitement, comme son nom l’indique, là encore, plus ou moins, est dédiée au traitement des données. Derrière ce terme assez passe-partout se cache la notion essentielle de logique applicative.

La logique applicative représente le cœur du programme, sa raison d’être : en effet, toute application a pour seul et unique but de rendre un service à son utilisateur·rice. La logique applicative est tout simplement la traduction de ce service en code, qui est donc logiquement le code le plus important de l’ensemble.

Nous avons parlé de tests unitaires dans un précédent article ; sachez que dans l’immense majorité des cas, le code de la logique applicative (et donc de la couche traitement) sera non seulement le plus simple à tester (s’agissant généralement de fonctions pures, c’est-à-dire des fonctions qui donneront toujours le même résultat avec les mêmes paramètres au départ), mais aussi celui pour lequel l’existence de ces tests sera la plus pertinente, compte tenu de son rôle central dans l’ensemble.

La couche de traitement est utilisée par la couche de présentation (laquelle y récupère ses données et y transmet les desiderata de l’utilisateur·rice), et utilise elle-même la dernière couche (décidément, que de transitions de haut vol dans cet article !), faisant ainsi la glu entre les deux.

Enfin, on accède (cette blague filée n’a aucun sens)

La troisième et dernière couche de notre architecture est donc la couche d’accès aux données, et porte probablement le nom le plus explicite des trois. Dans une application back-end, elle serait typiquement dédiée à l’utilisation d’un ORM dédié à l’interaction avec la base de données, pour y lire les données existantes et y écrire les changements décidés par la couche de traitement en fonction des actions menées par l’utilisateur via la couche de présentation (vous suivez ?).

Dans une application front-end, et en particulier dans notre cas avec Vue.js, c’est quasiment la même chose, à ceci près que c’est avec le back-end, et plus particulièrement avec son API que nous interagissons généralement dans cette optique. Notre couche d’accès consistera donc principalement en un « client d’API » s’appuyant sur fetch ou une de ses surcouches, par exemple Axios.

Vous noterez que j’ai dit « principalement », et pour cause : cette couche doit également, le cas échéant (et c’est souvent), gérer la conversion des données entre le format exposé par l’API et celui utilisé en interne par notre code JavaScript, que ce soit du premier vers le second (« normalisation ») ou l’inverse (« sérialisation »). Ceci permet d’obtenir un format de données homogène et décorrélé de celui de l’API universellement, puisque quoi qu’il arrive, nous ne discuterons avec l’API à aucun autre endroit. Cela facilite notamment l’absorption des changements pouvant survenir en back-end.

Comme indiqué plus haut, la couche d’accès n’est utilisée que depuis la couche traitement : celle-ci y récupère les données « brutes » (quoique mises aux normes de notre application), pouvant ensuite s’appuyer dessus pour réaliser le métier de l’application et mettant le résultat à disposition de la couche présentation, qui n’a plus qu’à l’afficher à nos utilisateur·rice·s tout en leur offrant le moyen de requérir sa modification. Le cas échéant, une telle requête est traitée par la couche… traitement (vous voyez que c’est logique !), laquelle soumettra cette fois-ci son résultat à la couche d’accès afin que cette dernière le persiste auprès de l’API.

Et en pratique ?

Je me doute qu’une présentation aussi abstraite de ces concepts peut paraître un peu futile de prime abord ; je vous propose donc de l’illustrer rapidement en reprenant l’exemple de la Memebox que nous avons construite ensemble. Voici un rappel de l’arborescence (je passe sur les éléments triviaux) :

src
├─┬ components
│ └── Meme.vue
├── copyToClipboard.js
└── getEmbedURL.js
  • Meme.vue est un composant représentant un meme dans notre collection. Il reçoit en entrée (props) l’URL du meme en question, et donne en résultat un div en contenant un aperçu (image ou embed YouTube). Il constitue l’essentiel de notre couche présentation.
  • copyToClipboard.js est une fonction utilitaire prenant en paramètre une chaîne de caractères, la plaçant dans le presse-papiers de l’utilisateur·rice. Il s’agit essentiellement d’un utilitaire, isolé dans un fichier afin de rendre le code plus propre et de faciliter sa réutilisation, mais attention : malgré le fait qu’il s’agisse, par-dessus le marché, de code dédié à l’usage du navigateur web, il s’agit bien de logique applicative (copier un lien dans le presse-papiers au clic résumant en gros le service rendu par cette dernière), qui fait donc partie de sa couche traitement.
  • getEmbedURL.js est une fonction construisant une URL d’embed YouTube à partir d’une URL classique de ce même site, supportant les différents formats existants. Fonction pure ? Abstraction de code directement lié au service rendu par l’application et aucunement à son apparence ? Nous sommes de nouveau face à de la logique applicative, se situant donc, là encore, dans notre couche traitement (et étant très facilement testable unitairement, au passage).

Mine de rien, on ne s’en était pas trop mal sortis pour une première fois ! Notre application ne comporte pas de couche d’accès aux données, ses seules données étant situées dans un fichier local. Ce serait néanmoins une autre histoire si nous lisions notre liste de memes depuis une source tierce :

src
├─┬ components
│ └── Meme.vue
├─┬ data
│ ├── getMemes.js
│ └── normalizeMeme.js
├── copyToClipboard.js
└── getEmbedURL.js

Dans cet exemple hypothétique, notre application se voit enrichie d’un client d’API (getMemes.js), ainsi que d’un normalizer destiné au formatage de la donnée reçue depuis le serveur. En imaginant que ce dernier nous renvoie un tableau d’objets, au lieu du tableau d’URLs dont nous avons besoin, voyons à quoi ils pourraient respectivement ressembler :

import normalizeMeme from "./normalizeMeme";

/**
 * @param {String} url
 *
 * @return {Promise}
 */
export default function getMemes(url) {
  return fetch(url)
    .then(response => response.json())
    .then(memes => memes.map(normalizeMeme));
}
/**
 * @param {Object} meme
 * @param {String} meme.url
 *
 * @return {String}
 */
export default function normalizeMeme(meme) {
  return meme.url;
}

En termes d’arborescence, vous pouvez constater que les couches de présentation et d’accès se voient chacune dotée d’un répertoire dédié (respectivement components et data). Personnellement, dans la vraie vie, si je conserve toujours le répertoire components (créé par le CLI du framework), je laisse en général tout le reste directement dans src, la taille du code ne justifiant pas d’expliciter ces différentes couches. Je préfère typiquement rassembler mes normalizers et serializers dans des dossiers éponymes, par exemple, s’agissant d’un des rares cas où je peux me retrouver avec beaucoup de fichiers ayant un point commun concret — et encore, ces deux dossiers et mon client d’API formeraient exactement le contenu du répertoire data si je le créais. Je pense qu’il n’y a ici pas de règle absolue, le plus important étant que vous et votre équipe ayez en tête cette notion de couches dans vos choix techniques et architecturaux. N’hésitez pas à la faire refléter par votre arborescence si c’est préférable dans votre cas !

Done

J’espère que cette rapide introduction à l’architecture front-end vous aura inspiré, si possible, des réflexions plus profondes qu’auparavant sur la construction de vos applications Vue.js. Une fois encore, nous n’avons fait que gratter la surface, et il est loin d’être exclu que je revisite ce type de sujet à l’avenir ; si cela vous intéresse, faites-vous entendre dans les commentaires !


L’équipe Synbioz.
Libres d’être ensemble.

Articles connexes

Jeu de la vie et rendus de la mort

09/09/2019

Bonjour à tous, aujourd’hui je vous propose de revoir un classique du monde du développement, le jeu de la vie. Automate cellulaire plus qu’un vrai jeu, c’est avant tout un algorithme qui va nous...

Houdini, CSS by JS

21/03/2019

Bonjour à tous, bienvenue dans le monde magique de l’illusion et des faux-semblants, où un background peut souvent en cacher un autre. Que le rideau se lève le temps d’apercevoir ce qui se cache...

Une bibliothèque pour gérer l'authentification avec Vue.js, partie 2 — en route vers HTTP

08/02/2019

À l’heure où j’écris ces lignes, il m’est difficile de prévoir le temps qu’il fera quand vous les lirez. Laissons donc cette fois-ci les considérations météorologiques de côté et replongeons-nous...

Dark mode et CSS

24/01/2019

Bonjour à tous, aujourd’hui un sujet (presque) d’actualité puisque nous allons parler du mode sombre de MacOS, mais surtout d’une nouvelle manière — assez radicale — de penser nos interfaces. Le...