Maven の依存関係を Git への切り替え時に解決する
Matt Shelton
開発者アドボケート
そう、Git への移行を進めている私たちが夢中になっているのが git-flow です。ここまで来たら、何もかもテストしてみましょう! 何といっても自慢のチームです。彼らは Confluence にある開発ワークフローのヒット リスト、つまりこれまでのチームの開発をもとにしたすべてのワークフローと、将来取り組む必要が生じる可能性があると考える悩ましいワークフローのすべてをかき集めました。そして、私たちのプロジェクトをミラーリングしたプロジェクト構成 (もちろんコードは一切なし、pom.xml 一本) によって、すべてのワークフローを試してみました。
Maven の依存関係は、このすべての中でも最大の問題であることがほぼ明らかになりました。
Maven のビルド番号設定 Maven はリリースするまでは 1.0.0-SNAPSHOT
ビルドを生成します。リリースすると、-SNAPSHOT
が削除されてバージョンは 1.0.0
になります。ビルドのプロセスはその後のマイナー バージョンをインクリメントする仕組みに対応している必要があり、その結果、次に取り組む作業で生成されるビルドは 1.1.0-SNAPSHOT
のようになります。これは 3 桁以外でも構いません、この桁数はプロジェクトを開始する際に定義できます。ここでは 3 桁を使用しています。とにかく、-SNAPSHOT の部分の意味を理解しておくことが非常に重要です。この部分は常にプロジェクトの最新のリリース前バージョンを示すことになります。
アーティファクト
これらすべてのワークフローで大きな関心事となったのは、プロジェクトのバージョンとプロジェクト間の依存関係を適切に管理する方法でした。
ビルドで Maven の依存関係が検出されるたびに、初期設定ではその依存関係をインターネットからダウンロードします。これらのアーティファクトはローカルに保存されるため、その後のビルドはより速く実行されます。この手順にかかる手間を少し省く 1 つの方法が、ローカル ネットワーク上のアーティファクト リポジトリを外部依存関係用のローカル キャッシュとして使用する方法です。最も速い CDN からダウンロードするより、LAN による取得の方が大体いつも速くなります。私たちは Artifactory Pro をアーティファクト用リポジトリとして使用しています。また、マルチモジュール構成であるため、チームのビルド アーティファクトも Artifactory に保存しています。共通のパッケージの 1 つをビルドする時に、その特定のバージョンを maven dependency resolution を介してダウンロードして、アーティファクト リポジトリからアーティファクトをすぐに取得できます。
このやり方はとてもうまく機能します。また、Artifactory はアーティファクトをインスタンス間で同期してくれるため、たとえば Artifactory によってリリース リポジトリを本番デプロイ用にデータセンターに複製する場合は、別のプロセスをビルド不要でこの作業を実行できます。
関連資料
Git リポジトリ全体を移動する方法
ソリューションを見る
Bitbucket Cloud での Git の使用方法についてのチュートリアルです。
Maven の依存関係、フィーチャー ブランチ、プル リクエスト
すべてのビルドは Artifactory に格納されます。SVN では、最新の 2 つのスナップショット ビルドを保持するためにスナップショット リポジトリを使用しており、承認前のリリース ビルドに対してはステージング リポジトリを、本番環境に移行できるビルドに対してのみリリース リポジトリを使用していました [1]。前述のようにこれらのビルドは番号が付けられて、リポジトリとバージョンに基づいて予測可能な URL パターンで取得できます。
それぞれの開発者が使用する基本的なワークフローは開発ブランチから作業用のフィーチャー ブランチを作成して、作業が完了したらプル リクエストを発行して開発ブランチに作業をマージして戻すという流れでした。単独のプロジェクトの場合にこのワークフローはほとんど問題なく機能しますが、ここで待っていたのが私たちが最初にぶつかった問題であり、そのために移行全体を真剣に再検討することになった問題です。これからそれをご説明します。
前述のように、プロジェクト間には複数の層の依存関係があります。これには、アトラシアン製品の歴史的にも戦略的にも非常に正当な理由があります。この問題を解消するため代替アーキテクチャを検討してきましたが、それによって他の問題が生じる可能性がありました。自分達にとって便利にできますが (そうした事実もありますが後の記事で紹介します)、現時点では戦略として今の構造をそのままにしておくことにします。
では、開発者 A を Angela として、Jira でフィーチャーを利用してみます。これには共通プロジェクトからと、ProductX からの 2 つのブランチが必要です。共通のバージョンは 2.1.0-SNAPSHOT です。ProductX のバージョンは 2.4.0-SNAPSHOT です。Angela はしばらくローカルで作業しますが、最終的には Bitbucket Data Center にプッシュ バックします。Bamboo がこれらの変更を取り込んで、共通パッケージをビルドして common-2.1.0-SNAPSHOT を Artifactory にアップロードし、次に common-2.1.0-SNAPSHOT に依存する ProductX をビルドして productX-2.4.0-SNAPSHOT もアップロードします。ユニット テストには合格します。
開発者 B は Bruce として、Jira にある別のフィーチャーにおける作業を別の製品 ProductY のために開始します。これにも共通プロジェクトから、および ProductY からの 2 つのブランチが必要です。共通のバージョンは上と同じように 2.1.0-SNAPSHOT です。ProductY のバージョンは 2.7.0-SNAPSHOT です。Bruce はしばらくローカルで作業しますが、最終的には Bitbucket Data Center に変更内容をプッシュ バックします。Bamboo がこれらの変更を取り込んで、共通パッケージをビルドして common-2.1.0-SNAPSHOT を Artifactory にアップロードし、次に common-2.1.0-SNAPSHOT に依存する ProductX をビルドして productX-2.4.0-SNAPSHOT もアップロードします。ユニット テストには合格します。
そのうち Angela は ProductX コードに小さなバグを見つけて、修正を検証するためのユニット・テストを記述します。それをローカルで実行すると合格します。Angela が自分の変更点を Bitbucket にプッシュすると、Bamboo がその変更点を取り込んで ProductX をビルドします。ビルドは成功しますが、Angela の一部のユニット・テストが失敗します。それは Angela が新しく記述したものではなく、最初にフィーチャーに加えた変更です。Bamboo ビルドでは Angela のローカルのビルドで実行されなかった回帰を検出したのでしょうか?どのような方法が可能なのでしょうか?
Bamboo が ProductX をビルドした際に取り込んだ Angela の共通の依存関係は、もはや Angela のものではなかったのです。Bruce がフィーチャーのビルドを完了した際に、common-2.1.0-SNAPSHOT を Artifactory で上書きしていました。ソース コードの競合はありませんでした。双方の開発者がそれぞれのブランチで隔離された状態で作業していましたが、Maven のアーティファクト取得のための信頼できる情報源は破損していました。
さっぱり訳が分からない。
この問題が見つかってから約 1 か月間、これを回避するためにあらゆることを試みました。アトラシアンの TAM[2] を通じて git-flow を使用している Bamboo チームと検討のうえ、git-flow の Java 実装である git-flow を管理している開発者にも相談しました。どちらのサポートも非常に役立ちましたがプロセスが不足しており、フィーチャーを使用するたびに開発者が手動の手順リストで処理する必要があるため、許容できる範囲の解決先は見つかりませんでした。
何を検討したかというと、次が私たちが試したすべてです。
1. ブランチ作成時のバージョン番号、またはその直後のバージョン番号を変更する。
mvn jgitflow:feature-start
によってブランチを作成すると番号を変更できます。- Bitbucket Data Center のフックまたはローカルの githook を使用できます。
- ブランチを作成した後は、
mvn version: set-version
で手動で設定できます。 - この変更は [maven-external-version] プラグインで自動化できます。
2. ブランチ作業を完了して開発ブランチにマージする際に、バージョン番号を変更する。
mvn jgitflow:feature-finish
によってブランチを終了すると番号を変更できます。- git merge ドライバーによって pom の競合を処理します。
- 非同期の post-receive フックを Bitbucket Data Center で使用します。
3. すべて手動で行う。(冗談です。ほんの思い付きで即却下しました。)
考えられるワークフロー
コア コンセプトを念頭に置きながら考えてみると、submodule
がうまくサポートできるワークフローがあればそれほどうまくいかないワークフローもあることがわかるでしょう。サブモジュールがよい選択だと言えるシナリオが少なくとも 3 つあります。
-
コンポーネントまたはサブプロジェクトの変更が速すぎる、または今後の変更によって API が壊れる場合は、安全のためにコードを特定のコミットにロックできます。
-
あまり頻繁に更新されないコンポーネントがあり、それをベンダーの依存関係として追跡する場合があります。たとえば、私はこれを vim プラグインのために行います。
-
プロジェクトの一部をサード パーティに委任して、その作業を特定の時間またはリリースで統合する場合。これも、更新があまり頻繁でない場合に機能します。
よく説明されたシナリオに対する finch へのクレジット。
これらのオプションのそれぞれには、ある種の弊害がありました。特に、開発者がフィーチャー ブランチを必要とするたびに必要になる手作業のステップです。そしてフィーチャー ブランチは常に作成が必要でした。また、ほとんどの場合は、必須のものであるプル リクエストを効果的に使用できませんでした。
1、2 名のメンバーがほぼ 2 か月間費やした後、私たちがこの問題に間違った方向から取り組んでいた (ショッキングな) 理由が明らかになりました。
1 つのバージョンがすべてを規定する
後になってみれば正しく判断できるのですが、私たちの最大の間違いは既に所有しているツールではなく、git-flow ツールによって必要なワークフローを実装することに集中していたことです。次のものは既にありました。
- Jira
- Bamboo Data Center
- Maven
- Artifactory Pro
つまり、必要なツールはこれだけでした。
チームのエンジニアの一人が、問題はビルド管理そのものではなく、アーティファクトが上書きされたことだから Artifactory を代わりに手直しすべきだという非常に素晴らしい考えを思いつきました。彼の考えというのは、Maven プロパティによってスナップショット リポジトリの URL を Jira 課題 ID を含むカスタム URL に設定して、その後、カスタム テンプレートによって Artifactory 内に動的に作成されたリポジトリにそのアーティファクトを書き出すというものでした。アーティファクトをブランチ不要だった、たとえば本番でのみ作業して共通部分でも作業しない場合、Maven の依存関係リゾルバーはアーティファクトを開発スナップショット リポジトリで見つけることになります。
この手軽なプロパティ変数をビルド設定ファイルに設定して Maven プラグインを記述し、それを Maven のビルド ライフサイクルの早期の段階で設定しました。紙面だけではにわかに信じがたかったので、チームはこの問題の解決にさらに粘り強く取り組みました。問題は、実際にこれを実践できないことでした。Maven ライフサイクルの第一段階は「検証」です。リポジトリ URL は、検証にバインドされたプラグインが実行されるまでには既に解決されています。このため、変数は入力されずに URL にブランチ名は付けられません。開発スナップショットとは別のリポジトリでレイアウトを使用していたとしても、並行開発では隔離されませんでした。
ふたたび、さっぱり訳が分からない、どうしたものか。
ビールを一杯飲んだ後で、前に話したエンジニアがもう少し掘りさげて Maven に機能を追加する他の方法を調べました。つまり、拡張機能です。
「ビールは人生のトラブルの原因そして解決である」- ホーマー・シンプソン
拡張機能はプラグインと同様に Maven ワークフローを強化するための強力な機能を提供しますが、ライフサイクル目標の前に実行されるために Maven 内部へのアクセスが容易になります。RepositoryUtils パッケージを利用することで、Maven でカスタム パーサーによって URL を再評価して、更新された値によって再設定できるようにしました。[3]
拡張機能を導入してテストしたら「これは決して発生しない」というタスクから「これは月曜日に発生するので、明日までに 10 ページのドキュメントを記述する必要がある」というタスクまで、移行前のタスクを順次処理し始めました。近いうちに、新しい開発ワークフローを実現するためにツールをどう使ったか、そしてこのプロセスに関して学んだ教訓についてをさらに書く予定です。
脚注
[1]: ここのデメリットの 1 つは、ビルドをステージングからリリースに「昇格」させるために、Artifactory REST API を起動するために作成したスクリプトを使用する必要があったことです。これでも十分速いのですが、さらに多くの自動化が必要になります。
[2]:テクニカル・アカウント・マネージャー。詳細をご覧ください。
[3]: 最初の開発作業の後、その時点でこの作業を 100% にするにはさらに作業が必要なことに気付きました。たとえば、Artifactory の (別のエンジニアからの) スナップショットがローカル スナップショットよりも新しい場合、Maven はリモート アーティファクトの方が新しく適切なため、こちらを取得します。
この記事を共有する
次のトピック
おすすめコンテンツ
次のリソースをブックマークして、DevOps チームのタイプに関する詳細や、アトラシアンの DevOps についての継続的な更新をご覧ください。