Premier épisode dans la série d'articles « Mise en production et exploitation d'une application Java/J2EE ».
Le Jour J approche pour la mise en ligne de l'application de ChocoCorp. Les développements sont terminés, et l'application va bientôt être déployée en production.
Pour l'hébergement de son application, ChocoCorp a fait appel à un hébergeur spécialisé qui lui a mis à disposition un serveur sous Linux avec le serveur d'applications Glassfish et MySQL pré installé. Ils n'ont, normalement, plus qu'à déployer leur EAR sur ce serveur et valider que tout fonctionne avant d'ouvrir le site au public.
Confiants parce que leur application passe tous les tests unitaires et qu'il n'y a pas eu de grosses modifications depuis plusieurs semaines, ils décident de prendre le fichier EAR généré par Maven2 et de le déployer via l'interface d'administration de Glassfish. Et c'est là que les problèmes commencent...
La gestion du classloader et du classpath est un problème assez courant dès qu'on déploie un EAR.
Il y a plusieurs problèmes dans la gestion du classpath d'une application :
Pendant les phases de développements, on détecte assez rapidement le problème quand on développe et déploie l'application dans son ensemble. Beaucoup moins quand on développe indépendamment des modules et qu'on assemble au final l'application. Dans ce cas on aura beaucoup de chances d'avoir des erreurs de type ClassCastException ou NoClassDefFoundError.
Alors, comment éviter ces erreurs ?
C'est un problème d'architecture et d'assemblage de l'application. Il n'y a pas de solution toute simple à ce problème. Il faut bien comprendre comment fonctionne le Classloader du serveur d'applications. Il n'y a malheureusement rien dans les spécifications JEE qui imposent une gestion standard du Classpath. C'est généralement dépendant du serveur. Heureusement, tous les serveurs utilisent plus ou moins la même technique à savoir un classloader hiérarchisé.
La solution la plus efficace consiste à déplacer à la racine de l'EAR l'ensemble des classes et librairies, ainsi que leurs dépendances, communes à tous les modules de l'application. Il ne faut pas oublier de référencer le classpath dans le fichier META-INF/Manifest.mf de l'EAR . Ce classpath devra contenir toutes les librairies accessibles pas les autres modules.
Il se peut que certaines librairies, non obligatoires, soient manquantes et que cela bloque le lancement de l'application. En fait, le serveur d'applications va analyser le classpath déclaré dans le fichier META-INF/Manifest.mf présent dans tous les fichiers EAR, WAR, JAR. Si certaines dépendances ne sont pas remplies, alors le serveur peut refuser le démarrage de l'application.
Pour résoudre le problème, vous pouvez simplement inclure la librairie manquante ou supprimer la référence à la librairie dans le fichier META-INF/Manifest.mf.
Certaines librairies fournies par le serveur d'applications peuvent avoir des versions différentes. Le problème ne sera peut-être pas détecté au moment le déploiement. Il pourra apparaitre lors de l'utilisation de l'application.
Soit, vous voulez utiliser une version spécifique d'une librairie auquel cas vous devez l'inclure dans votre EAR. Soit, vous souhaitez utiliser la version fournie par le serveur d'applications. Dans ce cas, vous devez utiliser, pendant la phase de développement, le même serveur et supprimer de votre classpath la librairie.
Une application sans connexion à une base de données est assez rare de nos jours. Cependant, on rencontre assez souvent un problème au moment du déploiement de l'application : configurer la connexion JDBC à la base de données. La plus part du temps la connexion est configurée en dur dans un fichier de propriétés. Il est rare d'avoir le même mot de passe sur l'environnement de Test et celui de Production. D'où un problème de connexion à la base de données au démarrage de l'application.
Comment résoudre le problème ?
Il existe plusieurs solutions dont :
Pour des raisons évidentes de sécurité, je ne trouve pas la première solution satisfaisante. La deuxième solution quant à elle oblige à maintenir plusieurs fichiers de configuration et générer des builds différents avec le risque qu'un bug soit introduit entre 2 builds.
La dernière est de loin celle que je préfère et préconise. La connexion est configurée sur le serveur d'applications (via une interface d'admin ou via un fichier de configuration). C'est devenu aujourd'hui relativement simple à configurer et à utiliser, tout particulièrement quand on utilise Spring. Quelques lignes suffisent.
C'est également le même principe pour la configuration des Queues JMS.
L'application de ChocoCorp démarre maintenant correctement et crée correctement la base de données. Après plusieurs tests, un autre problème apparait : les factures ne semblent pas être générées. Après investigation le problème vient d'un répertoire qui n'est pas trouvé.
La encore c'est un problème assez récurrent : les chemins d'accès vers des fichiers ou des répertoires sont également codés en dur dans l'application.
Plusieurs solutions sont possibles dont :
Les solutions 1 et 2 sont faciles à mettre en place et ne demandent pas beaucoup de modifications dans le code source. Par contre, je ne les trouve pas vraiment utilisable sur du long terme pour la raison suivante : Si on upgrade ou qu'on change le chemin d'accès au serveur d'applications, il ne faudra pas oublier déplacer les fichiers et répertoires spécifiques à l'application dans ce nouveau répertoire.
Avec ces deux premières solutions, on dépend fortement de l'emplacement du serveur d'applications. Je trouve beaucoup plus propre et plus logique de stocker les données en dehors de celui-ci. C'est pour cela que je préfère les solutions 3 et 4. Elles vont cependant dépendre fortement de l'architecture de votre application. Si vous n'avez qu'un répertoire ou un fichier à exporter, la troisième solution me parait plus adaptée.
Cette fois l'application de ChocoCorp se déploie et fonctionne correctement. Il ne reste plus qu'à mettre en place un frontal web (Apache, HAProxy ou autre) et à faire pointer le nom de domaine vers le bon contexte. Une fois le frontal configuré, certaines pages ne s'affichent plus correctement et des liens sont cassés.
C'est aussi un grand classique des problèmes qu'on peut rencontrer quand on déploie une application J2EE. Pour que l'application soit accessible au travers d'un frontal, il n'y a pas beaucoup de solutions possibles :
Dés qu'on travaille dans un environnement de production industrialisé, un certain nombre de processus sont standardisés. Généralement avant de déployer votre application on vous demande de compléter un document contenant toutes les informations nécessaires à votre application.
Sans aller jusqu'à ces pratiques, je trouve qu'il est important de rédiger et de maintenir dans une procédure d'installation ou du moins un document référençant les différents paramètres de l'application à configurer sur votre serveur d'applications :
Avoir un environnement d'intégration ou recette proche voir identique à l'environnement de production.
Utiliser le même build pour toutes les phases de tests, d'intégration/recette et de production. Comme je l'expliquais plus haut, cela évite qu'un bug soit introduit durant ou par le processus de build.
Quant à vous, avez-vous déjà rencontré ce type de problèmes dans le déploiement d'une application ?
Et comment les avez-vous résolus ?