14 juin 2010

Configuration d'une application JavaEE

Nicolas Romanetti m'a fait l'autre jour une remarque sur un manque dans mon livre Apache Maven concernant le chapitre sur JavaEE : Dans le modèle JavaEE (pré-6), on met les classes et les ressources dans un WAR, lui même dans un EAR, et on balance tout ça sur le serveur. Où placer la configuration de l'application ?

Si on lit la spec JavaEE, elle prévoit un rôle d'assembleur d'application, qui va prendre l'EAR, éditer ses fichiers de déploiement XML pour faire le lien entre les ressources de son serveur (DataSource, files JMS, ...) et l'application. Personnellement je n'ai jamais rencontré ce cas de figure, et mes clients attendent plutôt une application prête à fonctionner avec au plus un fichier de configuration properties externe à l'EAR.

Option 1 : embarquer la conf en fonction de l'environnement cible

Si on veut suivre l'esprit JavaEE on peut utiliser des profils Maven pour packager dans le WAR les ressources associées à l'environnement cible :

<profile>
      <id>dev</id>
      <build>
        <resources>
          <resource>
            <directory>${basedir}/src/env/dev</directory>
          </resource>
        </resources>
      </build>
    </profile>
    <profile>
      <id>prod</id>
      <build>
        <resources>
          <resource>
            <directory>${basedir}/src/env/prod</directory>
          </resource>
        </resources>
      </build>
    </profile>

il suffit donc d'ajouter un petit "-Pprod" pour générer l'application dans sa configuration de production. Par contre, la configuration ne peut pas être éditée simplement une fois l'application installée.

Option 2 : passer par le JNDI


L'autre option respectant l'esprit JavaEE est de se baser sur les ressources JNDI, et donc d'accéder à une donnée d'environnement de type URL ou String qui pointe vers notre fichier de properties. Dans le web.xml j'ajoute donc un petit :

<env-entry>
      <description>Chemin de la configuration locale</description>
      <env-entry-name>appli.home</env-entry-name>
      <env-entry-type>java.lang.String</env-entry-type>
      <env-entry-value>/valeur/par/defaut</env-entry-value>
    </env-entry>

Je n'ai plus qu'à définir cette ressource dans le JNDI de mon serveur d'application, ce qui va par contre être spécifique à chaque serveur - Tomcat par exemple ne permet pas de définir une ressource URL, alors que Websphère le supporte (pas classique de dire du bien de celui-là, n'est ce pas ?).

Pour y accéder, il faut faire un lookup JNIDI ce qui n'est pas léger léger, et si on veut passer par Spring et ses PlaceHolder ${truc} on regardera du côté de SPR-3030.

Option 3 : passer par une propriété système


Dernière option, la version "light" : dans de nombreux cas, je constat que le serveur d'application n'héberge qu'une seule instance de l'application, aussi on peut positionner des variables systèmes au niveau de la JVM pour lui passer un paramètre, typiquement notre appli.home. Et là, rien de spécial à faire pour activer le configuration Spring en dehors d'activer le SYSTEM_PROPERTIES_MODE_OVERRIDE.
Dans le même esprit, on peut externaliser la configuration log4j en ajoutant un -Dlog4j.configuration=file:///monlog4j.xml.

Perso je préfère cette dernière variante, la plus légère à mettre en oeuvre et la plus simple à expliquer aux développeurs. JNDI reste un truc assez mystérieux, qui plus est avec des variations pour sa configuration en fonction des serveurs d'application. L'accès au DirContext se fait trop souvent via un mauvais copier-coller qui référence les classes du serveur d'application (alors que justement le but est de s'en découpler !) et/ou en ajoutant au petit bonheur la chance des java:comp/env ... peut être le sujet d'un autre billet :)

Option 4: compléter le classpath du serveur

Suite au commentaire de Sylvain François, une 4ème option, comparable au system properties : ajouter au Classpath de l'application le chemin d'un répertoire contenant les fichiers de conf. En jouant sur l'ordre de chargement du ClassPath on peut ainsi surchager les fichiers de conf "par défaut" présents dans le WAR/EAR.

Ces deux dernière options sont assez comparables et leur mise en ouvre va dépendre de la liberté qu'on a pour configurer le serveur d'application. Certaines équipes d'exploit' donnent la main sur la commande de lancement de la JVM, d'autres préfèrent la blinder mais laissent libre accès aux répertoires de libs ...

Qu'en pensez vous ? Donnez votre avis : http://doodle.com/d4qdxc87w76i524c

7 commentaires:

Sylvain FRANCOIS a dit…

Autre solution simple à mettre en place et très flexible : se baser sur le CLASSPATH.

Sur un serveur JEE, on peut par exemple ajouter un répertoire spécifique à l'application dans le répertoire de configuration du serveur, (e.g. jboss-as/server/myserver/conf/myapp), puis référencer les ressources via des chemins du type (myapp/myresource).

L'intérêt est que cela ne suppose pas de changement sur le lancement du serveur, et qu'on peut exploiter les mécanismes de priorité du CLASSPATH pour embarquer par exemple des ressources par défaut dans l'application (surchargeables).

Sylvain

nicolas deloof a dit…
Ce commentaire a été supprimé par l'auteur.
nicolas deloof a dit…

C'est une variante du "system properties", qui suppose aussi qu'on est tout seul sur le serveur d'appli (ça doit être le cas de presque tout le monde).

Ca à l'air facile sur JBoss, par contre est-ce que c'est aussi simple sur d'autres serveurs ? J'ai du modifier une fois le ClassPath de Websphère et j'ai souffert :P

Pour ma part j'ai plus de facilité pour modifier les paramètres de lancement que le classpath. Ca doit dépendre des habitudes de l'équipe d'exploit...

Damien a dit…

Commons-configuration est aussi une bonne alternative pour lire des fichiers de configuration, interne ou externe à l'application.

Le lib gère la notion de propriétés avec valeur par défaut, surchargée ou non, dans un ou plusieurs fichiers, lus dans différents répertoires ou classpath.

Malheureusement, la dernière release date un peu (décembre 2008), ce qui n'est plutôt pas bon signe pour la pérénité de l'outil ...

nicolas deloof a dit…

J'utilise aussi commons-configuration, en complément de cette problématique ça évite au code de préciser explicitement d'où il tire une info (et ça clarifie les tests).

J'ai souvent des exigences pour stocker certaines données de conf en base, avec CC il n'y a aucun impact sur le code.

Stephen Bruce a dit…

Pour ma part, l'externalisation de la configuration mais aussi de toute la partie statique des applications web étant une exigence client, J'ai 3 artefacts (1 war, 1 static.zip, 1 conf.zip). L'exécution des bons plugins permet à l'exploitation de déployer et de configurer l'application.

Dans le détails, on utilise spring avec un placeholder qui contient classpath: en dev et le chemin absolu pour la recette et la prod.

FRT a dit…

Pour ma part, je travaille beaucoup avec Websphere et la solution que tu n'as jamais rencontrée est celle à laquelle je suis le plus fréquemment confronté : la personne en charge du deploiement fait le lien lors du deploiement entre les ressources du serveur et les ressources de l'application.

Des que l'on a des politiques de sécurité fortes il est général difficile d'utiliser une autre alternative : toutes les solutions que tu proposent présument que tu puisses soit modifier les classpath serveurs, soit que tu dispose des acces complets à la prod (nom des files, des datasources, voire des user/password). Dans mon cas ça n'a jamais été le cas (déjà avoir les acces en dev ce n'est pas toujours gagné mais alors en recette ou en prod...)

Reste le cas des ressources spécifiques à mon application que j'aimerai pouvoir customiser (genre une propriété d'initialisation de servlet dépendant de l'environnement). Je n'ai pas encore trouvé de solution me satisfaisant (j'utilise la solution 1, je me suis un peu cassé les dents avec la 2, et j'ai aussi utilisé un système de proprietes stockées en DB)
Ceci dit si quelqu'un à une "solution miracle" à proposer je suis preneur car régulièrement confronté à cette problématique