パイプラインにおけるビルドの依存関係を計画する
このユニットでは、コードを共有しやすいようパッケージ化することについて学習します。 パッケージを作成する理由、作成できるパッケージの種類、パッケージをホストできる場所、およびホストされているパッケージにアクセスする方法について確認します。 パッケージのバージョン管理についても学習します。
コードベースは、常に規模も複雑さも増しています。 アプリケーションで使用されるすべてのコードを、1 つのチームで記述することはほとんどありません。 むしろ、他の開発者によって記述された既存のコードを組み込みます。 アプリ内にそのようなパッケージや依存関係が多数存在することもあります。 これらの依存関係を積極的に管理し、適切に保守して、常にセキュリティ要件を満たすようにすることは重要です。
チームがどのように行っているかを確認してみましょう。 Andy は、既存のコードに対して考えられる、他のチームにとって役立つ変更について話し合うためにチームを招集しました。
チーム ミーティング
Andy: 皆さん、こんにちは。 私は、Space Game のバックエンド システムに従事しているチームと話をしてきました。 私たちが Web サイトに使用しているモデルは、彼らが作成しようとしているバックエンド アプリにも使用できる可能性があります。
Amita: ここでいうモデルとは何ですか?
Andy: ご存じのとおり、Space Game Web サイトは ASP.NET Core アプリケーションです。 Model-View-Controller (または MVC) パターンを使用して、ユーザー インターフェイスにおけるデータの表示方法からデータを切り離しています。 私たちのモデル クラスを含んだパッケージを作成すれば、任意のアプリケーションから使用できるようになるのでは、と考えました。
Amita: 具体的な目標は?
Andy: 同じデータベースを両方のチームが共有することです。 このゲームでは、高スコアがデータベースに送信されます。私たちは、それらのスコアを読み取ってランキングに表示しています。
Amita: なるほど。 そのパッケージはどのように作成するのですか?
Andy: まさにそのことについて、お話したいと思っています。 いくつかの選択肢はあるので、アイデアを聞かせていただければと思っています。
Tim: ぜひ協力したいですが、最初にいくつか質問があります。 お話の件について自分には経験がないので、実際のしくみを理解したいのですが。
パッケージとは何ですか?
"パッケージ" には、他の開発者が自らコードを記述することなく各自のプロジェクトに使用できる再利用可能なコードが格納されます。
一般に、コンパイル型言語では、コンパイル済みのバイナリ コードがパッケージに格納されます (.NET の .dll ファイル、Java の .class ファイルなど)。 コンパイル型言語ではなくインタプリター型言語 (JavaScript、Python など) では、パッケージにはソース コードが格納されることになります。
どちらの場合も、パッケージは ZIP またはそれと同様の形式に圧縮されるのが一般的です。 パッケージ システムでは、多くの場合、パッケージの用途を明確にするために一意のファイル拡張子 (.nupkg や .jar など) が定義されます。 圧縮によってダウンロード時間が短縮されることに加え、生成されるファイルが 1 つであるため管理も単純化されます。
また、パッケージには、メタデータ (そのパッケージについての情報) を指定する 1 つ以上のファイルが含まれることもよくあります。 このメタデータに、パッケージによって実行される内容や、ライセンス条項、作成者の連絡先情報、パッケージのバージョンを記述することもできます。
パッケージを作成すべき理由は?
パッケージを作成することには、コードの複製にはない利点があります。
コードを複製せずにパッケージを作成する理由の 1 つは、"ずれ" の防止です。 コードが複製されると、特定のアプリケーションの要件を満たすために、それぞれのコピーがたちまち枝分かれしてしまいます。 それぞれのコピー間で変更を移行することが難しくなってしまいます。 つまり、すべての人々に利する形でコードを改良する余地がなくなります。
また、パッケージでは、関連する機能が 1 つの再利用可能なコンポーネントにまとめられます。 プログラミング言語によっては、パッケージを通じて、その実装の細部へのアクセスを制限しつつ、特定の型や関数へのアクセスをアプリケーションに提供することができます。
パッケージを作成するもう 1 つの理由は、その機能を一貫した方法でビルドし、テストできることです。 コードが複製された場合、各アプリケーションがそれぞれ異なる方法でコードをビルドし、テストすることになるでしょう。 あるテスト セットに、別のセットにとって有益なチェックが含まれていることもあります。
テストに加え、パッケージを使用した保守の対象となる別のコードベースがあることは、1 つのトレードオフです。 また、機能を追加する際には注意が必要です。 一般に、パッケージには、さまざまな種類のアプリケーションに寄与する機能が含まれている必要があります。 たとえば、Json.NET は .NET 用の人気のある NuGet パッケージであり、これを使うと JSON ファイルを扱うことができます。 Json.NET はオープンソースであるため、コミュニティが改善を提案したり問題をレポートしたりすることができます。
複数のアプリケーションから同じコードを利用できるならば、そのメリットはデメリットをはるかに上回ります。 管理の対象となるビルド プロセスも、テスト セットも、コードベースも 1 つで済むのです。
依存関係を特定するにはどうすればよいですか?
コードを個別のコンポーネントに再編成することを目的としている場合は、アプリに含まれるそのような部分 (削除、再利用可能にするためのパッケージ化、一元管理できる場所での保管、およびバージョン管理が可能) を特定する必要があります。 また、独自のコードを、オープンソースまたはライセンスを持つサードパーティ製のコンポーネントに置き換えることもできます。
コードベースで潜在的な依存関係を特定する方法は数多くあります。 これには、再利用パターンのコードのスキャンや、ソリューションのアーキテクチャの分析が含まれます。 依存関係を識別するためのいくつかの方法を次に示します。
重複コード。
特定のコードが複数の場所で使われている場合、それはそのコードを再利用できるしるしです。 これらの重複するコードを一元化し、適切に再パッケージ化します。
高い凝集度と低結合。
2 つ目の方法は、相互の凝集度が高く、コードの他の部分との結合が低いコード要素を見つけることです。 基本的に、高い凝集度は、相互に関係するコードベースが 1 つの場所に保たれている状態を指します。 一方、低結合は、コードベースの関連性のない部分が可能な限り分離されている状態を意味します。
個々のライフサイクル。
ライフサイクルが似ていて、個別にデプロイおよびリリースできるコードの部分を探します。 このようなコードを別のチームで保守できるならば、ソリューションの外部のコンポーネントとしてパッケージ化できる可能性が高くなります。
安定した部分。
コードベースのある部分は、安定しており、あまり変更されない場合があります。 コード リポジトリを調べて、変更頻度が低いコードを見つけます。
独立したコードとコンポーネント。
コードとコンポーネントが独立していて、システムの他の部分と関連性がない場合は、別個の依存関係に分離できる可能性があります。
さまざまなツールを使用して、コードベースのスキャンや検査を行うことができます。 そのようなツールには、重複したコードをスキャンし、ソリューションの依存関係を表すグラフを描画するツールや、結合や凝集度の指標を計算できるツールが含まれます。
パッケージにはどのような種類がありますか?
パッケージには、プログラミング言語やフレームワークごとに固有の作成方法があります。 広く使われているパッケージ システムには、そのプロセスのしくみについて書かれたドキュメントが用意されています。
これらの一般的なパッケージ システムには、既に馴染みがあるかもしれません。
- NuGet: .NET ライブラリのパッケージ
- NPM: JavaScript ライブラリのパッケージ
- Maven: Java ライブラリのパッケージ
- Docker: ソフトウェアを "コンテナー" と呼ばれる分離されたユニットにパッケージ化したもの
パッケージはどこでホストされるのですか?
独自のネットワークでパッケージをホストするか、ホスティング サービスを使用することができます。 ホスティング サービスはよく、"パッケージ リポジトリ" や "パッケージ レジストリ" と呼ばれます。 こうしたサービスの多くは、オープンソース プロジェクト用に無料のホスティングを提供しています。
先ほど挙げたパッケージ タイプに関して広く使われているホスティング サービスを次にいくつか示します。
-
NuGet パッケージは、.NET コード成果物に使用されます。 これらの成果物には、.NET アセンブリと関連ファイル、ツール、場合によってはメタデータが含まれます。 NuGet は、パッケージの作成方法、格納方法、および使用方法を定義します。 NuGet パッケージは基本的に、ZIP 形式のファイルを含む圧縮フォルダー構造であり、.nupkg 拡張機能を備えています。
-
NPM パッケージは、JavaScript に使用されます。 NPM パッケージは、1 つのファイル、または JavaScript ファイルや、パッケージのメタデータを記述する package.json ファイルを含むフォルダーです。 node.js の場合、パッケージには通常、パッケージの使用後に読み込むことができるモジュールが 1 つ以上含まれています。
-
Maven は、Java ベースのプロジェクトに使用されます。 各パッケージには、プロジェクトのメタデータを記述する Project Object Model ファイルがあります。これは、パッケージを定義して操作するための基本単位です。
-
Docker パッケージはイメージと呼ばれ、完全な自己完結型のデプロイが含まれます。 通常、Docker イメージは、他のイメージに依存することなく、単独でホストおよび実行できるソフトウェア コンポーネントを表します。 Docker イメージは階層化されて、他のイメージに依存する場合があります。
"パッケージ フィード" とは、パッケージのリポジトリ サーバーのことです。 このサーバーは、インターネット上に置くことも、ファイアウォールの内側のネットワーク上に置くこともできます。 たとえば、Azure Artifacts や MyGet といったホスティング製品を使用して、独自の NuGet フィードをホストすることができます。 ファイル共有上でパッケージをホストすることもできます。
ファイアウォールの内側でパッケージをホストするときは、独自のパッケージへのフィードを含めることができます。 お使いのシステムがインターネットに接続できないときは、信頼するパッケージをネットワーク上でキャッシュすることもできます。
優れた依存関係管理戦略は、どのような要素で構成されますか?
適切な依存関係を管理するための戦略は、次の 3 つの要素に依存しています。
標準化。
依存関係を宣言して解決する方法を標準化することで、自動化されたリリース プロセスを反復可能で予測可能な状態に保つことができます。
パッケージ化の形式とソース。
各依存関係は、適切な形式を使用してパッケージ化し、一元管理できる場所に格納する必要があります。
バージョン管理。
独自のコードを使用する場合と同様、依存関係において時間の経過と共に発生する変更を追跡する必要があります。 つまり、依存関係をバージョン管理する必要があります。
パッケージにはだれがアクセスできるのですか?
多くのパッケージ フィードは、パッケージへのアクセス制限を設けていません。 たとえば、nuget.org からは、サインインも認証もすることなく Json.NET をダウンロードすることができます。
認証が必要なパッケージ フィードもあります。 フィードへのアクセスは、いくつかの方法で認証することができます。 たとえば、いくつかの種類のフィードでは、ユーザー名とパスワードが必要となります。 一方、"アクセス トークン" を必要とするフィードもあります。これは一般に、ユーザーが誰であるか、どのリソースへのアクセス権を持っているかを識別する、長い文字列です。 アクセス トークンは、特定の期間が経過したときに有効期限が切れるように設定できます。
パッケージはどのようにバージョン管理されるのですか?
バージョン管理スキームは、ご利用のパッケージ システムによって異なります。
たとえば、NuGet パッケージではセマンティック バージョニングが使用されます。
セマンティック バージョニングは、一般的なバージョン管理スキームです。 その形式は次のとおりです。
Major.Minor.Patch[-Suffix]
各パラメーターの意味を次に示します。
- 新しい Major バージョンでは、破壊的変更が導入されます。 一般に、新しいメジャー バージョンと連携するためには、アプリケーション側でパッケージの使用方法を更新する必要があります。
- 新しい Minor バージョンでは新機能が導入されますが、以前のバージョンとの下位互換性があります。
- 新しい Patch では、下位互換性のあるバグ修正が導入されますが、新機能は導入されません。
- -Suffix 部分は任意です。パッケージをプレリリース バージョンとして識別します。 たとえば 1.0.0-beta1 のパッケージであれば、1.0.0 リリースの最初のベータ プレリリース ビルドとして識別されます。
パッケージを参照する際は、バージョン番号で参照します。
PowerShell と特定のバージョン番号を使ってパッケージをインストールする例を次に示します。
Install-Package Newtonsoft.Json -Version 13.0.1
パッケージに変更が生じた場合はどうなりますか?
アプリからパッケージを参照するときは、通常、使用したいパッケージのバージョンを "固定" (指定) します。
多くのフレームワークでは、インストールするパッケージのバージョンの許容範囲を指定することができます。 また、ワイルドカードを指定できるものもあります ("浮動バージョン" と呼びます)。
たとえば、NuGet におけるバージョン "1.0" とは、1.0 と同じかそれ以上の最初のバージョンを意味します。 "[1.0]" は、バージョン 1.0 のみをインストールするよう指定するもので、それより新しいバージョンは許容されません。
その他、いくつかの例を紹介します。
表記: | 選択: |
---|---|
(1.0,) | 1 より大きい最初のバージョン。 |
[1.0,2.0] | 1.0 以上、2.0 以下の最初のバージョン。 |
(1.0,2.0) | 1.0 より大きく、2.0 より小さい最初のバージョン。 |
[1.0,2.0) | 1.0 以上、2.0 未満の最初のバージョン。 |
それぞれの保守管理者によって新しいパッケージ バージョンがリリースされたら、変更内容を評価し、それに対して自分のアプリケーションをテストすることができます。 準備が整ったら、対応する構成でパッケージのバージョン番号を更新し、その変更をビルド パイプラインに送信することができます。
C# アプリケーションのプロジェクト (.csproj) ファイルに Newtonsoft.Json パッケージを含める方法の例を次に示します。 この例では、そのパッケージのバージョン 13.0.1 を指定しています。
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>