Dependencies BOM

java

Managing and versioning a bunch of connected libraries or services is hard. It doesn’t matter if you are a consumer or a provider of it. In this post, I’m exploring two simple solutions which might come in handy if you develop an ecosystem of libraries/services or when you are a consumer of those. It is all about semantic versioning and import scope of BOM in maven dependencies.

tl;dr

Semantic Versioning

I’ve been working on few projects in my career and sometimes I was in the team providing libraries or services and sometimes I was a consumer of services provided by others. What is scary is that every single time we did some weird hacks to handle dependencies management. When I was on the team providing services we usually deployed them and we send an email to a bunch of people with information that we’ve deployed something and that they can update. When I was in a team consuming those services I usually got angry with every release because I was forced to bump a bunch of dependencies and track what has changed and what will be the impact of those changes on my project.

With time I’ve learned about semantic versioning which is a very convenient way of managing versions of the software.

Long story short. Your release version is basically X.Y.Z.

  • X is for major breaking changes (API change, which breaks the contract)

  • Y is for a minor change, for example, new feature but backward compatible

  • Z is for bug fixes

Bill Of Materials

Next, I’ve learned about import scope for maven dependencies which allows users to easily upgrade all the dependencies they are using. Semantic versioning is simple to understand. BOM usage is not so complicated but it is easier to explain using more concrete examples.

Let’s say we are working on a platform which provides bunch of libraries (or services, doesn’t really matter) and as a platform we release it all because it is more convent for us to work this way (been there and understand that in some cases it really is, at least for one side…). Let’s say we want to be good for the users, that’s why we are using semantic versioning, and provide some convenient way for the users to use our services. We can send an email to the users and politely ask them to update all the dependencies we’ve changed, or we can do something better and update all the dependencies for them, and ask them to bump only one dependency instead. That’s when maven’s BOM (Bill Of Materials) and import scope comes into play.

Let’s say that as an organization we provide several services:

<!-- discount-api/pom.xml -->
<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>com.pchudzik.blog.example.bom.system.discount</groupId>
    <artifactId>discount-api</artifactId>
    <packaging>jar</packaging>
    <version>4.2.0</version>
    <!-- ... -->
</project>

<!-- order-api/pom.xml -->
<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>com.pchudzik.blog.example.bom.system.order</groupId>
    <artifactId>order-api</artifactId>
    <packaging>jar</packaging>
    <version>2.1.3</version>
    <!-- ... -->
</project>

<!-- product-api/pom.xml -->
<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>com.pchudzik.blog.example.bom.system.product</groupId>
  <artifactId>product-api</artifactId>
  <packaging>jar</packaging>
  <version>3.2.1</version>
  <!-- ... -->
</project>

<!-- user-api/pom.xml -->
<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>com.pchudzik.blog.example.bom.system.user</groupId>
  <artifactId>user-api</artifactId>
  <packaging>jar</packaging>
  <version>3.7.8</version>
  <!-- ... -->
</project>

As a responsible team we can (and should) provide something extra:

<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>com.pchudzik.blog.example.bom</groupId>
    <artifactId>system-bom</artifactId>
    <packaging>pom</packaging>
    <version>5.21.2</version>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.pchudzik.blog.example.bom.system.discount</groupId>
                <artifactId>discount-api</artifactId>
                <version>4.2.0</version>
            </dependency>
            <dependency>
                <groupId>com.pchudzik.blog.example.bom.system.order</groupId>
                <artifactId>order-api</artifactId>
                <version>2.1.3</version>
            </dependency>
            <dependency>
                <groupId>com.pchudzik.blog.example.bom.system.product</groupId>
                <artifactId>product-api</artifactId>
                <version>3.2.1</version>
            </dependency>
            <dependency>
                <groupId>com.pchudzik.blog.example.bom.system.user</groupId>
                <artifactId>user-api</artifactId>
                <version>3.7.8</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

Now every user of our small ecosystem can simply add BOM to the dependency management and forget about updating versions of each library he is using:

<!-- project/pom.xml -->
<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>com.pchudzik.blog.example.bom.project</groupId>
    <artifactId>project</artifactId>
    <packaging>pom</packaging>
    <version>1.0.0</version>

    <modules>
        <module>project1</module>
        <module>project2</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.pchudzik.blog.example.bom</groupId>
                <artifactId>system-bom</artifactId>
                <version>5.21.2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>


<!-- project/project1/pom.xml -->
<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>

    <parent>
        <groupId>com.pchudzik.blog.example.bom.project</groupId>
        <artifactId>project</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>project1</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>com.pchudzik.blog.example.bom.system.user</groupId>
            <artifactId>user-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.pchudzik.blog.example.bom.system.product</groupId>
            <artifactId>product-api</artifactId>
        </dependency>
    </dependencies>
</project>


<!-- project/project2/pom.xml -->
<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>
    <parent>
        <groupId>com.pchudzik.blog.example.bom.project</groupId>
        <artifactId>project</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>project2</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>com.pchudzik.blog.example.bom.system.order</groupId>
            <artifactId>order-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.pchudzik.blog.example.bom.system.discount</groupId>
            <artifactId>discount-api</artifactId>
        </dependency>
    </dependencies>
</project>

Summary

This is nothing new. It’s been in maven since version 2.0.9, what is more, big players are using it - spring (docs). Too good to be real? Maybe. It will work perfectly in some cases and might be very problematic in others. The important thing to remember is that there is an option. This is not a silver bullet, but it would’ve saved me a lot of time in the past if I’d known about it…

See Also

If you've enjoyed or found this post useful you might also like:

18 Apr 2018 #mvn