Aller au contenu
Grégory BresolinMe contacter
Tous les projets

Isi-APP

SaaS multi-modules pour piloter un système d'information — parc informatique, ticketing, projets — avec un espace prestataire multi-clients.

Année
2019 — aujourd’hui
Cadre
Lead developer, ISI-APP
Statut
en cours
Stack
Laravel, Livewire, Alpine.js, Bootstrap, MySQL

Voir le projet en ligne →

Contexte

Isi-APP a démarré en 2019, à deux. Un ami lançait sa société, je l’ai suivi en quittant douze ans de freelance.

Il n’y avait pas rien : une première ébauche existait sous Laravel 5, et une partie du cœur fonctionnait déjà. Mais elle était écrite en dur, hors des conventions du framework — le genre de code qui tient tant qu’une seule personne le connaît. Remettre cet existant d’aplomb a été le premier chantier, avant même de pouvoir construire dessus.

Le constat de départ n’a pas changé depuis : il manquait un outil pour piloter et simplifier un système d’information. C’est la promesse que porte le logiciel — la boîte à outils française pour piloter et simplifier votre SI — et le mot française n’est pas un argument commercial ajouté après coup : la souveraineté était dans le cahier des charges dès la première ligne, et elle contraint les choix techniques.

Sept ans plus tard, l’équipe a grandi et le produit compte huit modules — de la gestion des identités et du parc informatique au ticketing et à la gestion de projet, jusqu’au décisionnel et à des modules dédiés à des métiers particuliers. Plutôt que d’empiler des outils qui ne se parlent pas, tout vit dans la même base et le même référentiel.

Module de ticketing en vue Kanban Le ticketing, en vue Kanban. Les mêmes données s’affichent aussi en tableau, avec tri, filtres et export.

Contraintes

Deux difficultés structurent le projet, et ce sont elles qui déterminent l’essentiel des choix techniques.

Maintenir plusieurs modules dans un même produit. Chaque module a son métier, son vocabulaire et son rythme d’évolution, mais tous partagent le même référentiel d’entités. Faire évoluer l’un sans casser les autres suppose des frontières nettes et un socle commun réellement commun — pas une collection d’applications déguisée en produit unique.

L’espace prestataire. Un prestataire gère plusieurs clients, et doit les piloter depuis une seule interface sans jamais voir ce qui ne le regarde pas. C’est la contrainte la plus exigeante du produit : elle impose une gestion des droits rigoureuse sur chaque accès, à la croisée de trois dimensions — l’utilisateur, le client sur lequel il intervient, et le module concerné.

Sur ce type de cloisonnement, il n’existe pas de demi-réussite : une seule fuite entre deux clients et c’est la confiance dans le produit qui tombe.

Espace prestataire listant les clients et leurs droits L’espace prestataire : chaque ligne est un client, avec le niveau de délégation accordé et les modules ouverts. C’est ce croisement — utilisateur, client, module — qui doit être vérifié à chaque requête.

Décisions d’architecture

Le cloisonnement ne pouvait pas être implicite

Base mutualisée, et filtrage tenant écrit explicitement, requête par requête. Deux approches plus séduisantes sur le papier ont été écartées.

Une base par client. Écartée pour son coût opérationnel : près de quatre cents modèles à migrer autant de fois qu’il y a de clients, et surtout des besoins transverses — pilotage, facturation, support, connecteurs — qui seraient tous devenus des agrégations entre bases. Beaucoup de complexité pour aucune contrepartie à notre échelle.

Un scope global appliqué automatiquement à chaque requête. Écartée pour une raison plus intéressante, et c’est l’arbitrage central du produit : le tenant courant n’est pas une valeur unique. Selon l’écran, la portée pertinente est l’entité seule, une filiale, une entité et toutes ses filles, ou la relation entre un prestataire et le client sur lequel il intervient. Un scope unique aurait dû être désactivé sur une grande partie des requêtes — c’est-à-dire la pire configuration possible : une garantie affichée mais pas tenue, avec la fausse sécurité qui va avec.

Le choix a donc été de rendre le filtrage visible sur chaque requête, plutôt que de le rendre invisible et contournable partout.

Ce que ça coûte, sans enrobage : la garantie devient conventionnelle plutôt que structurelle. Elle repose sur la convention, la revue et les tests — pas sur le langage. Et une convention n’est jamais appliquée uniformément : la concentration du filtrage dans une couche dédiée est la cible vers laquelle tend le code récent, pas une description de l’ensemble. Un socle qui appliquerait le cloisonnement par défaut, avec une sortie explicite et traçable pour les accès transverses légitimes, offrirait la même souplesse avec un défaut sûr. C’est la direction dans laquelle le produit évolue.

Le découplage est porté par la donnée, pas par le code

Dans ce produit, « module » a deux sens qu’il faut séparer.

Le module commercial est une ligne en base : le périmètre d’abonnement d’un client. L’activer ne demande aucun déploiement, et les accès sont vérifiés au niveau des routes.

Le module de code est une convention d’organisation : routes, composants et services regroupés par domaine.

Le découplage réel est donc porté par la donnée, pas par le code — et pour un SaaS où chaque client a un périmètre différent, c’est le bon endroit. Le catalogue est éditable à chaud, sans build conditionnel ni artefact par client.

Module portail affichant des actualités Un autre module, un autre métier : le portail de communication interne. Même socle, même référentiel d’entités — seule la ligne d’abonnement change.

Le revers est net : aucune frontière n’est vérifiée par un outil. Rien n’empêche techniquement un domaine d’aller chercher dans un autre, et ce couplage se contrôle en revue, donc imparfaitement. Passer à de vrais packages n’aurait de sens que pour réutiliser un domaine hors de cette application, ou pour donner à des équipes distinctes des cycles de release séparés. Ce n’est pas la situation : la surcouche coûterait plus qu’elle ne rapporterait.

Livewire plutôt qu’une SPA

Ce n’était pas le point de départ. En 2019, on a continué avec ce qu’on maîtrisait et ce qui était déjà là : jQuery. Livewire l’a remplacé progressivement, écran par écran, à mesure que le produit grossissait — et la migration n’est pas terminée.

Aujourd’hui : près de huit cents composants Livewire, plus de deux mille vues, et aucun framework front.

Ce que ça a fait gagner. Le gain principal découle directement du point précédent : l’interface étant rendue côté serveur, le modèle de droits et le filtrage s’appliquent là où ils sont écrits, sans avoir à être réexposés route par route. Une SPA aurait imposé de porter tout ce modèle dans une API et de le resécuriser sur chaque endpoint — exactement la surface où une fuite entre clients serait apparue. Sur un produit dont la difficulté centrale est le cloisonnement, ce n’est pas un détail.

S’y ajoutent une seule compétence à staffer au lieu de deux, pas de contrat d’API à maintenir en double, et une vraie vitesse sur les écrans de gestion : un socle de tables génériques produit tri, filtres, export et actions de masse en quelques fichiers. Sur une application dont l’enjeu est le nombre d’écrans, c’est décisif.

Ce que ça a coûté. Chaque interaction est un aller-retour réseau : sur les écrans denses, cela se voit, et il faut arbitrer en permanence entre réactivité et volume d’échanges. L’état vit côté serveur, ce qui demande une discipline quotidienne plutôt qu’un réglage. Le JavaScript revient malgré tout dès qu’il faut de l’interactivité fine, mais dispersé dans les vues au lieu d’être structuré. Les tests d’interface passent par des tests de bout en bout, plus lents et plus fragiles que des tests de composants. Et il n’existe pas d’API générale réutilisable : le jour où une application mobile deviendra nécessaire, l’essentiel restera à construire.

Le vrai coût, cela dit, n’est pas Livewire : c’est le double paradigme. Le Livewire d’aujourd’hui cohabite encore avec le jQuery des débuts, et c’est cette hétérogénéité — pas le framework — qui pèse le plus au quotidien. C’est le prix d’un produit qu’on fait vivre plutôt qu’on ne réécrit : on migre là où ça compte, on laisse le reste tant qu’il tient.

Le verdict. Pour un ERP B2B multi-clients où le facteur limitant est le volume d’écrans et la complexité du modèle de droits, et non la fluidité de l’animation, le compromis est le bon. Une SPA aurait exigé une équipe front dédiée et une API complète en préalable. Ce qu’on paie en échange est réel et identifiable, ce qui est la seule chose qu’on demande à un arbitrage.

Arbitrages

Trois choix qu’on assume, et ce qu’ils coûtent :

  • La sécurité du cloisonnement est conventionnelle, portée par la convention et la revue plutôt que par le langage. En échange, chaque requête dit ce qu’elle filtre, au lieu de dépendre d’un mécanisme invisible qu’on aurait passé son temps à désactiver. La contrepartie est réelle : une règle qui n’est pas outillée n’est appliquée uniformément que là où le code a été repris.
  • Aucune frontière technique entre domaines. Le couplage se contrôle en revue. On y gagne un déploiement unique et zéro surcouche de versionnage.
  • Pas de SPA, donc pas d’API générale exposant le modèle métier à l’interface. On y gagne un modèle de droits appliqué côté serveur sans être réexposé route par route ; on y perd une API réutilisable, qui reste largement à construire. La règle souffre une exception héritée : les écrans les plus anciens s’alimentent encore par des endpoints JSON dédiés — une des raisons pour lesquelles ils sont progressivement remplacés.

Résultat

Sept ans après le premier commit, la plateforme est en production et continue d’évoluer : un peu plus de 21 000 commits, une quinzaine de contributeurs, huit modules produit sur un socle commun.

En volume : environ 460 000 lignes de code applicatif, 250 000 lignes de vues, 374 modèles, 358 tables, et 800 fichiers de tests dont 117 parcours utilisateur de bout en bout.

Mon rôle

Je suis lead developer sur la plateforme : j’encadre l’équipe, je relis le code, et je porte les décisions techniques du produit.

Sept ans sur la même plateforme, c’est une position particulière — on ne livre pas puis on s’en va, on vit avec ce qu’on a construit et on le reprend quand il a vieilli.