Comprenez les pièges de l’architecture monolithique
Que se passe-t-il, sur le long terme, lorsqu'on ne prend jamais en compte l’architecture d’un logiciel pour le modifier ? Le résultat ressemble un peu à ceci :
Comment peut-on se retrouver avec une telle pagaille ? Traditionnellement, les applications sont construites pour remplir un seul objectif.
Exemple : une application de calculatrice semble être simple à créer : elle doit faire des calculs puis afficher le résultat.
On serait tenté pour cela de créer une application monolithique dans laquelle le code pour l’accès aux données, la logique business, et l’interface graphique seraient tous regroupés en une seule couche interconnectée.
Pourquoi est-ce une mauvaise chose ?
Pensez à un millefeuille. Il y a des couches de crème pâtissière et de pâte feuilletée, surmontées d’autres couches de crème pâtissière et de pâte feuilletée. Chaque couche contribue à rendre l’ensemble délicieux. Mais que se passe-t-il si on met le tout dans un mixeur ? On mélange toutes les saveurs, et les textures. Les couches disparaissent : impossible de faire la différence entre la crème pâtissière et la pâte feuilletée ! 🤢 Et il faudra un bon café pour faire passer tout ça…
Maintenant, revenons à notre exemple d’application de calculatrice. Nous avons tout codé en une application monolithique. Mais imaginez que nos clients demandent qu'on ajoute de nouvelles fonctionnalités ? Disons que nous voulons fournir une API REST (une interface d’application qui peut être appelée par n’importe qui, de n’importe où). Nous voulons sauvegarder les valeurs saisies, les opérations demandées, et les résultats, pour que nous puissions analyser ces informations plus tard (ou en temps réel). Nous voulons ajouter de nouvelles opérations pour traiter des calculs métier ou scientifiques, ce qui nécessite davantage de boutons sur l’interface.
Nous pourrions ajouter des éléments sur les parties existantes en fonction des besoins. Mais alors, nous obtiendrons une application difficile à modifier (souvenez-vous des prises sur l’image ci-dessus). La logique métier est enfouie profondément quelque part dans le code, ce qui signifie qu’il est difficile de prédire ce qui pourrait se casser lorsqu’on apporte un changement.
Je suis certaine que vous savez qu’il ne faut pas coder de cette manière.
Pourtant nombreux sont ceux qui tombent dans le piège ! Autour de nous, il existent beaucoup de systèmes hérités, qui ont évolué petit à petit jusqu’à atteindre cet état. Personne ne commence un projet avec l’intention de créer un casse-tête. Pour autant, c'est le type de problème que peu de développeurs prennent le temps de réparer au fur et à mesure.
Mais alors, comment faire pour "réparer" une telle application ?
C’est exactement ce que je vais vous montrer dans ce cours ! Nous allons prendre une application monolithique, analyser ses problèmes, la refactoriser en composants séparés, et faciliter la communication entre différentes couches.
Plongeons-nous dans un exemple précis ! Nous allons travailler dessus ensemble tout au long du cours.
Cas d’usage : Réparez une application difficile à modifier et non scalable
Le cas d’usage suivant sera utilisé pour illustrer différentes étapes techniques dans ce cours.
Air Business, une petite compagnie aérienne charter, vous a demandé votre aide pour leur application. Leur application actuelle est capable de gérer la planification de vols spécialisés, et est exécutée sur un ordinateur à l’aéroport où est basée la compagnie. Une personne ouvre l’application et effectue toute la planification nécessaire. Jusqu’ici, tout va bien. C’est ce dont la compagnie aérienne avait besoin au moment où elle a pris son envol (hum, hum !). Néanmoins, cela ne répond plus aux besoins du client, qui évoluent.
Plus précisément, la compagnie aérienne a un nouveau client potentiel. Un tour opérateur voudrait utiliser leurs services régulièrement pour transporter par avion des voyageurs vers des destinations de rêve. Actuellement, la compagnie a uniquement acheminé des particuliers ou de petits groupes vers des réunions professionnelles. Cette nouvelle opportunité est très intéressante pour leur croissance !
Malheureusement, l’application actuelle a un problème : plusieurs aspects de l'application ne peuvent pas être adaptés, ils ne sont pas "scalables". C’est-à-dire que si le système doit répondre à davantage d’exigences, il ralentira — ou ne fonctionnera plus du tout. Voyons certaines de ces limites :
Actuellement, il n’est accessible que par une personne. Or il devra être accessible par d’autres agents de la compagnie dans d’autres aéroports. De plus, les agents de réservation du tour opérateur auront besoin d’un accès. À l’avenir, il serait bénéfique que des particuliers puissent eux aussi réserver des vols charters.
La base de données ne supporte pas la connectivité simultanée pour le requêtage et la mise à jour.
La logique business pour trouver les combinaisons d’avions et de pilotes disponibles est enfouie dans du code difficile à trouver, et encore plus à modifier.
Maintenant, regardons les rouages en détail. L’application est écrite en Java. Les seules classes sont :
Client - représente la personne qui a acheté le voyage charter
Pilote (Pilot) - une personne qualifiée pour piloter un avion
Avion (Plane) - un véhicule qui emmène des passagers d’un endroit à un autre
Réservation (Reservation) - Détails concernant un voyage
L’architecture actuelle est également constituée d’une poignée de classes Java Swing pour le rendu, et d’une base de données SQLite pour stocker des informations concernant les pilotes, les avions, les réservations, et les clients. La gestion des événements et la logique business sont enfouies dans Java.
Comme vous pouvez le constater, il y a de nombreuses connexions entre les classes : une situation propice au changement pour aboutir à une architecture web découplée !
Super… mais qu’est-ce que c’est, au juste ?
"Découplé" signifie que les différentes parties ne connaissent pas beaucoup de détails sur les autres parties d’un système. Elles communiquent à travers des interfaces bien définies. Par exemple, lorsque vous conduisez une voiture, vous tournez le volant (c’est une interface). Il est relié aux roues. Vous n’avez pas besoin de connaître tous les détails sur les paliers et les connexions pour faire prendre un virage à la voiture.
Quels sont les avantages du découplage ?
Une architecture découplée permet à tous les composants d’opérer de manière indépendante. Si on modifie l’implémentation d'un composant, cela n’impacte aucun des autres qui en dépendent. Autrement dit, l’exécution du code dans une couche peut changer sans affecter les autres couches !
Alors… comment améliorer notre architecture ?
Pour concevoir l’architecture d’une meilleure solution (découplée), nous pouvons appliquer le pattern Modèle-Vue-Contrôleur (MVC). Ce pattern séparera les responsabilités du système en couches et éléments distincts.
Que font les couches ?
C’est ce que l'on va voir dans ce cours. Mais d’abord, quelques définitions :
Interface Utilisateur : Elle interagit avec l’utilisateur, affiche des informations, et récolte des valeurs saisies ou événements déclenchés..
Couche d’entités : Les éléments qui nous intéressent et qui sont affichés, ou manipulés par l’interface utilisateur.
Couche de données : Si l’on doit sauvegarder les informations des entités, elle les stockera et les récupérera via une solution pérenne de stockage de données.
Couche de connexion : Elle colle toutes les couches les unes aux autres, pour qu’elles n’aient pas à interagir directement entre elles.
Chaque couche communiquera avec la couche de connexion à travers des interfaces clairement spécifiées. Nous pouvons donc remplacer le code derrière l’interface par différentes implémentations, sans affecter ce qui appelle chaque interface. (Ici, "interface" désigne les messages et les méthodes fournis par chaque couche, et n’a donc pas le sens d’ "interface utilisateur".)
Pour déterminer ce que nous devons changer et comment procéder, nous allons passer par les étapes suivantes :
Exprimer les opportunités business à travers des user stories.
Étendre les user stories en descriptions de cas d’usage et déterminer quelles entités doivent être ajoutées à notre application.
Nous poser des questions clés pour déterminer quelle partie de l’application actuelle fait défaut, et ce qui peut être récupéré.
Prioriser nos user stories en fonction de nos découvertes.
En analysant nos besoins, en planifiant nos changements, et en les priorisant, nous pourrons tirer le maximum de notre temps et de notre énergie — et nous éviterons de nous retrouver perdus !
Ensuite, nous appliquerons le pattern MVC étape par étape, en utilisant la refactorisation. La refactorisation nous permet d’apporter des changements progressifs tout en s'assurant de l'intégrité et de la stabilité du système.
Allez c'est parti ! Construisons une meilleure application pour notre client !
En résumé
Les applications monolithiques sont difficiles à modifier et ne peuvent évoluer de façon pérenne.
Nous allons découpler notre architecture grâce au pattern MVC et à la refactorisation.
Pour démarrer, il suffit de faire le premier pas : définir les user stories !