はじめに
こんにちは。アソビューでプロダクト開発に携わっているエンジニアの進藤です。日々の開発ではアプリケーションコードだけでなく、依存ライブラリの更新や CI/CD の設定、ビルド周りの調整など、基盤となる部分に触れる機会も少なくありません。
特に複数モジュールから構成される中規模〜大規模なプロダクトでは、「どの依存がどこで使われているのか」「どのバージョンが正なのか」が分かりづらくなり、アップデート時の混乱につながることがあります。
私の担当範囲でも、長年運用してきた依存の棚卸しが必要になり、そのタイミングで Version Catalog を導入することになりました。名前は前から知っていたものの、実務で触るのは初めてです。実際に使ってみると便利な一方で、「これは仕組みを理解していないと意外とつまずきやすいな」と感じた部分も多くありました。
この記事では、Version Catalog を初めて実務で扱った立場として、「何が分かりにくかったか」「どこが便利だったか」といった実務寄りの観点で、アップデートの中で得た気づきをまとめています。技術仕様を深く掘り下げるのではなく、実際に触った際の感覚やポイントを中心に紹介します。
背景
私が所属している事業部のプロダクトは、複数のサブモジュールで構成された比較的大きめのシステムで、10年近く運用を続けてきた経緯もあり、依存ライブラリや設定が古いまま残っている箇所がいくつか存在します。開発を続ける中で新しい依存を追加したり、サブモジュールごとに個別で設定を行ったりしてきた結果、どのライブラリがどこで参照されているかが徐々に見えにくくなっていました。
複数のモジュールが共存しているプロジェクトでは、運用が長引いてくると以下のような状況が起こりがちです。
- 依存バージョンがモジュールごとにバラバラ
- transitive で入ってきたライブラリのバージョンが意図せずプロジェクト全体に影響
- plugin の定義もモジュールごとに書き方が異なる
- BOM に任せている箇所と手書きでバージョン指定している箇所が混在
※ BOM(Bill of Materials)とは、Spring Boot などが提供している「依存ライブラリの推奨バージョンをひとまとめに管理する仕組み」のことです。プロジェクト側でバージョンを個別に書かなくても、BOM が安全なバージョン組み合わせを提供してくれます。
小さな変更では問題が表に出にくいものの、ライブラリの更新や設定のメンテナンスを行うタイミングでは「どこを直せば影響を最小限にできるのか」が判断しづらく、作業の見通しを立てるのが難しくなることがありました。
こうした依存管理のばらつきを整理するために、「プロジェクト全体の依存は Version Catalog で統一して管理する」という方針が決まりました。モジュールごとに散らばった依存定義を集約し、管理の負担を減らすためです。
Version Catalog を扱うのは私自身初めてでしたが、依存の整理という課題に向き合う中で、そのメリットとつまずきやすいポイントが見えてきました。
Version Catalog の概要
Version Catalog は、Gradle の依存管理を一箇所に集約するための仕組みです。libs.versions.toml にライブラリ、バージョン、plugin などを定義しておき、各モジュールは libs.xxx の形式で参照します。これで、プロジェクト全体で依存の書き方を統一できます。project-root/
├── settings.gradle
│ └── versionCatalogs {
│ create("libs") {
│ from(files("gradle/libs.versions.toml"))
│ }
│ }
│
├── gradle/
│ └── libs.versions.toml ← 依存とバージョンの“単一情報源”
│
├── build.gradle (or build.gradle.kts)
│ ├── 共通の plugin / dependency 設定があればここに記述
│ └── subprojects / allprojects でモジュール共通設定を適用
│
└── modules/
├── module-a/
│ └── build.gradle
│ ├── dependencies {
│ │ implementation(libs.spring.boot.starter.web)
│ │ implementation(libs.jackson.databind)
│ │ testImplementation(libs.junit.jupiter)
│ │ }
│ └── plugins {
│ alias(libs.plugins.spring.boot)
│ }
│
├── module-b/
│ └── build.gradle
│ ├── dependencies { ... 同様に libs.xxx を参照 }
│
└── module-c/
└── build.gradle
├── dependencies { ... 同様に libs.xxx を参照 }対比として、従来の構成は以下のようなイメージです。
project-root/
├── module-a/
│ └── build.gradle
│ ├── implementation("spring-boot-starter-web:2.7.4")
│ ├── implementation("jackson-databind:2.10.1")
│ └── testImplementation("junit-jupiter:5.7.0")
│
├── module-b/
│ └── build.gradle
│ │ ↓↓↓ バージョンがズレる
│ ├── implementation("spring-boot-starter-web:2.6.7")
│ │ ↓↓↓ モジュールごとにバラバラ
│ ├── implementation("jackson-databind:2.9.8")
│ └── testImplementation("junit-jupiter:5.7.1")
│
└── module-c/
└── build.gradle
│ ↓↓↓ バージョン未指定(transitive)
├── implementation("spring-boot-starter-web")
├── implementation("jackson-databind:2.10.1")
└── testImplementation("junit-jupiter:5.8.0")比較すると導入のメリットがわかりやすいですが、従来はモジュールごとに「バージョンを直接記述する」「BOM に任せて記述しない」といった複数のパターンが混在します。この状況がアップデート時に「どこを直すべきか」を判断しづらくなる状況をもたらします。
Version Catalog は使い方こそシンプルですが、「依存がどこで定義されているのか」を明確にするという基盤としての役割が大きいです。アップデート対応が多いプロジェクトほど、効果を感じやすい仕組みだと感じました。
Version Catalog を使うときの注意点
Version Catalog は概念としては単純ですが、実際の作業を通じて気をつけた方がよい点があると感じたので、ここでご紹介しておきます。BOM と Version Catalog を混在させない
Version Catalog は仕組みとしてはシンプルですが、実際にプロジェクトへ適用していく中で「ここは最初に決めておいた方がよい」と感じたポイントがありました。ここでは、特につまずきやすい点を紹介します。BOM が提供するバージョン管理と、catalog 側の version.ref を同じ依存に対して併用してしまうと、「どちらが最終的なバージョンを決めているのか」 が分かりづらくなり、後で整合性が崩れやすくなります。
実際の作業でも、次のような状況で混乱が生まれました。
- BOM が管理している jackson や jdbc などの依存に対して、catalog 側にも手動で version.ref を書いてしまう
- BOM を更新したつもりが、catalog の古い version.ref が残っていて意図しないバージョンが使われる
- モジュールごとに参照するバージョンの出どころが違い、依存の衝突を引き起こす
こうした境界の曖昧さは、次のような形で具体的な不具合として現れることがあります。
- コンパイルエラー(cannot find symbol など)
- ビルド時に失敗する(BOM と catalog の両方が異なるバージョンの同じライブラリを引っ張り、衝突が起きる)
- 実行時に例外が発生する(ClassNotFoundException など)
このような問題を回避するためには、計画時にあらかじめ依存管理における責任管理を明確に線引きしておくことで、混乱を防ぎやすくなると思います。
依存管理は「どこで何を面倒見るか」を決めるだけで作業の進めやすさが大きく変わります。Version Catalog を導入するときは、まずこの線引きから始めるのが個人的にもおすすめです。
以下、例)
BOM に任せるもの:Web フレームワーク、JSON ライブラリ、JDBC など、BOM が一式で整合性を保証しているもの
Version Catalog で管理するもの:プロダクト固有の依存、フレームワークが関与しない周辺ライブラリ、plugin のバージョン、社内ライブラリなど
BOM と Version Catalog を混在させないための具体例
先ほど挙げたような混乱を避けるには、「BOM が面倒を見る依存」と「Catalog が面倒を見る依存」を分けて定義するというルールを最初に決めておくことが有効です。実際の記述としては、次のような分け方を徹底するだけで、依存の衝突が起きにくくなります。BOM に任せる依存は、catalog 側ではバージョンを定義しない例
// build.gradle(BOM が管理する依存)
dependencies {
// ← BOM が適切なバージョンを提供
implementation(platform("org.example:example-bom:1.2.3"))
// ← バージョンは書かず BOM に任せる
implementation("org.example:example-core")
implementation("org.example:example-utils")
}Catalog 側で管理する依存は version.ref を定義する例
# libs.versions.toml
[versions]
kotlin = "1.9.23"
[libraries]
kotlin.stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" }
// build.gradle
dependencies {
implementation(libs.kotlin.stdlib) // catalog 管理の依存
}この書き分けで防げること
- 同じ依存を BOM と catalog が別々のバージョンで管理してしまう問題
- バージョン衝突による コンパイルエラーやビルドエラー
- 実行時の ClassNotFoundException の発生
- 「どこでバージョンが決まっているのか分からない」状態の発生
以上はあくまで一例ですが、BOM と Catalog の役割を明確に分けたうえで書き方を統一しておくと、大規模な依存整理や将来のアップデートでも対応時の迷いを軽減できるようになります。
最後に
依存管理は日々の開発の中では後回しになりがちな部分ですが、プロジェクトが長く続くほど無視できない課題になります。Version Catalog を取り入れたことで、「依存がどこで定義されているのか」「どの単位で管理するのか」といった基盤部分をあらためて見直す機会になりました。今回の記事では、技術仕様の細かい説明ではなく、実務で触れた際に感じた“扱いやすさ”や“つまずきポイント”を中心にまとめました。複数モジュールを扱うプロジェクトや、依存の棚卸しを検討している方にとって、今回の内容が少しでも参考になれば幸いです。
Version Catalog は単なる仕組みの置き換えではなく、プロジェクトを長く維持していくための基盤づくりにもつながると感じています。これから導入する方にとって、良いスタートの助けになればうれしいです。
PR
アソビューでは、より良いプロダクトを社会に届けるために、日々挑戦を続けています。私たちと一緒に、仕組みで“遊び”を支えるエンジニアリングに取り組んでみませんか。
カジュアル面談も歓迎です。お気軽にご応募ください。