依存関係の管理

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

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

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

Bazel などのアーティファクトベースのビルドシステムを使用するプロジェクトは、一連のモジュールに分割され、モジュールは BUILD ファイルを使用して相互の依存関係を表現します。これらのモジュールと依存関係を適切に整理すると、 ビルドシステムのパフォーマンスにも、 維持することです

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

アーティファクト ベースのビルドを構築する際にまず浮かぶのは、 個々のモジュールに含める機能を決定する 必要がありますBazel では、 module は、 java_library または go_binary。極端な話として、プロジェクト全体が 単一のモジュールに含めるには、1 つの BUILD ファイルをルートに配置し、 そのプロジェクトのソースファイルをすべて 再帰的にひとまとめにしますもう一方の 事実上、ほぼすべてのソースファイルを独自のモジュールに 各ファイルを、依存する他のすべてのファイルを BUILD ファイルにリストすることを要求します。

ほとんどのプロジェクトは、これらの極端な選択肢の間のどこかに位置し、選択にはパフォーマンスとメンテナンス性のトレードオフが伴います。各モジュールに 1 つのモジュールを プロジェクト全体という意味で、BUILD ファイル 外部依存関係を追加するときに使用しますが、その場合、ビルドシステムには必ず 常にプロジェクト全体を一度にビルドします。つまり、ビルドの一部を並列化または分散することはできず、すでにビルドされている部分をキャッシュに保存することもできません。ファイルごとに 1 つのモジュールはその逆で、ビルドシステムは ビルドのステップをキャッシュし、スケジュールできます。ただし、 エンジニアが依存関係のリストを管理するのに どのファイルがどのファイルを参照しているかを 変更できます

正確な粒度は言語によって異なりますが、多くの場合、 Google は、一般的なモジュールの 1 つよりもはるかに小さいモジュールを優先する傾向があります。 タスクベースのビルドシステムで記述します。標準的な本番環境バイナリは 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 ファイルのコンテンツの大部分になります。

内部依存関係

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

推移的依存関係

図 1. 推移的依存関係

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

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

通常どおり、厳格な推移的依存関係を適用すると、トレードオフが発生します。その結果 よく使用するライブラリをリストする必要があるため、ビルドファイルの内容が冗長になる 多くの場所に明示的に取り込んだり、 BUILD ファイルに依存関係を追加するのに多くの時間を費やす必要がありました。それ以来 このトイルを軽減するツールを開発しました。 開発せずに BUILD ファイルに追加することもできます。 できます。ただし、このようなツールがなくても、コードベースのスケーリングに伴うトレードオフは十分に価値があると判断しています。BUILD ファイルに依存関係を明示的に追加するのは 1 回限りの費用ですが、暗黙的な伝播依存関係を処理すると、ビルド ターゲットが存在する限り、継続的な問題が発生する可能性があります。Bazel はデフォルトで Java コードに厳格な伝播依存関係を適用します。

外部依存関係

依存関係が内部的でない場合は、外部にする必要があります。外部依存関係は、ビルドシステムの外部でビルドされ、保存されるアーティファクトの依存関係です。「 依存関係がアーティファクト リポジトリ(通常は インターネット経由で転送されます。また、ソースから構築されるのではなく、そのまま使用されます。次のいずれか 外部依存関係と内部依存関係の最大の違いは 外部依存関係にはバージョンがあり、それらのバージョンは ソースコードが表示されます。

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

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

依存関係の自動管理は、小規模プロジェクトには便利ですが、 規模の大きなプロジェクトや 1 人でも 1 人でも作業できます。自動的に管理される依存関係の問題は、バージョンが更新されるタイミングを制御できないことです。外部の関係者が破壊的な更新を行わないことを保証する方法はありません(セマンティック バージョニングを使用していると主張している場合でも同様です)。そのため、ある日正常に動作していたビルドが、翌日には動作しなくなる可能性があります。変更内容を簡単に検出したり、動作状態にロールバックしたりすることはできません。ビルドが破綻しなくても、微妙な動作やパフォーマンスの変化が起きて、追跡できない場合があります。

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

1 バージョンのルール

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

複数バージョンを許可する際の最大の問題は、ダイヤモンドの依存関係 あります。ターゲット A がターゲット B と外部ライブラリの v1 に依存しているとします。後でターゲット B をリファクタリングして、同じ外部ライブラリの v2 への依存関係を追加すると、ターゲット A は破綻します。これは、同じライブラリの 2 つの異なるバージョンに暗黙的に依存することになるためです。ターゲットのユーザーがすでに別のバージョンに依存している可能性があるため、ターゲットから複数のバージョンがあるサードパーティ ライブラリに新しい依存関係を追加することは、実際には安全ではありません。1 つのバージョン ルールに従うと、特定のバージョンに target は、サードパーティのライブラリへの依存関係を追加する 同じバージョンにあるものなので 問題なく共存できます

推移的な外部依存関係

外部依存関係の転送依存関係の処理は特に困難です。Maven Central などの多くのアーティファクト リポジトリでは、アーティファクトがリポジトリ内の他のアーティファクトの特定のバージョンへの依存関係を指定できます。Maven や Gradle などのビルドツールは、多くの場合、それぞれを再帰的にダウンロードします。 デフォルトで推移的依存関係があります。つまり、依存関係を 1 つ追加すると、 プロジェクトで数十のアーティファクトがダウンロードされ、 確認できます。

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

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

ここでも、利便性とスケーラビリティのどちらかを選択する必要があります。小 プロジェクトでは、推移的依存関係の管理について心配する必要がない方がよい 自己回帰モデルを使用することで、 確認します。この戦略は、組織とコードベースの規模が大きくなるにつれて魅力が薄れ、競合や予期しない結果が頻繁に発生するようになります。大規模な場合、依存関係を手動で管理するコストは、依存関係の自動管理によって発生する問題に対処するコストよりもはるかに低くなります。

外部依存関係を使用してビルド結果をキャッシュに保存する

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

しかし、これには多くのオーバーヘッドと複雑さが伴います。 各アーティファクトのビルドと アーティファクト リポジトリに保管されます。クライアントは、その状態を維持し、 できます。デバッグ作業もかなり困難になります。 システムの各部分が、データセンターの複数のポイントから ソースツリーのビューも一貫していません。

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

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

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