Meer met Maven – Bills of Materials

In elke editie zal Robert Scholte een probleem voorleggen en deze oplossen met behulp van Apache Maven om meer inzicht te geven in Maven zelf en de vele beschikbare plugins.

Door: Robert Scholte

Bills of Materials (BOM)

Een aantal keren heb ik naast Ray Tsang op heb podium gestaan om hem tijdens zijn presentatie “Surviving Dependency Hell With Apache Maven” te ondersteunen. Hierin komen tal van voorbeelden voorbij van dependency gerelateerde problemen. Doel is voornamelijk om inzicht te geven in hoe dependency resolution en de classpath werken.

Eén van de adviezen is om ervoor te zorgen dat versies van een bepaalde groep dependencies altijd in sync zijn. Denk bijvoorbeeld aan de dependencies van org.springframework, deze horen allemaal dezelfde versie te hebben.

De klassieke manier om dit op te lossen is met een placeholder voor de versie:

   <properties>
      <spring.version>1.0.0</ spring.version >
   </properties>
   <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
      </dependency>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
      </dependency>
     …
    </dependencies>
  </dependencyManagement>

Het goede aan deze oplossing is dat je op één plek voor alle dependencies kunt bepalen welke versie je wilt gebruiken. Toch kan het helaas nog steeds gebeuren dat je dependencies van org.springframework met een andere versie binnen krijgt. Stel een dependency gebruik spring-jdbc versie 2.5.6, terwijl jij spring.version op 5.2.4.RELEASE hebt en spring-jdbc niet in dependencyManagement hebt staan. In zo’n geval krijg je de oude versie van de spring-jdbc jar.

Kern van het probleem is, dat je nooit zeker kunt weten of de lijst met managed dependencies compleet is. Er zijn uiteindelijk twee oplossingen mogelijk: met de requireSameVersion rule van de maven-enforcer-plugin kun je de build laten falen als de geconfigureerde dependencies van versie verschillen.

<requireSameVersions>
  <dependencies>
    <dependency>org.springframework</dependency>
  </dependencies>
</requireSameVersions>

Dit is echter meer een pleister dan een solide oplossing. Een mooiere oplossing in de Bills Of Materials, kortweg bom-file. Zo’n bom-file is een pom, maar dan met een uitputtende lijst van managed dependencies die qua versie bij elkaar horen. Je zou deze lijst kunnen copy/pasten in je eigen pom, maar eenvoudiger is het om de bom-file als volgt op te nemen:

<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-framework-bom</artifactId>
        <version>5.2.4.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

Let er vooral op dat dit een managed dependency is. Naast de groupdId, artifactId en version zie je 2 elementen: type  en scope. Voor dependencies is de default type ‘jar’, maar die willen we niet: vandaar dat hier pom staat. En de scope moet ‘import’ zijn, zodat Maven weet dat hij het blok ‘dependencyManagement’ uit de pom-file moet overnemen.

Dit betekent wel, dat de ontwikkelaars van dat project (in dit geval springframework) tevens een bom-file moeten leveren. En zo’n bom bevat alleen(!) de set van modules, die bij elkaar horen. De volgorde van deze import-scoped dependencies maakt uit, dus als er ineens een gedeelde library in staat (bijv. slf4j-api) dan kan het vervelende gevolgen hebben.

Voor meer tips, klik hier.