依存関係の管理

問題を報告する ソースを表示 夜間 7.4 をタップします。 7.3 7.2 7.1 7.0 650

前のページを見ていくと、1 つのテーマが何度も繰り返されています。 独自のコードはかなり簡単ですが、依存関係の管理はとても簡単です。 困難になります。依存関係にはさまざまな種類があり、場合によっては タスクへの依存関係(例: 「リリースをマークする前にドキュメントを push する」 「 「コードをビルドするには、最新バージョンのコンピュータ ビジョン ライブラリが必要です」)。 コードベースの他の部分に内部依存関係がある場合があります。 場合によっては、別のチームが所有するコードやデータに外部から依存している 認証できます。いずれにせよ、「 という問題は、IT 部門で繰り返し発生している問題です。 依存関係の管理は、おそらく最もサーバーレスな 基本的な役割を担います。

モジュールと依存関係の取り扱い

Bazel などのアーティファクト ベースのビルドシステムを使用するプロジェクトは、次のセットに分割されます。 BUILD を介して相互の依存関係を表現するモジュールを含む、複数のモジュール できます。これらのモジュールと依存関係を適切に整理すると、ビルドシステムのパフォーマンスとメンテナンスに必要な作業量の両方に大きな影響を与える可能性があります。

きめ細かいモジュールと 1:1:1 ルールの使用

アーティファクト ベースのビルドを構造化する際に最初に発生する問題は、個々のモジュールに含める機能の量を決定することです。Bazel では、 module は、 java_library または go_binary。極端な例として、1 つの BUILD ファイルをルートに配置し、そのプロジェクトのすべてのソースファイルを再帰的にグルーピングすることで、プロジェクト全体を 1 つのモジュールに含めることができます。もう一方の 事実上、ほぼすべてのソースファイルを独自のモジュールに 各ファイルを、依存する他のすべてのファイルを BUILD ファイルにリストすることを要求します。

ほとんどのプロジェクトは、これらの極端な選択肢の間のどこかに位置し、選択にはパフォーマンスとメンテナンス性のトレードオフが伴います。プロジェクト全体に 1 つのモジュールを使用すると、外部依存関係を追加する場合を除き、BUILD ファイルを変更する必要はありませんが、ビルドシステムは常にプロジェクト全体を一度にビルドする必要があります。つまり、ビルドの一部を並列化または分散することはできず、すでにビルドされている部分をキャッシュに保存することもできません。ファイルごとに 1 つのモジュールの場合はその逆です。ビルドシステムはビルドのステップのキャッシュとスケジューリングにおいて最大限の柔軟性がありますが、エンジニアは、どのファイルを参照するかを変更するたびに依存関係のリストを維持するためにより多くの労力を費やす必要があります。

正確な粒度は言語によって異なりますが(場合によっては言語内でも異なります)、Google では、タスクベースのビルドシステムで通常作成するモジュールよりもはるかに小さいモジュールを優先する傾向があります。標準的な本番環境バイナリは Google は何万ものターゲットに依存していることが多く、中規模の企業でさえ コードベース内で数百のターゲットを 所有できますパッケージ化の概念が組み込まれている Java などの言語では、通常、各ディレクトリに 1 つのパッケージ、ターゲット、BUILD ファイルが含まれています(Bazel に基づく別のビルドシステムである Pants では、これを 1:1:1 ルールと呼びます)。パッケージ化規則が緩い言語では、BUILD ファイルごとに複数のターゲットが定義されることがよくあります。

小さいビルド ターゲットのメリットは、分散ビルドの速度が向上し、ターゲットの再ビルド頻度が低下するため、大規模な場合に特に顕著になります。テストが加わると、このメリットはさらに強くなります。よりきめ細かいターゲットにより、ビルドシステムは特定の変更の影響を受ける可能性のあるテストのサブセットのみを実行できるため、よりスマートに動作できるようになります。Google では、Google Cloud での使用より小規模な 目標を掲げているにもかかわらず、このマイナス面の BUILD ファイルを自動管理してデベロッパーの負担を軽減するツール。

buildifierbuildozer などの一部のツールは、Bazel の buildtools ディレクトリで使用できます。

モジュールの可視性を最小限に抑える

Bazel などのビルドシステムでは、各ターゲットで可視性(つまり、 プロパティを使用して、他のターゲットがそれに依存するかどうかを決定します。限定公開ターゲットは、独自の BUILD ファイル内でのみ参照できます。ターゲットは、明示的に定義された BUILD ファイルのリスト内のターゲットに対して、または公開可視性の場合はワークスペース内のすべてのターゲットに対して、より広範な公開設定を許可できます。

ほとんどのプログラミング言語と同様に、通常は可視性をできるだけ最小限に抑えることをおすすめします。一般に、Google のチームは、Google のどのチームでも利用できる広く使用されているライブラリを表すターゲットのみ公開します。コードを使用する前に他の人と調整する必要があるチームは、 お客様のターゲットの許可リストを維持します。各チームの内部実装ターゲットは、チームが所有するディレクトリのみに制限されます。また、ほとんどの BUILD ファイルには、非公開ではないターゲットが 1 つだけあります。

依存関係の管理

モジュールは互いを参照できる必要があります。コードベースをきめ細かいモジュールに分割するデメリットは、それらのモジュール間の依存関係を管理する必要があることです(ただし、ツールを使用して自動化できます)。これらを表現することは、 依存関係が通常、BUILD ファイルのコンテンツの大部分になります。

内部依存関係

きめ細かいモジュールに分割された大規模なプロジェクトでは、ほとんどの依存関係が 内部の可能性が高いつまり、同じマシンで定義され、構築された別のターゲット できます。内部依存関係は、ビルドの実行中にビルド済みアーティファクトとしてダウンロードされるのではなく、ソースからビルドされるという点で、外部依存関係とは異なります。また、内部依存関係に「バージョン」という概念はありません。ターゲットとその内部依存関係はすべて、リポジトリ内の同じ commit / リビジョンで常にビルドされます。解決すべき問題として 慎重に扱う必要がありますが、 推移的依存関係(図 1)を示します。ターゲット A がターゲット B に依存し、ターゲット B が共通ライブラリ ターゲット C に依存しているとします。ターゲット A がクラスを使用できるようにする 定義されているでしょうか。

推移的依存関係

図 1. 推移的依存関係

基盤となるツールに関する限り、問題ありません。両方 B と C は、ビルド時にターゲット A にリンクされるため、 C は A に認識されます。Bazel では長年この機能が許可されていましたが、Google の成長に伴い問題が発生し始めました。B がリファクタリングされ、C に依存する必要がなくなったとします。その後、B の C への依存関係が削除された場合、A と B への依存関係を介して C を使用していたターゲットが破損します。実質的に、ターゲットの依存関係は公開コントラクトの一部となり、安全に変更できなくなりました。そのため、依存関係が時間とともに蓄積され、Google でのビルドが遅れ始めました。

Google は最終的に、Bazel に「厳格な伝播依存関係モード」を導入することで、この問題を解決しました。このモードでは、Bazel はターゲットが次の試行を試行したかどうかを検出します。 直接依存せずにシンボルを参照します。その場合、 というシェルコマンドを使用して 確認します。この変更を Google のコードベース全体にロールアウトし、数百万ものビルド ターゲットをすべてリファクタリングして依存関係を明示的にリストすることは、数年にわたる取り組みでしたが、その価値は十分ありました。ターゲットに不要な依存関係が減ったため、ビルドが大幅に高速化されました。エンジニアは、依存するターゲットが破損する心配をすることなく、不要な依存関係を削除できます。

通常どおり、厳格な推移的依存関係を適用するとトレードオフが発生します。頻繁に使用されるライブラリは、偶発的にプルインされるのではなく、多くの場所で明示的にリストする必要があるため、ビルドファイルが冗長になり、エンジニアは BUILD ファイルに依存関係を追加するためにより多くの労力を費やす必要がありました。Google はその後、不足している多くの依存関係を自動的に検出し、デベロッパーの介入なしで BUILD ファイルに追加することで、この負担を軽減するツールを開発しました。ただし、このようなツールがなくても、コードベースのスケーリングに伴うトレードオフは十分に価値があると判断しています。BUILD ファイルに依存関係を明示的に追加するのは 1 回限りの費用ですが、暗黙的な伝播依存関係を処理すると、ビルド ターゲットが存在する限り、継続的な問題が発生する可能性があります。Bazel 厳格な推移的依存関係を Java コードにネイティブに実装されています

外部依存関係

依存関係が内部用でない場合は、外部用である必要があります。外部依存関係は、 ビルドシステムの外部でビルドおよび保存されたアーティファクトに 保存されたデータなどです「 依存関係がアーティファクト リポジトリ(通常は インターネット経由で転送されます。また、ソースから構築されるのではなく、そのまま使用されます。外部依存関係と内部依存関係の最大の違いの 1 つは、外部依存関係にはバージョンがあり、それらのバージョンはプロジェクトのソースコードとは独立して存在することです。

自動と手動の依存関係管理

ビルドシステムを使用すると、外部依存関係のバージョンを管理できる 手動または自動で行えます手動で管理する場合、buildfile には、アーティファクト リポジトリからダウンロードするバージョンが明示的にリストされます。多くの場合、1.1.4 などのセマンティック バージョン文字列が使用されます。自動的に管理する場合、ソースファイルで許容されるバージョンの範囲を指定し、ビルドシステムは常に最新バージョンをダウンロードします。対象 たとえば、Gradle では依存関係のバージョンを「1.+」と宣言して、 依存関係のマイナー バージョンやパッチ バージョンについては、 メジャー バージョンは 1 です。

依存関係の自動管理は、小規模プロジェクトには便利ですが、 規模の大きなプロジェクトや 1 人でも 1 人でも作業できます。自動的に管理される依存関係の問題は、バージョンが更新されるタイミングを制御できないことです。外部の関係者によるセキュリティ侵害や 更新(セマンティック バージョニングの使用を主張している場合も含む)が伴うため、 変更された内容を簡単に検出できない 動作状態にロールバックできますビルドが壊れなくても 追跡できない微妙な動作やパフォーマンスの変化などが考えられます。

これに対して、依存関係を手動で管理する場合はソースの変更が必要になるため、 簡単に検出してロールバックできます。また、 古い依存関係を使用してビルドするには、古いバージョンのリポジトリを確認してください。 Bazel では、すべての依存関係のバージョンを手動で指定する必要があります。中程度の規模でも、手動バージョン管理のオーバーヘッドは、安定性を確保するために十分な価値があります。

1 バージョンのルール

通常、ライブラリのバージョンごとに異なるアーティファクトは、 理論上は、同じ外部 IP アドレスの異なるバージョンが 依存関係の両方をビルドシステムで異なる名前で宣言することはできません。 これにより、各ターゲットは、必要な依存関係のバージョンを選択できます。 あります。これは実際には多くの問題を引き起こすため、Google はコードベース内のすべてのサードパーティ依存関係に厳格な1 バージョン ルールを適用しています。

複数バージョンを許可する際の最大の問題は、ダイヤモンドの依存関係 あります。ターゲット A がターゲット B と外部 IP アドレスの v1 に依存しているとします。 ライブラリです。ターゲット B が後でリファクタリングされて、同じ v2 に ターゲット A は 2 つの依存関係に暗黙的に依存するため、 同じライブラリの異なるバージョンに 割り当てることもできます実質的に、タグを追加するのは ターゲットから複数のバージョンを持つサードパーティ ライブラリへの新しい依存関係。 なぜならターゲットのユーザーは すでに別のプラットフォームに依存している可能性があるからです できます。1 バージョン ルールに従うと、この競合は発生しません。ターゲットがサードパーティ ライブラリへの依存関係を追加する場合、既存の依存関係はすでに同じバージョンにあるため、問題なく共存できます。

一時的な外部依存関係

外部依存関係の推移的依存関係には、 困難です。Maven Central などの多くのアーティファクト リポジトリでは、アーティファクトがリポジトリ内の他のアーティファクトの特定のバージョンへの依存関係を指定できます。Maven や Gradle などのビルドツールでは、デフォルトで各伝播依存関係が再帰的にダウンロードされることが多いため、プロジェクトに 1 つの依存関係を追加すると、合計で数十個のアーティファクトがダウンロードされる可能性があります。

これはとても便利です。新しいライブラリへの依存関係を追加する際に、 ライブラリの推移的依存関係をそれぞれ追跡するのは大変な労力です。 すべて手動で追加できますただし、大きなデメリットもあります。異なるライブラリが同じサードパーティ ライブラリの異なるバージョンに依存する可能性があるため、この戦略は必然的に 1 つのバージョンのルールに違反し、ダイアモンド依存関係の問題につながります。ターゲットが、同じ依存関係の異なるバージョンを使用する 2 つの外部ライブラリに依存している場合、どちらが取得されるかはわかりません。また、外部依存関係を更新すると、外部依存関係を 新しいバージョンが pull し始めると、コードベース全体で無関係な障害が発生します。 競合するバージョンが存在する場合があります。

このため、Bazel は推移的依存関係を自動的にダウンロードしません。 残念ながら、解決策はありません。Bazel の代替手段は、 このグローバル ファイルには、リポジトリの外部接続の モジュール全体を通じて依存関係に使用される明示的なバージョンを できます。幸い、Bazel には、一連の Maven アーティファクトの伝播依存関係を含むこのようなファイルを自動的に生成できるツールが用意されています。このツールを 1 回実行すると、プロジェクトの最初の WORKSPACE ファイルが生成されます。このファイルを手動で更新して、各依存関係のバージョンを調整できます。

ここでも、利便性とスケーラビリティのどちらかを選択する必要があります。小規模なプロジェクトでは、自己参照型の依存関係の管理を気にせず、自動の自己参照型の依存関係を使用できる場合があります。この戦略の魅力は、組織がイノベーションを起こすにつれ コードベースが増大し、競合や予期しない結果はますます増加する あります。大規模な場合、依存関係を手動で管理するコストは、依存関係の自動管理によって発生する問題に対処するコストよりもはるかに低くなります。

外部依存関係を使用したビルド結果のキャッシュ保存

外部依存関係は、多くの場合、安定版のライブラリをリリースするサードパーティによって提供されます。ソースコードが提供されないこともあります。一部 組織は、独自のコードの一部を 他のコードがサードパーティとして依存できるようにし、 内部依存関係よりもはるかに効率的ですアーティファクトのビルドは遅いがダウンロードは速い場合、理論的にはビルドを高速化できます。

ただし、これにより多くのオーバーヘッドと複雑さも生じます。これらのアーティファクトのビルドとアーティファクト リポジトリへのアップロードを担当する人員が必要になり、クライアントは最新バージョンを常に最新の状態に保つ必要があります。また、システムのさまざまな部分がリポジトリ内のさまざまなポイントからビルドされるため、ソースツリーが一貫したビューではなくなり、デバッグがはるかに難しくなります。

ビルドに時間がかかるアーティファクトの問題を解決するには、 前述のように、リモート キャッシュをサポートするビルドシステムを使用する。そのような ビルドシステムは、すべてのビルドの結果のアーティファクトを特定の場所に保存する エンジニア間で共有されます。そのため、開発者が依存するアーティファクトが 第三者によって最近ビルドされた場合、ビルドシステムは モデルに任せることができます。これにより、アーティファクトに直接依存するパフォーマンス上のメリットがすべて得られる一方で、常に同じソースからビルドされた場合と同様にビルドの整合性が確保されます。これは Google で内部的に使用されている戦略であり、Bazel はリモート キャッシュを使用するように構成できます。

外部依存関係のセキュリティと信頼性

サードパーティ ソースのアーティファクトに依存することは、本質的にリスクがあります。サードパーティ ソース(アーティファクト リポジトリなど)が停止すると、外部依存関係をダウンロードできず、ビルド全体が停止する可能性があるため、可用性にリスクが生じます。セキュリティ リスクもあります。サードパーティ システムが攻撃者に侵害された場合、攻撃者は参照されているアーティファクトを独自の設計のものに置き換え、任意のコードをビルドに挿入できるようになります。どちらの問題も、依存するアーティファクトを管理するサーバーにミラーリングし、Maven Central などのサードパーティ アーティファクト リポジトリへのビルドシステムのアクセスをブロックすることで軽減できます。トレードオフは ミラーを維持するには労力とリソースが必要になるため、 プロジェクトの規模によって異なります。セキュリティ問題は 完全に防止できます。そのため、各 API のハッシュを サードパーティ アーティファクトがソース リポジトリで指定されると、ビルドが アーティファクトが改ざんされた場合に失敗します。この問題を完全に回避する別の方法として、プロジェクトの依存関係をベンダーに依存させることもできます。プロジェクトが依存関係をベンダーリングする場合、プロジェクトのソースコードとともに、ソースまたはバイナリとしてソース管理にチェックインします。これは実質的に、 すべての外部依存関係が内部リソースに変換されることを 確認します。Google では社内でこの手法を使用し、すべてのサードパーティ ルートの third_party ディレクトリに、Google 全体で参照されるライブラリ ソースツリーで参照できますただし、これは Google でのみ機能します。これは、Google のソース管理システムが非常に大規模な Monorepo を処理するようにカスタム構築されているためです。そのため、すべての組織でベンダーリングが選択できるとは限りません。