Ce site met a disposition le build journalier de la traduction francaise du Maven: The Definitive Guide
Consultez :
  • Les documents de reference sur le projet original
  • Les sources de la traduction fr sur GitHub
  • maven


    8.3. Optimiser les dépendances

    Si vous jetez un oeil aux différents POMs créés dans le Chapitre 7, Un projet multimodule d'entreprise, vous remarquerez plusieurs types de répétition. Le premier type que vous pouvez voir est la duplication de dépendances comme spring et hibernate-annotations dans plusieurs modules. La dépendance hibernate a en outre l'exclusion de javax.transaction à chacune de ses définitions. Le second type de répétition rencontré vient du fait que parfois plusieurs dépendances sont liées entre elles et partagent la même version. C'est souvent le cas lorsqu'un projet livre plusieurs composants couplés entre eux. Par exemple, regardez les dépendances hibernate-annotations et hibernate-commons-annotations. Toutes les deux ont la même version 3.3.0.ga, et nous pouvons nous attendre à ce que les versions de ces deux dépendances évoluent de concert. Les deux artefacts hibernate-annotations et hibernate-commons-annotations sont des composants du même projet mis à disposition par JBoss, et donc quand une nouvelle version de ce projet sort, ces deux dépendances changent. Le troisième et dernier type de répétition est la duplication de dépendances et de version de modules frères. Maven fournit un mécanisme simple pour vous permettre de factoriser ces duplications dans un POM parent.

    Comme pour le code source de votre projet, chaque duplication dans vos POMs, est une porte ouverte pour de futurs problèmes . La cohérence des versions sur un gros projet sera difficile à assurer si des déclarations de dépendance sont dupliquées. Tant que vous avez deux ou trois modules, cela reste gérable, mais lorsque votre organisation utilise un énorme build multimodule pour gérer des centaines de composants produits par plusieurs services, une seule erreur de dépendance peut entraîner chaos et confusion. Une simple erreur de version sur une dépendance permettant la manipulation de bytecode comme ASM dans les profondeurs des dépendances du projet peut devenir le grains de sable qui gripperait une application web, maintenue par une autre équipe de développeurs, et qui dépendrait de ce module. Les tests unitaires pourraient passer car ils sont exécutés avec une certaine version de la dépendance, mais ils échoueraient lamentablement en production là où le package (un WAR, dans ce cas) serait réalisé avec une version différente. Si vous avez des dizaines de projets qui utilisent une même dépendance comme Hibernate Annotations, chaque recopie et duplication des dépendances et des exclusions vous rapproche du moment où un build échouera. Au fur et à mesure que la complexité de vos projets Maven augmente, la liste de vos dépendances s'allonge et vous allez donc devoir stabiliser les déclarations de dépendance et de version dans des POMs parent.

    La duplication des versions des modules frères peut produire un problème assez redoutable qui ne résulte pas directement de Maven, et dont on ne se souvient qu'après l'avoir rencontré plusieurs fois. Si vous utilisez le plugin Maven Release pour effectuer vos livraisons, toutes les versions de dépendance soeurs seront automatiquement mises à jour pour vous, aussi ce n'est pas là que réside le problème. Si simple-web version 1.3-SNAPSHOT dépend de simple-persist version 1.3-SNAPSHOT, et si vous produisez la version 1.3 de ces deux projets, le plugin Maven Release est suffisamment intelligent pour changer les versions dans les POMs de votre projet multimodule automatiquement. Produire la livraison avec le plugin Release va automatiquement incrémenter les versions de votre build à 1.4-SNAPSHOT, et le plugin Release va commiter ces modifications sur le dépôt de source. Livrer un énorme projet multimodule ne pourrait être plus facile, à moins que...

    Les problèmes arrivent lorsque les développeurs fusionnent les modifications du POM et perturbent une livraison en cours. Un développeur fusionne souvent des modifications et parfois il se trompe lors de la gestion du conflit sur la dépendance sur un module frère, revenant par inadvertance à la version de la livraison précédente. Comme les versions consécutives d'une dépendance sont souvent compatibles, cela n'apparaît pas lorsque le développeur lance le build, ni avec un système d'intégration continue. Imaginez un build très complexe où le tronc est rempli de composants à la version 1.4-SNAPSHOT, et maintenant imaginez que le Développeur A a mis à jour le Composant A tout au fond de la hiérarchie du projet pour qu'il dépende de la version 1.3-SNAPSHOT du Composant B. Même si la plupart des développeurs utilisent la version 1.4-SNAPSHOT, le build fonctionne correctement si les versions 1.3-SNAPSHOT et 1.4-SNAPSHOT du Composant B sont compatibles. Maven continuera à construire le projet en utilisant la version 1.3-SNAPSHOT du Composant B depuis le dépôt local des développeurs. Tout semble bien se passer — le projet est construit, l'intégration continue est au vert, etc. Quelqu'un peut alors rencontrer un bug étrange en rapport avec le Composant B, mais il va se dire que c'est la faute à "pas de chance" et va poursuivre son développement. Pendant ce temps, dans la salle des machines la pression monte, jusqu'à ce qu'une des pièces explose ...

    Quelqu'un, appelons le Mr. Distrait, a rencontré un conflit lors de la fusion du Composant A, et a malencontreusement indiqué que le Composant A dépend de la version 1.3-SNAPSHOT du Composant B alors que le projet continuait sa marche en avant. Une équipe de développeurs essaye de corriger un bug dans le Composant B depuis tout ce temps et ils ne comprennent pas pourquoi ils n'arrivent pas à le corriger en production. Finalement, quelqu'un regarde le Composant A et s'aperçoit de cette dépendance sur une mauvaise version. Heureusement, ce bug n'était pas suffisamment important pour coûter de l'argent ou des vies, mais Monsieur Distrait se sent un peu bête et les autres ont perdu un peu de leur confiance en lui pour ce problème de dépendances entre composants. (Heureusement, Monsieur Distrait se rend compte que ce problème est une erreur d'utilisateur et ne vient pas de Maven, mais il est plus que probable qu'il commence un vilain blog dans lequel il se plaint de Maven sans arrêt pour se sentir mieux.)

    Heureusement, la duplication de dépendance et les erreurs de dépendance entre projets frères peuvent être facilement évitées avec quelques modifications. La première chose à faire est de trouver toutes les dépendances utilisées sur plus d'un projet et de les déplacer dans la section dependencyManagement du POM parent. Nous allons laisser de côté les dépendances entre modules frères pour l'instant. Le pom simple-parent contient maintenant :

    <project>
      ...
      <dependencyManagement>
        <dependencies>
          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
            <version>2.0.7</version>
          </dependency>
          <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
            <version>1.5</version>
          </dependency>  
          <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-annotations</artifactId>
            <version>3.3.0.ga</version>
          </dependency>
          <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-commons-annotations</artifactId>
            <version>3.3.0.ga</version>
          </dependency>
          <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate</artifactId>
            <version>3.2.5.ga</version>
            <exclusions>
              <exclusion>
                <groupId>javax.transaction</groupId>
                <artifactId>jta</artifactId>
              </exclusion>
            </exclusions>
          </dependency>
        </dependencies>
      </dependencyManagement>
      ...
    </project>

    Une fois ces dépendances remontées, nous allons devoir supprimer les versions de ces dépendances de tous les POMs ; sinon, elles vont surcharger ce qui est défini dans la balise dependencyManagement du projet parent. Regardons juste le simple-model pour plus de clarté :

    <project>
      ...
      <dependencies>
        <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-annotations</artifactId>
        </dependency>
        <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate</artifactId>
        </dependency>
      </dependencies>
      ...
    </project>

    La seconde chose à faire est de corriger la duplication des versions de hibernate-annotations et hibernate-commons-annotations puisqu'elles devraient rester identiques. Nous allons faire cela en créant une propriété appelée hibernate.annotations.version. La section simple-parent résultante ressemble à ceci :

    <project>
      ...
      <properties>
        <hibernate.annotations.version>3.3.0.ga</hibernate.annotations.version>
      </properties>
    
      <dependencyManagement>
        ...
        <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-annotations</artifactId>
          <version>${hibernate.annotations.version}</version>
        </dependency>
        <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-commons-annotations</artifactId>
          <version>${hibernate.annotations.version}</version>
        </dependency>
        ...
      </dependencyManagement>
      ...
    </project

    Notre dernier problème à corriger est celui des dépendances entre modules frères. Une technique que nous pourrions utiliser est de les déplacer dans la section dependencyManagement, comme toutes les autres, et de définir les versions des projets frères dans le projet parent qui les contient. Cette approche est certainement valide, mais nous pouvons aussi résoudre ce problème de version en utilisant deux propriétés prédéfinies — ${project.groupId} et ${project.version}. Puisqu'il s'agit de dépendances entre frères, il n'y a pas grand-chose à gagner à les lister dans le parent, aussi nous allons faire confiance à la propriété prédéfinie ${project.version}. Comme ces projets font tous partie du même groupe, nous pouvons sécuriser d'avantage ces déclarations en faisant référence au groupe du POM courant via la propriété prédéfinie ${project.groupId}. La section dependency de simple-command ressemble maintenant à cela :

    <project>
      ...
      <dependencies>
        ...
        <dependency>
          <groupId>${project.groupId}</groupId>
          <artifactId>simple-weather</artifactId>
          <version>${project.version}</version>
        </dependency>
        <dependency>
          <groupId>${project.groupId}</groupId>
          <artifactId>simple-persist</artifactId>
          <version>${project.version}</version>
        </dependency>
        ...
      </dependencies>
      ...
    </project>

    Voici un résumé des deux optimisations que nous avons réalisées pour réduire ce problème de duplication des dépendances :

    Remonter les dépendances communes dans dependencyManagement

    Si plus d'un projet dépend d'une dépendance particulière, vous pouvez ajouter celle-ci à dependencyManagement. Le POM parent peut contenir une version et un ensemble d'exclusions ; tout ce que les POMs fils ont besoin de faire pour référencer cette dépendance est d'utiliser le groupId et l'artifactId. Les projets fils peuvent ne pas déclarer la version et les exclusions si la dépendance fait partie des dépendances de la balise dependencyManagement.

    Utiliser la version et le groupId pré-définis pour les projets frères

    Utiliser ${project.version} et ${project.groupId} pour faire référence à un projet frère. Les projets frères ont la plupart du temps le même groupId, et presque toujours la même version. Utiliser ${project.version} vous aidera à éviter les incohérences de versions entre projets frères comme nous l'avons vu plus tôt.