CodeGym /Cours /Java Server /Configuration hors du code

Configuration hors du code

Java Server
Niveau 20, Leçon 0
Disponible

1. Le config dans ReadLater Starter

Si vous n'avez écrit que des programmes console d'apprentissage, il est très facile de croire à une magie naïve : « j'ai écrit une fois String baseUrl = "...", donc ce sera toujours comme ça ». Mais les applications backend vivent dans une réalité où la même application doit être lancée dans des conditions différentes : chez vous, chez un collègue, sur un autre ordinateur, avec ou sans Internet, sur un autre port, en mode mock parce que le service externe est « tombé ». Et tout cela doit être résolu sans modifier les sources, sinon chaque lancement se transforme en mini-réparation.

Commençons par un aveu honnête : notre ReadLater Starter vit déjà dans plusieurs modes. Il a une phase client avec les commandes catalog search et catalog details, et bientôt une phase serveur (server). Cela signifie que nous avons immédiatement des paramètres qui diffèrent inévitablement d'un lancement à l'autre : l'adresse de l'API externe, les timeout, le mode real/mock, le port du serveur. Et c'est là que le code en dur se comporte comme cet ami qui « arrive à l'heure » — mais seulement si vous vous rencontrez toujours au même endroit, le même jour et sans jamais changer de plans.

Une bonne règle pratique pour aujourd'hui est la suivante : le code répond à la question « que fait l'application ? », et la configuration répond à la question « avec quelles valeurs le fait-elle ? ». Par exemple, « faire une requête HTTP au catalogue » — c'est de la logique. Mais « où exactement la requête est faite » — c'est de la configuration.

La configuration est une responsabilité distincte

Comprendre la configuration est plus simple par analogie avec une recette. La recette dit quoi faire : mélanger, cuire, attendre. Mais elle ne doit pas être gravée avec des « ingrédients uniques sur Terre ». Aujourd'hui vous avez du lait à 2,5%, demain à 3,2%, et ce n'est pas une raison pour réécrire la recette et reconstruire la cuisine. Dans l'application, c'est pareil : la logique doit rester stable, et les paramètres doivent pouvoir changer sans recompilation.

Dans le monde backend, la configuration est un ensemble de valeurs qui peuvent différer entre les environnements (chez différents développeurs, sur différentes machines, dans différentes conditions réseau) ou entre les lancements (aujourd'hui mock, demain real). De plus, la configuration ne doit généralement pas entrer dans le modèle domaine et ne doit pas se propager dans tout le code. C'est une responsabilité distincte : il est pratique de la garder « près du point d'entrée » et de la fournir au reste de l'application sous une forme compréhensible.

Ici surgit une autre idée importante : la configuration n'est pas « tout ce que j'ai la flemme d'écrire ». Il y a une tentation de faire de chaque variable un config, et alors l'application devient un casino de réglages. Nous nous en tiendrons au bon sens : ne sortir que ce qui doit vraiment changer sans modifier le code Java.

2. La configuration dans ReadLater Starter

Maintenant, nous allons fixer précisément pour notre projet ce qui relève de la configuration, ce qui relève des données de lancement (arguments) ou d'une partie de la logique. Pour ne pas en faire une liste « à mémoriser », nous allons le présenter sous forme de tableau : il est plus facile de voir la limite.

Catégorie Exemple Pourquoi c'est cette catégorie
Configuration de l'application app.name=readlater Le nom n'affecte pas la logique métier, mais peut être différent selon les environnements et les logs
Configuration du client catalogue catalog.api.base-url=https://openlibrary.org L'adresse du service externe peut différer (real/mock, autre fournisseur, autre environnement)
Configuration du client catalogue catalog.api.request-timeout-ms=2000 Les timeout dépendent de l'environnement et du réseau, pas de la « logique de recherche de livre »
Configuration du client catalogue catalog.api.mode=real C'est un commutateur de comportement sans modification du code (particulièrement utile pour l'apprentissage)
Configuration du mode serveur server.host=localhost, server.port=8080 Le port peut être occupé, l'hôte peut différer, mais le code du serveur doit être le même
Données d'un lancement spécifique (args) catalog search clean code C'est une commande ponctuelle : aujourd'hui nous cherchons « clean code », demain « java concurrency »
Données d'un lancement spécifique (args) catalog details OL12345M C'est un identifiant de livre pour un appel spécifique
Partie de la logique applicative « comment chercher des livres », « comment normaliser un DTO » C'est le comportement de l'application, qui ne doit pas changer selon l'environnement

Remarquez un point subtil mais important. Le mode server n'est pas de la configuration, c'est une commande. En revanche, server.port est déjà une configuration pour cette commande. De la même manière, « chercher un livre » est une commande, et catalog.api.base-url est une configuration avec laquelle la commande travaille.

Si vous ne ressentez pas encore la différence, c'est normal. On commence généralement à la ressentir un jour où l'on change le port de 8080 à 8081 et que l'on fait soudainement un commit dans Git « parce que sinon ça ne démarre pas ». Et là, la question se pose : pourquoi changer le port a-t-il nécessité de modifier les sources ?

3. Le code en dur dans main() : démarrage rapide, vie lente

Parfois, le code en dur semble innocent. Surtout au début du cours. Il semble même « honnête » : voici la valeur, voici le code, tout est ensemble. Le problème est qu'un projet backend est une chose qui vit longtemps, et cette « innocence » s'arrête vite. Vous voudrez basculer entre real/mock, vous voudrez augmenter le timeout, vous voudrez lancer le serveur sur un autre port — et soudain, chaque désir nécessite : ouvrir le source, changer la ligne, recompiler, vérifier qu'on n'a rien cassé.

Regardons un « code en dur typique de démarrage », qui apparaît presque automatiquement dans les petits projets :

public class ReadLaterApplication {
    public static void main(String[] args) {
        // Valeurs de configuration de l'environnement (aujourd'hui celles-ci, demain d'autres)
        // Problème : elles sont « câblées » dans le source, donc nécessitent une modification du code et une recompilation.
        String baseUrl = "https://openlibrary.org"; // adresse de l'API externe (peut être real/mock ou autre environnement)
        int requestTimeoutMs = 2000;                // les timeout réseau dépendent du réseau et de l'environnement
        String apiMode = "real";                    // commutateur de comportement : "real" ou "mock"
        int serverPort = 8080;                      // le port peut être occupé sur une machine donnée

        // Pour la démonstration : affiche les valeurs actuelles avec lesquelles l'application a démarré
        System.out.println("baseUrl = " + baseUrl);                 // baseUrl = https://openlibrary.org
        System.out.println("requestTimeoutMs = " + requestTimeoutMs); // requestTimeoutMs = 2000
    }
}

Au niveau « je lance tout seul chez moi », cela fonctionne. Mais imaginez maintenant la réalité : vous êtes dans un avion sans Internet et vous voulez le mode mock. Ou sur votre machine le port 8080 est occupé (par exemple, vous avez accidentellement laissé un autre projet en cours). Ou le catalogue externe est devenu lent, et votre timeout de 2000 ms se transforme soudain en « timeout éternel ». Et que faites-vous ? Exactement : vous allez modifier le code. Ensuite vous oubliez de revenir en arrière. Ensuite vous commitez. Ensuite votre collègue vous réprimande parce que maintenant tout fonctionne différemment chez lui.

Le plus désagréable ici n'est même pas le fait de « devoir changer une ligne ». Le plus désagréable est que l'artefact change. Vos sources changent, donc la « version » de l'application change. Et la différence entre « j'ai besoin d'un autre port » et « j'ai modifié la logique métier » devient floue. C'est une très mauvaise habitude pour un développeur backend.

4. static final : mieux, mais pas une config

La réaction naturelle suivante d'une personne intelligente : « Bon, au moins je vais extraire les littéraux dans des constantes ». Cela rend effectivement le code plus propre. Les constantes réduisent la duplication et aident à ne pas transformer le projet en « cimetière de nombres magiques ». Mais il est important de comprendre : ce n'est toujours pas une configuration externe. Vous avez simplement déplacé le code en dur d'un endroit à un autre, mais il reste une partie des sources.

Par exemple :

public class ReadLaterApplication {
    // Les constantes améliorent la lisibilité et suppriment la duplication des valeurs « magiques ».
    // Mais ce sont toujours des valeurs dans le code : pour les changer, il faut modifier le source et recompiler.
    private static final String BASE_URL = "https://openlibrary.org";
    private static final int REQUEST_TIMEOUT_MS = 2000;
    private static final String API_MODE = "real";
    private static final int SERVER_PORT = 8080;

    public static void main(String[] args) {
        // Démonstration : la constante sert de « source unique » de la valeur dans le code
        System.out.println("API mode = " + API_MODE); // API mode = real
    }
}

Du point de vue de la lisibilité, c'est mieux. Mais si vous voulez changer API_MODE en "mock", vous modifiez à nouveau le source. Donc vous recompilez à nouveau. Et de nouveau vous risquez d'oublier. Et de nouveau il y a du bruit dans Git. C'est comme si vous aviez écrit sur une porte « entrée à droite », puis le magasin a déménagé — vous pouvez bien sûr acheter une nouvelle porte, mais il est généralement plus simple d'accrocher un panneau remplaçable sans réparer le bâtiment.

Fixons donc ceci : les constantes sont un excellent outil contre le chaos dans le code. Mais ce n'est pas un outil pour séparer le code de l'environnement. Elles résolvent le problème « je ne veux pas voir de valeurs magiques dans tout le projet », mais ne résolvent pas le problème « je veux changer les réglages sans recompilation ».

5. Config et args : on ne mélange pas

Ici, beaucoup de débutants tombent dans le piège : puisque nous avons args, on peut tout passer par les arguments. Techniquement, c'est possible. Mais si vous commencez à tout passer par args, le lancement de l'application devient une incantation du niveau « Harry Potter et l'Ordre de la ligne de commande ».

Nous avons besoin d'un modèle simple et prévisible :

  • args choisit le scénario : catalog search, catalog details, server ;
  • la configuration définit les paramètres d'environnement : baseUrl, timeout, real/mock, host/port.

Pour que cela soit visible non seulement dans le texte, mais aussi dans l'esprit, on peut imaginer un schéma (au niveau de l'idée, sans implémentation) :

flowchart TD
    A[args] -->|choisit| C[Mode de lancement]
    B[configuration] -->|définit les valeurs| D[Paramètres d'environnement]
    C --> E[Logique de l'application]
    D --> E

Ainsi, catalog search clean code est la commande « que faire maintenant ». Et catalog.api.base-url est « où se trouve le catalogue auquel on s'adresse ». La commande change souvent d'un lancement à l'autre, c'est normal. La configuration peut aussi changer, mais généralement moins souvent et pour une autre raison : parce que l'environnement est différent.

Appliquons cela à nos commandes de lancement réelles (que nous avons déjà utilisées) :

# Lancement du scénario de recherche (les valeurs d'environnement ne sont pas passées dans args)
./gradlew run --args="catalog search clean code"

# Lancement du scénario de détails pour un identifiant spécifique
./gradlew run --args="catalog details OL12345M"

# Lancement du serveur (port/hôte font partie de la configuration, pas de la commande)
./gradlew run --args="server"

Dans ces commandes, on voit déjà la bonne séparation. Dans args, il n'y a pas de baseUrl, pas de timeout, pas de port — et c'est bien. Sinon, vous traîneriez à chaque lancement un « sac de réglages ». Les valeurs d'environnement doivent vivre à part pour que le lancement reste lisible : vous comprenez immédiatement ce que vous lancez, au lieu de décoder « que signifie le huitième argument ».

6. Mini-modèle pour la configuration future

Nous n'introduisons pas encore de système de configuration complet (ce sont les prochaines leçons du jour), mais il est important d'avoir à l'avance un « squelette de pensée » pour ne pas se perdre. Dans ce cours, nous nous en tiendrons à un modèle simple, presque domestique : il y a une source de base des réglages (fichier), une couche externe de surcharge (variables d'environnement), et une commande de lancement (args).

Cela ressemble à ceci :

flowchart TD
    P[application.properties] -->|base| X[Sélection de la valeur]
    E[Variables d'environnement] -->|override| X
    D[Valeurs par défaut dans le code] -->|fallback| X
    X --> C[AppConfig]
    A[args] --> L[Commande de lancement]
    C --> R[Exécution de la logique]
    L --> R

Remarquez : les valeurs par défaut dans le code sont acceptables, mais elles doivent être pertinentes et locales. « Si rien n'est dit, on lance sur le port 8080 » — c'est un défaut normal. « Si rien n'est dit, on va vers la passerelle de paiement de production » — c'est déjà un mauvais défaut (mais nous ne parlons pas de paiements, heureusement).

Une autre règle importante que nous suivrons dans tout le projet : « les sources de valeurs » ne doivent pas être dispersées dans l'application. Si une classe lit System.getenv(), une autre lit le fichier, une troisième parse args, vous obtiendrez rapidement un chaos configurationnel. L'approche mature consiste à collecter la configuration en un seul endroit (généralement dans la couche app/config près du point d'entrée), puis à transmettre aux autres composants des valeurs déjà préparées.

7. Ce qu'il ne faut pas mettre dans la config

Quand les gens entendent pour la première fois « configuration hors du code », ils ont parfois envie de tout sortir. Et là, il est important de s'arrêter et de se poser une question simple : « Cette valeur doit-elle vraiment changer sans recompilation ? Ou est-ce que j'ai simplement peur de prendre une décision dans le code ? »

Par exemple, les clés JSON ("title", "author") dans notre DTO font partie du contrat. Si vous les mettez dans la config, vous obtenez un système où le comportement de l'application dépend d'un texte aléatoire dans un fichier de configuration. Ce n'est pas de la flexibilité, c'est un risque accru de bugs étranges. La même chose s'applique aux « règles du domaine ». Si notre domaine dit « title ne doit pas être vide », ce n'est pas de la configuration. C'est de la logique. Oui, dans les grands systèmes, certaines règles peuvent être paramétrables, mais c'est un tout autre niveau de complexité, et dans notre cours, ce serait une complication prématurée.

Dans ReadLater Starter, nous sortons précisément les paramètres d'environnement : adresses, ports, timeout, modes. Ce sont ces choses que vous souhaitez vraiment pouvoir changer sans recompilation, et qui ne doivent pas modifier le sens des opérations métier. L'application reste la même, elle se « connecte » simplement au monde avec d'autres câbles.

8. Erreurs typiques lors de l'externalisation de la configuration

Erreur n°1 : coder en dur des valeurs à plusieurs endroits, puis les rechercher héroïquement dans le projet.
Le plus souvent, cela donne un zoo : quelque part dans ReadLaterApplication on a 8080, ailleurs dans ServerLauncher un autre 8080, et dans une méthode de test « temporairement » on a 8081. C'est gênant car vous ne savez pas quelle valeur est la « vraie », et toute modification se transforme en quête « trouve toutes les occurrences ». Le remède est simple : convenir que les valeurs de configuration sont déterminées à un seul endroit, puis transmises comme paramètres.

Erreur n°2 : considérer que static final est déjà une configuration externe.
Les constantes améliorent effectivement la lisibilité, et vous les utiliserez. Mais elles font toujours partie des sources, donc un changement nécessite une recompilation. Dès que vous vous surprenez à penser « je vais changer le port et le commiter pour ne pas oublier », c'est un signal quasi certain que vous confondez code et configuration. Les constantes sont bonnes pour des significations immuables, la configuration pour l'environnement.

Erreur n°3 : mélanger « commande de lancement » et « paramètres d'environnement ».
Si vous commencez à passer baseUrl ou le port via args, le lancement devient rapidement illisible. Et inversement, si vous essayez de « choisir la commande dans la config », vous obtenez une application qu'il est difficile de lancer explicitement : vous lancez le programme, et il décide « selon son humeur » d'être serveur ou client. Dans notre projet, la séparation est claire : args est la commande (server ou catalog ...), la config est les paramètres de cette commande.

Erreur n°4 : essayer de faire de chaque variable locale une configuration.
Parfois cela ressemble à : « et si on mettait la chaîne 'Searching…' dans la config, et le message d'erreur aussi, et la longueur de l'indentation dans la console… ». Résultat : un énorme fichier de configuration que personne ne lit, et une application devenue plus complexe sans réel bénéfice. Dans ce cours, nous restons pragmatiques : ce qui devient configuration, c'est ce qui change réellement entre environnements et impacte les intégrations et le lancement.

Erreur n°5 : créer une configuration mais oublier de nommer les clés de manière lisible.
Des clés comme x1, timeout2, modeFlag peuvent être « compréhensibles par l'auteur aujourd'hui », mais demain vous maudirez vous-même ce fichier. Les clés normales doivent se lire comme une phrase : catalog.api.base-url, server.port. Ce n'est pas une question d'esthétique, c'est une partie de la discipline d'ingénierie, car la configuration est une interface au même titre qu'une méthode publique.

1
Mission
Java Server, niveau 20, leçon 0
Bloqué
Synthèse centralisée des paramètres
Synthèse centralisée des paramètres
1
Mission
Java Server, niveau 20, leçon 0
Bloqué
Séparation des paramètres et des données de lancement
Séparation des paramètres et des données de lancement
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION