この記事では、Javaアプリケーション開発において、ライブラリ管理を効率化する手段としてBOM (Bill of Materials) の生成と利用方法について説明します。
BOMを利用するとversionが一元管理でき、アプリケーションごとに最適なversionが何か悩まなくて良くなります!
この記事を読むとこんな課題が軽減されます!
・どのversionを使えばいいのか迷う
・脆弱性対応でのversion upがめんどう
BOMの生成方法
この記事では、Gradleを用いたBOMの生成方法をご紹介します。
BOMを生成するリポジトリはbuild.gradle
のみ以下のように記述すればOKです!
plugins {
// bom生成に必要なプラグイン
id 'java-platform'
id 'maven-publish'
}
group = 'hirabay.bom'
version = '0.0.1'
javaPlatform {
allowDependencies()
}
repositories {
mavenCentral()
}
dependencies {
// constraintsでくくるとbom利用者側でversion指定がいらなくなる
// ライブラリを使用していない場合は何も起きない(勝手にライブラリが追加されたりしない)
constraints {
api 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.2'
api 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.2'
}
// bomの中にbomを含めることも可能
api platform('org.springframework.boot:spring-boot-dependencies:3.0.8')
api platform('org.springframework.cloud:spring-cloud-dependencies:2022.0.3')
}
publishing {
publications {
mavenJava(MavenPublication) {
group = 'hirabay.bom' // 未指定の場合、プロジェクトのgroupを使用
artifactId = 'sample-bom' // 未指定の場合、プロジェクト名を使用
version = '0.0.1' // 未指定の場合、プロジェクトのversionを使用
from components.javaPlatform
}
}
}
この例で publishタスクを実行すると、hirabay.bom:sample-bom:0.0.1
が利用可能になります!
※publishのこれ以上の設定は、publish先によって変わるため省略させていただきます。。
BOMを利用する
bomの利用はgradleの標準機能でも可能なのですが、io.spring.dependency-management
プラグインが便利です!
plugins {
...
// BOMの利用が便利になるプラグイン
id 'io.spring.dependency-management' version '1.1.0'
}
...
repositories {
mavenCentral()
mavenLocal() // お試し検証ではMavenLocalを使うとスムーズ
}
// 利用するBOMを定義する(bomを追加したい場合は、独自のbomに内包させると便利)
dependencyManagement {
imports {
mavenBom "hirabay.bom:sample-bom:0.0.1"
}
}
dependencies {
// BOMに含まれるライブラリであれば、versionを記載しなくて良い
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
versionは指定していませんが、bom側で定義したversionでライブラリが取り込まれていることが確認できます!
※Intellij画面(左:External Libraries、右:build.gradle)
dependencyManagementというタグでbomをimportします。
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>rhirabay.bom</groupId>
<artifactId>maven-user</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>maven-user</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<!-- bomに定義されているライブラリはversionを記載しなくて良い -->
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- dependencyManagementタグでbomを定義-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>hirabay.bom</groupId>
<artifactId>sample-bom</artifactId>
<version>0.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
versionは指定していませんが、bom側で定義したversionでライブラリが取り込まれていることが確認できます!
※Intellij画面(左:External Libraries、右:pom.xml)
特定のアプリケーションの特定のライブラリだけ別versionを使う
bomによりライブラリのversionの一元管理ができましたが、場合によってはbomで定義したversion以外を使用したいケースもあると思います。
そんな時は、bom利用前と同様に例外的なライブラリだけversionを指定すればOKです!
※Intellij画面(左:External Libraries、右:build.gradle)
※Intellij画面(左:External Libraries、右:pom.xml)
メインフレームワークのversionごと、複数のbomを生成する
例えば、メインフレームワークとしてSpringBootを使っている場合、minor versionごとにサポートversionが異なるライブラリがあります。
そんなとき1つのリポジトリで複数のversionに対応したbomが生成できると便利ですよね!
そこで先ほど紹介したbomの生成方法を少しアレンジして、複数のversionに対応できるようにしてみます。
ext {
// ライブラリのversionをmap形式で定義(keyはSpringBootのminor version)
versionMap = [
'org.mybatis.spring.boot:mybatis-spring-boot-starter': [
'2.7': '2.2.+',
'3.0': '3.0.+'
],
'org.mybatis.spring.boot:mybatis-spring-boot-starter-test': [
'2.7': '2.2.+',
'3.0': '3.0.+'
],
]
// bomのversionをmap形式で定義(keyはSpringBootのminor version)
bomVersionMap = [
'org.springframework.boot:spring-boot-dependencies': [
'2.7': '2.7.+',
'3.0': '3.0.+'
],
'org.springframework.cloud:spring-cloud-dependencies': [
'2.7': '2021.0.+',
'3.0': '2022.0.+'
],
]
}
dependencies {
constraints {
// mapからライブラリを展開
// SpringBootに依存しないライブラリを利用する場合を考慮し、
// 「*」を指定するとminor versionに関係なく組み込み可能にしておく
for (lib in versionMap.keySet()) {
if (versionMap[lib]['*'] != null) {
api "${lib}:${versionMap[lib]['*']}"
} else if (versionMap[lib][springBootVersion] != null) {
api "${lib}:${versionMap[lib][springBootVersion]}"
}
}
}
// mapからbomを展開
// SpringBootに依存しないbomを利用する場合を考慮し、
// 「*」を指定するとminor versionに関係なく組み込み可能にしておく
for (bom in bomVersionMap.keySet()) {
if (bomVersionMap[bom]['*'] != null) {
api platform("${bom}:${bomVersionMap[bom]['*']}")
} else if (bomVersionMap[bom][springBootVersion] != null) {
api platform("${bom}:${bomVersionMap[bom][springBootVersion]}")
}
}
}
publishing {
publications {
mavenJava(MavenPublication) {
group = 'hirabay.bom'
artifactId = 'sample-bom'
// versionを日時を使って自動で生成
def date = new Date()
def format = new SimpleDateFormat("yyyyMMddHHmmss")
version = "${springBootVersion}.${format.format(date)}"
from components.javaPlatform
}
}
}
また、gradle.properties
にデフォルトの変数springBootVersion
を設定しておきます。
springBootVersion=3.0
これで準備はOK!
以下のようにspringBootVersionを指定することで、複数のbomを生成できるようになりました!
$ ./gradlew publish -PspringBootVersion=2.7
$ ./gradlew publish -PspringBootVersion=3.0
ただ、ブランチ間のライブラリの生合成を揃えるのが大変に感じるので
個人的には1ブランチ内で複数version対応できた方が管理が楽に感じます。