My org has many spring microapps, on many different spring versions. We also have a security requirement of 0 Critical or High severity CVEs in our packages. This has meant an endless treadmill of opening CRs in dozens of repos to make the same dependency upgrades over and over again, just for new CVEs to show up a week later and force us to revisit repos we already hit. It’s obviously bad, and gradle has improved a lot since we last looked at this problem (like gradle 2).
My idea is to set up central dependency management with a BOM repo. Something that looks like this:
BOM build.gradle
plugins {
id 'maven-publish'
id 'java-platform'
}
description = "Spring apps BOM v3.2.11"
ext {
springBootVersion = '3.2.11'
springCloudVersion = '2023.0.3'
}
publishing {
publications {
mavenJava(MavenPublication) {
groupId = 'com.mygroup'
artifactId = 'spring-bom'
version = "${springBootVersion}"
from components.javaPlatform
}
}
}
javaPlatform {
allowDependencies()
}
dependencies {
api platform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}")
api platform("org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}")
constraints {
api 'org.yaml:snakeyaml:2.3'
api 'org.xmlunit:xmlunit-core:2.9.2'
// etc
}
}
Then the apps in our org just need to consume it without explicitly versioning their packages
app build.gradle
dependencies {
implementation platform("com.mygroup:spring-bom:3.2.11")
implementation 'org.slf4j:slf4j-api'
// niche package, versioning not supplied by the BOM, no big deal
// we'll add/"own" it if it gets enough widespread use
implementation 'com.nimbusds:nimbus-jose-jwt:9.37.3'
constraints {
implementation 'org.xmlunit:xmlunit-core:2.10.0' // devs maintain freedom
}
}
So far this is very promising, but here are my concerns:
– Supporting multiple versions
Our BOM is essentially us saying “just use these versions, and you won’t have to worry about it!” It’s implied that we’ve verified that the versions we’re recommending work well together.
So if app A is on springboot 3.2.4, and app B is on 2.5.2, we need to personally take responsibility for a com.mygroup:spring-bom:3.2.4
build and a com.mygroup:spring-bom:2.5.2
build, and every other version. It’s easier if we can simplify it to 3.2.x
and 2.5.x
but it’s still a lot of maintenance, and takes away some flexibility for our developers (e.g. “We’re being forced to constantly upgrade patch versions because the central BOM is designed for the latest one” — whether they should be doing that anyway is a separate matter).
Spring as a resource exists in part specifically to take care of this for us, so it’s hard to get away from the feeling that we’re doing a lot of rework.
– Nondeterministic builds
We sort of already deal with this in our central CI/CD templates which can change and mess up a project’s pipeline without the project itself changing at all, but I’m more nervous about it when we’re messing with the build itself.
We update the BOM for 3.2.11 so that it uses snakeyaml:2.3, and for 99% of our apps that doesn’t cause a problem, but for one it does, and they have no real way to figure out what changed or why. Easier to solve if you were working on that app last week. Harder if it’s been untouched for 9 months and now suddenly doesn’t build.
I have gotten the impression that I am not blazing any trails with this work, so I’m hoping there are some best practices that mitigate these issues (or others that I’m not foreseeing).