Quantcast
Channel: Publicis Sapient Engineering – Engineering Done Right
Viewing all articles
Browse latest Browse all 1865

La notion de BOM avec Maven

$
0
0

Maven est une solution de gestion de production de projets mature et connue du plus grand nombre. Sa dernière release majeure (version 3.0) date de 2010 et depuis, peu de grandes nouveautés ont été ajoutées (nous en sommes à la release 3.1.1).

Dans cet article, nous allons revenir sur une fonctionnalité de Maven ancienne mais moins utilisée, la notion de BOM ou Bill Of Materials. Ce principe remonte à la release 2.0.9 de Maven d’avril 2008 et s’appuie sur l’ajout du scope import.

Scope import

Revenons tout d’abord sur le scope import de Maven. Les scopes dans Maven permettent d’agir sur la portée de vos dépendances. Ainsi, par exemple, le scope test permet l’utilisation des dépendances uniquement lors du déroulement des tests.

Le scope import est utilisable seulement dans la section dependencyManagement du pom.xml et pour une dépendance de type pom. À la lecture du pom.xml, Maven va remplacer la dépendance import par toute la section dependencyManagement de cette dépendance.

Pour mieux comprendre ce principe, voici un exemple concret. Prenons un pom.xml A comme suit (remarquez la valeur "pom" de la balise packaging):

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
  <groupId>fr.xebia.blog</groupId>
  <artifactId>pomA</artifactId>
  <version>1</version>
  <packaging>pom</packaging>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.0.1.RELEASE</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

Puis un pom.xml B utilisant le scope import:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
  <groupId>fr.xebia.blog</groupId>
  <artifactId>pomB</artifactId>
  <version>1</version>
  <packaging>jar</packaging>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>fr.xebia.blog</groupId>
        <artifactId>pomA</artifactId>
        <version>1</version>
        <scope>import</scope>
        <type>pom</type>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

 

Maven en déduira à la lecture du pom.xml B un pom comme suit:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
  <groupId>fr.xebia.blog</groupId>
  <artifactId>pomB</artifactId>
  <version>1</version>
  <packaging>jar</packaging>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.0.1.RELEASE</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

Remarquez le type pom de la dépendance sur l’artifact pomA. Sans ce type, vous ne pourrez pas compiler vos projets.

Voilà comment fonctionne le scope import de Maven. L’intérêt de celui-ci peut ne pas vous sauter aux yeux de prime abord. Nous allons donc voir un de ses intérêts avec les BOM Maven.

Principe du BOM Maven

Concrètement, nous avons vu que le scope import permet d’importer des dependencyManagements dans un autre pom. Le principe du BOM est d’exploiter cette capacité.

Nous allons définir dans le BOM, qui n’est rien d’autre qu’un pom avec seulement une section dependencyManagement, toutes les versions des dépendances que nous voulons utiliser dans nos projets. Le BOM est donc une sorte d’annuaire de versions de toutes les dépendances, ce qui permet de ne plus s’occuper des versions dans le reste de vos projets.

En effet, avec Maven, si vous définissez une dépendance dans la section dependencyManagement, vous n’êtes plus obligé de déclarer la version dans la déclaration réelle de vos dépendances (section dependencies du pom). Ainsi, pour notre exemple ci-dessus avec la dépendance Spring, vous pouvez déclarer les dépendances comme suit:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
</dependency>

Maven saura automatiquement qu’il s’agit de la version 4.0.1.RELEASE.

Avantage de la notion de BOM

La définition de version est tout à fait possible avec un pom parent classique. Le parent définit les versions dans le dependencyManagement de la même façon que le BOM.

Ce système est très pratique dans le cadre d’un petit projet. Par petit, je parle du nombre de modules liés et de la complexité des dépendances inter-projets.

Mais sur un gros projet, voire un ensemble de projets, cela peut vite devenir un casse tête.

Alors quel est l’avantage du BOM ?

Un pom.xml ne peut avoir qu’un seul et unique parent, ce qui vous oblige à déclarer TOUTES les versions de vos dépendances dans le même parent.

Si vous voulez découper proprement vos versions (le front d’un côté, le back de l’autre par exemple), ce n’est pas possible. Un parent peut bien sûr avoir lui-même un parent et ainsi de suite, mais cela peut vite devenir compliqué à gérer.

Par exemple, comment définir qui est le parent de qui ? Le front est-il le parent du back ou l’inverse ? Si vous releasez un parent, vous serez obligé de releaser ces fils pour pouvoir prendre en compte la nouvelle version alors que ceux-ci n’ont pas forcément bougé.

Bref, l’utilisation abusive de parents peut vite devenir problématique. Le BOM Maven va nous permettre de régler ça.

En effet le BOM agit comme une dépendance classique, vous pouvez donc en importer autant que vous le souhaitez dans un pom.xml

Ainsi, vous pouvez décider d’avoir un BOM front (avec votre framework front favori par exemple), un BOM back (avec hibernate) et un autre BOM avec des dépendances autres (Spring, …).

Ensuite, nous pouvons imaginer avoir un pom.xml comme ceci:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
  <groupId>fr.xebia.blog</groupId>
  <artifactId>pomB</artifactId>
  <version>1</version>
  <packaging>pom</packaging>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>fr.xebia.blog</groupId>
        <artifactId>bom-back</artifactId>
        <version>1</version>
        <scope>import</scope>
        <type>pom</type>
      </dependency>
      <dependency>
        <groupId>fr.xebia.blog</groupId>
        <artifactId>bom-spring</artifactId>
        <version>1</version>
        <scope>import</scope>
        <type>pom</type>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

Grâce à ce système de BOM, vous pouvez centraliser vos versions externes à un seul endroit. Si Spring sort une nouvelle version, seule la personne s’occupant des BOMs devra les mettre à jour. Ensuite, tous les projets dépendants n’ont pas besoin de savoir que Spring a évolué.

Pour ces projets, seule la version du BOM a évolué, et il suffit de l’impacter dans les pom.xml du projet.

Exemple concret : mise en place d’une bibliothèque de composants modulaires

Après la théorie, place à la pratique.
Cet exemple est tiré d’une expérience réelle. Avant de voir le code, voici le contexte et l’explication du choix du BOM.

L’équipe dans laquelle je travaillais a pour objectif de maintenir un ensemble de produits d’assurances et de pouvoir rapidement en développer de nouveaux.
Chaque produit est une webapp distincte mais doit avoir une ergonomie identique et une base technique identique.

Ainsi pour tous les composants front (mais également techniques), il a été décidé de mettre en place une bibliothèque maison proposant un ensemble de composants réutilisables. Un exemple de composant peut être un formulaire de saisie d’adresse normalisée par la Poste.

Les contraintes de ce framework sont :

  • Forte modularité pour permettre des mises à jour rapides
  • Permettre l’ajout de nouveaux composants facilement
  • Faciliter l’intégration dans les produits finaux en masquant au maximum la complexité de la bibliothèque

Pourquoi choisir le BOM dans la conception de la bibliothèque ?

Avant de s’orienter vers le BOM, nous avons étudié deux autres pistes plus communes que nous avons rejetées:

La release monolithique. Avec cette option, il n’y a qu’un seul trunk svn contenant l’ensemble des composants. Un pom parent porte le versionning de tous ces derniers. Ils sont releasés ensemble et évoluent donc de version ensemble.
Cette option est rejetée : tous les composants évoluent en même temps, ce qui empêche une maintenance facile et rapide. La bibliothèque propose plus d’une quarantaine de briques, la release prend donc du temps.
De plus, un développeur ne peut facilement toucher un composant. En effet, s’il commite ses modifications alors qu’une maintenance doit avoir lieu, ces dernières seront embarquées de manière non voulue (et potentiellement non visible).
Enfin le pom parent oblige les produits à l’utiliser, ce qui peut poser des problèmes.

La multiplication des trunks svn. Dans notre cas, étant donné que la release monolithique n’est pas une bonne option, pourquoi ne pas avoir un trunk svn par composant ? Cela élimine le problème du temps des releases et du pom parent.
Chaque composant étant releasé indépendamment, chaque produit doit donc connaitre l’ensemble des versions des composants, ce qui peut devenir complexe.

Nous voila donc arrivé à la solution du BOM. Comme vu plus haut, le BOM agit comme une dépendance classique, éliminant la contrainte liée au parent. Ensuite le BOM va agir comme un index de bibliothèque en portant l’ensemble des versions des différents composants. Ainsi, les produits ont besoin de connaitre uniquement la version du BOM et non l’ensemble des versions, masquant la complexité de la bibliothèque. Enfin l’ensemble est dans une seule structure svn, ce qui facilite la gestion et l’ajout de nouveaux composants (un répertoire à créer, et une version à ajouter dans le BOM).

Voyons maintenant comment créer cet index.

Tout d’abord commençons par la structure de répertoire. Comme dit plus haut, tout est dans la même structure svn, nous avons donc une structure simple:

|___bom-composant
|___composantA
|___composantB
|___composantC
|pom.xml

Je ne rentrerai pas dans les détails des composants, vous pouvez mettre ce que vous voulez dedans.
L’intérêt repose sur le bom-composant. C’est lui qui va être l’index de notre bibliothèque.
Ce répertoire ne contient qu’un seul pom comme suit:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>


    <groupId>fr.xebia.blog</groupId>
    <artifactId>bom-composant</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>${project.artifactId}</name>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>fr.xebia.blog</groupId>
                <artifactId>composantA</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>fr.xebia.blog</groupId>
                <artifactId>composantB</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>fr.xebia.blog</groupId>
                <artifactId>composantC</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

Ce pom.xml contient uniquement une section dependencyManagement, où vous trouverez la version de toutes les briques. Au départ, elles sont toutes en SNAPSHOT, mais cela va vite bouger.

Pour le moment rien de compliqué. Toute la subtilité de ce schéma réside dans le pom reactor de la bibliothèque (le pom à la racine) et la commande de release.

Voici ce pom un peu plus long que les précédents:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>


 <artifactId>reactor</artifactId>
    <version>1.9-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>${project.artifactId}</name>


    <profiles>
        <profile>
            <id>composantA</id>
            <modules>
                <module>composantA</module>
                <module>bom-composant</module>
            </modules>
        </profile>
        <profile>
            <id>composantB</id>
            <modules>
                <module>composantB</module>
                <module>bom-composant</module>
            </modules>
        </profile>
        <profile>
            <id>composantC</id>
            <modules>
                <module>composantC</module>
                <module>bom-composant</module>
            </modules>
        </profile>
    </profiles>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-release-plugin</artifactId>
                <version>2.4.2</version>
                <configuration>
                    <updateDependencies>false</updateDependencies>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project> 

 

Intéressons-nous d’abord au bloc profiles. Aucun profil actif par défaut. Ce comportement est voulu afin d’éviter l’abus de compilation complète à tout va (et perdre l’avantage de la modularité).
Chaque composant possède donc son profil d’activation afin de le compiler. Les profils contiennent tous le BOM, afin que ce dernier soit toujours à jour en cas de compilation.

Ainsi la commande Maven pour compiler un composant devient la suivante:

mvn clean install -PcomposantA

Voyons maintenant la configuration du plugin release. Veillez à bien prendre la dernière version (2.4.2), l’option utilisée ne fonctionnant pas dans les versions précédant la 2.3 (pourtant intégrée depuis la 2.0).

Dans la configuration, on ajoute l’option updateDependencies en la mettant à false. Cette option va permettre au BOM de fixer les versions qu’il porte. Sans cette option, au moment de la release, les versions des composants dans le BOM vont automatiquement passer à la version de développement supérieure.
Par exemple, lors de la première release, la version du composantA va passer de 1.0-SNAPSHOT à 1.1-SNAPSHOT. Ceci nous empêcherait de fixer nos versions.

Avec l’option activée, le composantA restera figé à 1.0.

C’est cela qui va nous permettre de modulariser notre bibliothèque. Si pour une raison ou une autre vous devez modifier le composantA, il suffit de passer la version à 1.1-SNAPSHOT dans le BOM, faire la modification et lancer la commande suivante pour fixer la version:

mvn release:prepare -PcomposantA release:perform

Le composantA est maintenant en 1.1 dans votre index de bibliothèque et les deux autres composants n’ont pas bougé.

Enfin pour utiliser l’index dans une webapp, il suffit de tirer la dépendance BOM comme suit:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>fr.xebia.blog</groupId>
            <artifactId>bom-composant</artifactId>
            <version>1.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

 

Puis d’indiquer dans vos dépendances celle que vous souhaitez utiliser (notez l’absence de version dans ces dépendances).

<dependencies>
    <dependency>
        <groupId>fr.xebia.blog</groupId>
        <artifactId>composantA</artifactId>
    </dependency>
    <dependency>
        <groupId>fr.xebia.blog</groupId>
        <artifactId>composantB</artifactId>
    </dependency>
    <dependency>
        <groupId>fr.xebia.blog</groupId>
        <artifactId>composantC</artifactId>
    </dependency>
</dependencies>

Vos webapps sont maintenant capables de bénéficier des composants sans se préoccuper des versions et peuvent bénéficier rapidement des correctifs de la bibliothèque en mettant à jour uniquement la version du BOM.

Si vous souhaitez voir l’exemple complet de code en action, je vous invite à voir les sources sur le github suivant: https://github.com/RNiveau/article-bom-maven

Voilà pour la notion de BOM avec un exemple concret. Ce système de bibliothèque permet de pouvoir modulariser vos frameworks tout en gardant une complexité faible pour les produits/webapp finaux. De plus, contrairement aux pom parents classiques, il est tout à fait possible d’imaginer tirer plusieurs BOM afin de bénéficier de plusieurs frameworks différents.

Le système de BOM peut être plus coûteux à mettre en place qu’un système plus classique de parents, mais sur des projets complexes en terme de dépendances ou sur un ensemble de projets, ce système s’avère être beaucoup plus évolutif et simple à maintenir.


Viewing all articles
Browse latest Browse all 1865

Trending Articles