1. はじめに
こんにちは。Oisixプロダクト開発部の栗崎です。
ECサイト「Oisix」のバックエンドには、マイクロサービスアーキテクチャを採用した「第三世代アーキテクチャ」と呼んでいる基盤があります。従来のシステム世代と区別した呼び方で、これまでの取り組みは次の記事で紹介してきました。
- 負債解消Prjにおけるナビ運用CMS化の取り組み
- Oisix REBORNプロジェクトにおけるバックエンドチームでの開発の進め方
- Oisix技術スタック紹介(2025年度版)
- 第三世代バックエンド開発の紹介 - OpenAPI Generator を活用したAPI駆動開発
今回は、この第三世代アーキテクチャの11アプリケーションを対象に、Spring Boot 4 への移行とコードベースの整備を Claude Code と一緒に進めた話を紹介します。 なお、本対応は2026年春に実施した内容です。
2. 背景
2-1. 開発順序によるバラツキ
第三世代アーキテクチャの11アプリケーションは、開発順序の関係で構成や使用技術にバラツキがありました。
たとえば、後発のアプリケーションはOpenAPI Generator を活用したAPI駆動開発で紹介した OpenAPI Spec を Submodule で取り込む構成を最初から採用していますが、初期に作られたアプリケーションには適用されておらず、OpenAPI 生成コードを社内 Maven リポジトリから参照する古い構成のままでした。 Gradle のプロジェクト構成も同様で、初期のアプリケーションはマルチプロジェクト構成、後発は単一プロジェクト構成と、同じ第三世代の中で構成が混在していました。
これは、初期のアプリケーションが悪いわけではなく、開発を進める中で「ここはこうしたほうがいい」という知見が積み上がり、後発のアプリケーションにだけ反映されてきた結果です。 知見が標準になる一方で、先頭を走っていた初期のアプリケーションが取り残され、バラツキが生まれてきていました。
2-2. Spring Boot 4 のリリース
そんな中、2025年11月に Spring Boot 4 がリリースされました。 メジャーバージョンアップはすべてのアプリケーションを触る機会です。本対応では、Spring Boot 4 への移行と合わせて、これらのバラツキを後発アプリケーションの標準に揃える対応を一括で行うことにしました。
最初の記事で「走りながら考える」とともに、「現状の最善を積み上げる」「ドキュメントを残す」を挙げました。 現状の最善を積み上げてきた結果のバラツキを、残してきたドキュメントを頼りに揃え直す。今回の対応は、この方針の答え合わせのような位置づけでもあります。
3. やったことの全体像
3-1. 対象アプリケーション
対象は、第三世代アーキテクチャの11アプリケーションです。
- バックエンドAPI: 8アプリケーション
- BFF: 2アプリケーション
- APIテストランナー: 1アプリケーション
3-2. 対応項目
第三世代アーキテクチャで使用している技術はOisix技術スタック紹介(2025年度版)で紹介しています。 今回の対応項目は、この技術スタックを Spring Boot 4 に合わせて更新・整理するもので、全部で14項目ありました。ここでは主な10項目を挙げ、細かな対応は割愛します。 性質ごとに次の2つに分類できます。
- バージョンアップ適用: Spring Boot 4 とその周辺ライブラリのバージョンアップに追従するための対応
- コードベースの整備: 後発アプリケーションで標準になっていた構成を初期のアプリケーションに遡って適用する対応と、今回新たに整備した対応
バージョンアップ適用
| 対応項目 | 内容 | 対象 |
|---|---|---|
| Spring Boot 4 | 3系から4系へアップグレード (公式の移行ガイドに沿って対応) |
全アプリケーション |
| Gradle 9 | 8系から9系へ事前アップグレード(Spring Boot 4 は Gradle 8.14 以上が要件) | 全アプリケーション |
| Jackson 3 対応 | tools.jackson へのパッケージ移動、ObjectMapper から JsonMapper への移行 |
全アプリケーション |
| OpenAPI Generator の Spring Boot 4 対応 | useSpringBoot4 オプションの追加、生成コードへのパッチ |
該当アプリケーション |
| Doma | Spring Boot 4 対応バージョンへの更新 | 該当アプリケーション |
| テストライブラリ更新 | REST Assured 6.0、Spock 2.4-groovy-5.0 への更新 | 該当アプリケーション |
Spock は後述の通り最終的に JUnit 5 へ移行していますが、バージョンアップとテストの書き換えを同時に行うと問題の切り分けが難しくなります。 そのため Spring Boot 4 対応の時点ではまず既存の Spock テストが動く状態を保ち、JUnit 5 への移行は別ステップで行いました。
コードベースの整備
| 対応項目 | 内容 | 対象 |
|---|---|---|
| Submodule 化 | OpenAPI 生成コードを、社内 Maven リポジトリからの参照をやめ、リポジトリ内生成に切り替え | 該当アプリケーション |
| 単一プロジェクト化1 | Gradle のマルチプロジェクト構成を単一プロジェクト構成に再編 | 該当アプリケーション |
| Spock 廃止(JUnit 5 移行)2 | Spock 記法のテストコードを JUnit 5 記法へ書き換え | 該当アプリケーション |
| APIテスト整備 | APIテストが未整備だったアプリケーション向けに新規作成し、全アプリケーションで揃った状態にする | 該当アプリケーション |
どの項目がどのアプリケーションに必要かはそれぞれ異なるため、アプリケーションごとに対応項目の組み合わせが変わります。 この物量を Claude Code とどう進めたかを、次章で紹介します。
4. Claude Code との進め方
本対応は Claude Code を全面的に活用して進めました。 この章では、その進め方を紹介します。
4-1. 最初の1つで進め方を固める
最初から11アプリケーションを並べて進めることはせず、構成がシンプルなアプリケーションを1つ選んで、Claude Code と一緒に進め方を固めるところから始めました。 依存バージョンをどこまで上げるか、どのコードをどう直すか、何をどの順で確認するか。最初の1つで一通り経験し、対応内容・判明した注意点・ハマり所をすべて Notion の覚書ページに記録しました。
2つ目以降は、この進め方をベースに差分だけを考えれば済みます。 覚書も「最初のアプリケーションと共通の変更は同様に実施。異なる部分は次の通り」という差分形式で書き足していきました。
4-2. 覚書を育てながら横展開する
各アプリケーションの対応を始めるとき、まずこの覚書を Claude Code にインプットとして渡します。 Claude Code は過去のアプリケーションで判明したハマり所を踏まえた状態で作業を始めるので、同じ問題の調査を繰り返すことがありません。
対応中に新しく判明したこと(そのアプリケーション固有の構成による追加対応や、初めて踏んだ落とし穴)は、また覚書に追記します。 覚書は後のアプリケーションほど厚くなり、対応は後のアプリケーションほど速く正確になっていきました。
2章で触れた「ドキュメントを残す」は、もともと将来の自分たちのための方針でしたが、残したドキュメントがそのまま Claude Code へのインプットになりました。
4-3. レビューはチームで
生成されたコードのレビューは、チームメンバーに支えてもらいました。
レビューを成立させるために意識したのは、PR の分割です。 Gradle 9、Spring Boot 4、Submodule 化、単一プロジェクト化、テスト移行を、それぞれ独立した PR に分けました。一度に変えると問題の切り分けが難しくなるのは 3章の Spock の話と同じで、レビューする側にとっても「この PR では何が変わるのか」が明確になります。
PR を分割しても、Spock から JUnit 5 への移行のような大規模な書き換えは、多いもので1リポジトリあたり100クラス・600ケースを超える diff になります。行単位の人手レビューは現実的ではありません。
一方でこの移行は、テストの意味を変えずに記法だけを書き換える等価変換です。 そこで、等価であることを数字で示してレビューしてもらう形にしました。移行の前後で次を機械的に確認し、結果を PR 本文に載せます。
- テスト件数が変わらないこと
- テストカバレッジ(JaCoCo のレポート)が変わらないこと
- 本番コードに変更がないこと(
git diffでsrc/main/配下が空)
これに加えて、機械的な書き換えで起きがちな事故もチェックして件数を添えました。
たとえば Spock の result == expected を Java にそのまま持ち込むと、ただの式評価になり何も検証しないテストができあがります。こうした取りこぼしを grep で検出し、0件であることを示します。
レビュアーには、数字がすべて一致していれば等価な書き換えとみなして OK、差分がある場合は PR 本文に書かれた原因と見解の妥当性で判定してもらう、という方針で依頼しました。 この進め方自体も、事前にチーム内で合意を取ったうえで運用しています。検証手順と判定基準は覚書にまとめ、各 PR からは覚書を参照する形にしました。覚書は、チームで進め方を合意するための土台にもなっています。
人間のレビューを省くのではなく、人間が判断できる形に整える。Claude Code に大きな作業を任せるほど、この整え方が効いてきます。
5. 技術的なトピック
正直なところ、対応の多くは Claude Code に任せて淡々と消化され、苦労話はあまり残っていません(おぼえていません)。 その中で、人間側の設計判断や調査が必要になったトピックを2つ紹介します。
5-1. OpenAPI 生成ライブラリの Submodule 化
初期のアプリケーションは、OpenAPI Spec から生成したコードをライブラリとして社内 Maven リポジトリに publish し、それを参照する構成でした。 この生成済みライブラリが Spring Framework 7 に対応しておらず、Spring Boot 4 に上げるとそのままでは動きません。
ライブラリ側を Spring Boot 4 対応版として publish し直すこともできますが、それを選ぶと、参照する側とされる側でバージョンを同期させる運用が今後も続きます。 そこで、2章で触れた後発アプリケーションの標準である Submodule 構成への切り替えを、このタイミングで行いました。OpenAPI Spec を Submodule としてリポジトリに取り込み、コードをビルド時にリポジトリ内で生成する構成にすれば、外部ライブラリの互換性問題そのものがなくなります。
進める順序は、まず Submodule 化を行い、その後で Spring Boot 4 に上げる、という2段階です。 構成変更とバージョンアップを同じ PR に混ぜない進め方は、ここでも同じです。
5-2. Jackson 3 対応
Spring Boot 4 は Jackson 3 ベースです。
Jackson 3 ではパッケージが com.fasterxml.jackson から tools.jackson へ移動しますが、annotations だけは com.fasterxml.jackson.annotation のまま残ります。すべてが移動するわけではない、というのが最初のハマり所でした。
ObjectMapper は immutable な JsonMapper に変わりました。
ObjectMapper の Bean を直接定義してカスタマイズしていた箇所は、JsonMapperBuilderCustomizer でビルダーに設定を加える方式へ書き換えています。
一番実害が大きかったのは推移的依存です。
OpenAPI 生成コードが使う jackson-databind-nullable の古いバージョンは Jackson 2 専用で、Jackson 3 環境ではモジュールが効かず、レスポンスの JsonNullable フィールドが {"present": true} のような内部構造のままシリアライズされます。コンパイルは通り、起動もするため、テストを実行して初めて発覚しました。Jackson 2/3 両対応のバージョンに固定して解決しています。
こうしたハマり所は、最初に踏んだアプリケーションで覚書に記録し、2つ目以降は Claude Code が最初から回避してくれました。
6. テスト戦略
バージョンアップ対応のテストが新機能開発のテストと違うのは、「何が変わったか」ではなく「何も変わっていないこと」を示す必要がある点です。 この章では、移行前後の同一性をどう担保したかを紹介します。
6-1. 同じAPIテストを移行前後に当てる
検証の主軸は APIテストに置きました。 私たちの APIテストは、開発環境にデプロイされたアプリケーションへ外部から HTTP リクエストを送り、レスポンスをアサートする作りです。つまり、期待されるレスポンスはテストコード自体が持っています。
この性質を利用して、次の手順で同一性を検証しました。
- master ブランチ(Spring Boot 3)をデプロイし、APIテストを実行して GREEN を確認する
- develop ブランチ(Spring Boot 4)をデプロイし、同じ APIテストを実行して GREEN を確認する
- 両方 GREEN であれば、アサーションの粒度の範囲で移行前後の振る舞いは同一と判定する
移行のために新しいテストを書くのではなく、同じテストを両方のバージョンに当てる。 3章の「APIテスト整備」は、この判定方法を全アプリケーションで使えるようにするための布石でした。
6-2. 手動の結合テストは正常パターンだけ
API のバリエーションは APIテストで網羅されているとみなし、手動の結合テストは実際の画面操作からの正常パターン確認だけに絞りました。 全アプリケーションの異常系まで人手で回すコストと、APIテストがすでに担保している範囲を考えての判断です。
なお、各 PR の CI では、ユニットテストと WireMock + Docker Compose によるサービス結合テストが従来通り実行されています(この仕組みは開発の進め方の記事で紹介しています)。 ここで書いたのは、その上に重ねるリリース判定のためのテストの話です。
7. 段階リリース
11アプリケーションを一斉にリリースすることはせず、関連する機能単位で3つのグループに分けて、3回に分割してリリースしました。 グルーピングは、関連の深いアプリケーション同士をまとめること、そしてリスクの小ささを考慮して決めています。同じ機能に関わるアプリケーションをまとめてリリースすることで、リリース後の動作確認と問題発生時の切り分けの範囲が、そのグループ内に収まります。
段階的にリリースするということは、本番環境で Spring Boot 3 のアプリケーションと Spring Boot 4 のアプリケーションが混在する期間を許容するということです。 サービス間のやり取りは HTTP API で、その契約は OpenAPI Spec で固定されています。フレームワークのバージョン差が通信の互換性に影響しないため、混在を恐れずに分割できました。
順序は、リスクの小さいグループを第一弾にしました。 第一弾のリリース後は様子見の期間を置き、問題がないことを確認してから第二弾へ。第二弾で同じ構成のアプリケーション群に問題がなければ、第三弾は間隔を詰めて進める、という考え方で進めました。
各リリースの単位で、6章の APIテストによる検証と手動の結合テストを実施しています。
8. 数字で振り返り
本対応で作成してマージされた PR を、11リポジトリから集計しました3。
| 指標 | 値 |
|---|---|
| 対応 PR 数 | 62 |
| 変更行数 | +71,773 / −52,732 |
| 期間 | 2026年3月中旬〜6月下旬 |
| 本番リリース | 3回(6/3、6/17、6/22) |
数字を眺めると、削除行数が追加行数に迫る規模なのが分かります。 行数の多くは Spock から JUnit 5 へのテスト移行によるもので、テストコードを書き換えたぶん追加と削除の両方が立っています。単一プロジェクト化のようにファイルの移動が中心の対応は、触ったファイル数のわりに行数には現れません。
リポジトリごとの差も大きく、変更行数がもっとも少なかったアプリケーションは +287 / −107、もっとも多かったアプリケーションは +32,858 / −30,349 でした。 少なかったのはもともと後発の標準構成で作られていたアプリケーション、多かったのは初期に作られたアプリケーションです。この差が、2章で書いたバラツキの定量的な姿でもあります。今回の対応で、この距離はなくなりました。
9. 残課題と次のステップ
今回のスコープに含めなかった課題も残っています。
- Java 25 対応: 今回は Spring Boot 4 への移行を優先し、Java は 21 のままにしました。各リポジトリに Issue を作成済みで、次のバージョンアップ対応として進めます。
- IntegrationTest の拡充: APIテストは全アプリケーションに揃いましたが、リポジトリ内の IntegrationTest が未整備のアプリケーションが残っています。こちらも Issue 化済みです。
どちらも、また全アプリケーションを触る対応です。 ただ、構成のバラツキが揃った今は、最初の1つで進め方を固めて覚書で横展開する回し方が、差分の少ないぶん今回よりそのまま効きます。
今回の対応で得たものは、Spring Boot 4 に上がった11アプリケーションだけではありません。 揃ったコードベースと、育った覚書。Claude Code に大きな作業を任せて、人間が判断できる形で受け取るための前提が整いました。 バージョンアップと同時に AI駆動開発の土台ができたことが、今回の一番の成果だと考えています。
もっと仲良くなれました。(めでたしめでたし)
補足:実は12アプリケーションありました(1つ忘れられていました)
ここまで「11アプリケーション」と書いてきましたが、実は対象に含め忘れていたアプリケーションが1つあります。 忘れられていたことには以前から気づいていて、頃合いを見て対応するつもりでいました。この記事を書いているうちに、ちょうどいい題材だと思い直し、執筆の裏で Claude Code に対応を進めてもらうことにしました。
進め方は4章の回し方そのままで、育った覚書をインプットとして渡すところからです。 ついでに、PR を親子に積み上げていく Stacked PR も試してみました(執筆時点ではパブリックプレビューの機能です)。 レビューがこれからのため恩恵はまだ受けられていませんが、小さい単位で PR を積んでいくスタイルとの相性は良さそうです。
結果、この記事を書き終えるより先に、全対応分の PR ができあがりました。かかった時間は2〜3時間ほどです。11アプリケーションで育てた土台が12個目にそのまま効くことが確認できました。
