Architecture Technique vs Architecture Fonctionnelle

L'architecture technique organise le code autour des composants du framework, tandis que l'architecture par fonctionnalités l'organise autour des domaines métier. Pour les projets à long terme, découvrez quelle approche favoriser.

Architecture Technique vs Architecture Fonctionnelle
Photo by Daniel McCullough / Unsplash

Que ce soit dans les tutoriels, les vidéos YouTube ou les exemples de codes, il est très souvent question d'utiliser une architecture technique, où le code est organisé autour des composants qui font partie du framework.

Mais il est possible d'utiliser une architecture un peu moins répandue qui organise le code autour des fonctionnalités, plutôt qu'autour d'une architecture technique.

Je vous propose de plonger dans cet article pour déterminer quels sont ces deux types d'architecture, quelles sont les différences et ce que je préconise pour vos projets qui ont une portée à long terme.

Architecture Technique

Une architecture technique est une organisation de code qui permet à un ou plusieurs développeurs de pouvoir créer et ranger les fichiers selon l'architecture d'un projet, que ce soit par type de fichiers (HTML, CSS, JavaScript) ou par composants techniques du framework (composants visuels, hooks, composables, utilitaires, etc...).

C'est clairement l'architecture qui est la plus répandue dans les tutoriels et les logiciels car elle permet de créer des exemples simples, sans logique complexe métier, et permet de faire comprendre certains concepts du framework simplement.

Voici un exemple de ce type d'architecture que l'on peut rencontrer lorsqu'on travaille avec le framework React.js.

.
├── package.json
├── src
│   ├── components
│   │   ├── Authenticated.tsx
│   │   ├── Layout.tsx
│   │   ├── LoginForm.tsx
│   │   ├── Plans.tsx
│   │   └── Plan.tsx
│   ├── contexts
│   │   ├── AuthenticationContext.ts
│   │   └── ThemeContext.ts
│   ├── hooks
│   │   ├── useAuthentication.ts
│   │   └── useTheme.ts
│   ├── index.html
│   ├── index.ts
│   ├── pages
│   │   ├── LoginPage.tsx
│   │   └── ProfilePage.tsx
│   ├── providers
│   │   ├── AuthenticationProvider.tsx
│   │   └── ThemeProvider.tsx
│   └── utils
│       ├── createTheme.ts
│       ├── fetchAuthentication.ts
│       └── fetchPlans.ts
├── tsconfig.json
└── vite.config.ts

J'ai pris comme exemple une application React.js qui définit un ensemble de pages, comme une page de connexion et une page de profil.

Ces différentes pages ont besoin de composants, par exemple la page de connexion va appeler le composant LoginForm, mais ce dernier ne sera pas appelé dans la page de profil bien entendu, et inversement : la page de profil va afficher les tiers payants disponibles via les composants Plans et Plan, qui ne seront pas nécessaires pour la page de connexion.

Cette architecture a l'avantage de pouvoir très facilement organiser ses fichiers : plutôt que d'avoir tout au même endroit, et créer une charge cognitive supplémentaire lors de la recherche d'éléments techniques (par exemple, rechercher un composant), on sait tout de suite où chercher en fonction du dossier en question.

C'est une architecture qui fonctionne très bien lorsqu'on commence à travailler sur des applications qui n'ont pas une durée de vie de développement longue voire très longue, où il est financièrement plus simple de se concentrer sur la partie technique que fonctionnelle dès le début.

Typiquement, pour les applications qui ne sont que de la pure présentation d'une logique métier fortement isolée du côté serveur, cela peut faire beaucoup de sens de partir sur ce genre d'architecture qui peut sembler initialement très simple et rapide à mettre en place.

Le principal inconvénient à cette architecture, et qu'il est difficile de raisonner avec celle-ci en contexte métier isolés.

En effet, il faut savoir d'entrée de jeu que les composants Plan et Plans sont associés à la page de connexion ici seulement. Si l'on souhaite par exemple partir sur un service externe qui permet de gérer l'ensemble de la partie authentification et paiement des tiers payants, il sera pénible pour nous de devoir isoler chacun des fichiers qui en sont dépendants.

De plus, lorsqu'on est amené à changer de contexte souvent (entre une API et plusieurs frontend), chercher de l'information devient tout de suite plus pénible, même si cela n'est clairement pas impossible.

C'est le cas si notre application grandit en complexité, il est alors plus difficile dans une équipe métier d'attribuer un espace isolé pour pouvoir travailler sur une fonctionnalité supplémentaire, par exemple la création d'un espace d'administration, on pourrait facilement se tromper et modifier un fichier qui n'est pas sous la responsabilité de notre service ou d'un contexte métier en dehors de notre champ d'action.

Architecture fonctionnelle

C'est là qu'intervient l'architecture fonctionnelle. Elle permet d'organiser son code, non pas en parties techniques, mais en parties cohérentes vis-à-vis de nos fonctionnalités.

Voici un exemple.

.
├── package.json
├── src
│   ├── features
│   │   ├── authentication
│   │   │   ├── components
│   │   │   │   └── Authenticated.tsx
│   │   │   ├── contexts
│   │   │   │   └── AuthenticationContext.ts
│   │   │   ├── hooks
│   │   │   │   └── useAuthentication.ts
│   │   │   ├── providers
│   │   │   │   └── AuthenticationProvider.tsx
│   │   │   └── utils
│   │   │       └── fetchAuthentication.ts
│   │   ├── layout
│   │   │   └── components
│   │   │       └── Layout.tsx
│   │   ├── login
│   │   │   ├── components
│   │   │   │   └── LoginForm.tsx
│   │   │   └── pages
│   │   │       └── LoginPage.tsx
│   │   ├── profile
│   │   │   ├── components
│   │   │   │   ├── Plans.tsx
│   │   │   │   └── Plan.tsx
│   │   │   ├── pages
│   │   │   │   └── ProfilePage.tsx
│   │   │   └── utils
│   │   │       └── fetchPlans.ts
│   │   └── theme
│   │       ├── contexts
│   │       │   └── ThemeContext.ts
│   │       ├── hooks
│   │       │   └── useTheme.ts
│   │       ├── providers
│   │       │   └── ThemeProvider.tsx
│   │       └── utils
│   │           └── createTheme.ts
│   ├── index.html
│   └── index.ts
├── tsconfig.json
└── vite.config.ts

La première chose qui saute aux yeux, c'est qu'il y a évidemment plus de dossiers, et de niveaux de dossier.

Comme vous pouvez le constater, les dossiers qui ont été rajoutés sont les dossiers qui correspondent à nos fonctionnalités : pouvoir se connecter, pouvoir consulter son profil, mais aussi les fonctionnalités liées au thème et à l'authentification.

Certains fichiers ont été déplacés, comme par exemple les composants Plan et Plans qui sont désormais dans le dossier src/features/profile/components par exemple, ou encore le Provider lié à l'authentification qui est désormais dans le dossier src/features/authentication/providers.

Cette architecture est plus simple lorsqu'on a besoin de changer de contexte souvent. Par exemple, nous travaillons sur une fonctionnalité liée au profil de l'utilisateur, et un de nos collègues nous pose une question sur la partie thème. On sait tout de suite sans avoir besoin de lister visuellement tous les fichiers et dossiers que la réponse va se situer dans le dossier src/features/theme à priori.

Mais c'est aussi une architecture qui est plus simple à isoler pour nos équipes : si nous souhaitons faire travailler un freelance externe sur l'intégration d'un panel d'administration pour gérer les utilisateurs, a priori il n'aura besoin de rajouter des fichiers que dans le dossier src/features/administration/users/pages.

Néanmoins, l'inconvénient est que ce n'est pas une architecture qui est normalisée : de nombreux projets commençent, et finissent d'ailleurs souvent par adopter pleinement une architecture technique, car c'est celle qui est enseignée en formation, sur internet, ce qui est utilisé dans les tutoriels sur YouTube, et elle demande un temps d'adaptation pour pouvoir sortir de cet automatisme.

Et surtout, elle demande une certaine discipline, car il est très facile d'être tenté de commencer par une architecture fonctionnelle, et de vite retomber dans les vieux travers et de créer rapidement du code dans un dossier technique, ce qui serait bien évidemment dommage d'un point de vue architectural.

Architecture Technique ou Fonctionnelle

On peut conclure en disant que le choix d'une architecture est fondamental car elle intervient dès le début d'un projet et pose des bases saines pour le développement d'une base de code future.

Il est bien entendu possible de migrer d'une architecture à une autre, spécialement si le code n'est créé que pour de la présentation et qu'il contient finalement peu de métier à ce stade du développement.

Néanmoins, le coût architectural et de développement de devoir migrer une grande base de code n'est pas à négliger, bien qu'il soit possible d'utiliser à bon escient un LLM pour cela, c'est une tâche qui nécessitera de valider les heures allouées sur le projet, sans lien direct avec une fonctionnalité métier à développer, et donc qui peut être complexe à faire valider par une direction technique par exemple.

Le choix reste donc crucial dès le début d'un projet de choisir la bonne architecture.

Pour ma part, j'essaie de toujours partir sur une architecture fonctionnelle depuis que j'en ai découvert ses bénéfices : lien plus évident avec le métier et les fonctionnalités, facilité d'activer/désactiver un ensemble de fonctionnalité (en déplaçant le dossier dans un dossier d'archive et en lançant un linter à la recherche d'éventuelles erreurs de typage), et sa scalabilité puisque rajouter une fonctionnalité ne rajoute pas beaucoup de charge cognitive à la compréhension de la base de code existante et tout y est logé de manière logique.

Le code qui a été présenté a été conçu sur le framework React.js, bien entendu, il est possible d'appliquer les mêmes concepts sur Vue.js et même sans framework.

Angular est un excellent framework pour se forcer à utiliser une architecture fonctionnelle dès le départ puisqu'il incite, grâce à sa ligne de commande et son organisation par défaut de fichier, à adopter une architecture proche d'une architecture fonctionnelle dès le début. Et bien qu'il lui a été reproché pendant de nombreuses années d'être un framework verbeux, c'est souvent la meilleure option pour des projets à moyen voire long terme.

Comme le dit l'adage : « on construit toujours une maison sur des bases saines ». Le code ne fait pas exception.