アーキテクチャの原則
ヒント
このコンテンツは eBook の「ASP.NET Core および Azure での最新の Web アプリケーションの設計」からの抜粋です。.NET Docs で閲覧できるほか、PDF として無料ダウンロードすると、オンラインで閲覧できます。
"プログラマーがプログラムを記述するやり方で建設業者がビルを建てたなら、最初にやって来たキツツキによって文明は破壊されてしまうでしょう。"
- Gerald Weinberg
ソフトウェア ソリューションを設計する場合は、保守容易性を念頭に置く必要があります。 このセクションで概説する原則は、クリーンで保守性の高いアプリケーションをもたらすアーキテクチャを決定する上で役に立ちます。 一般に、これらの原則に従うことで、明示的なインターフェイスまたはメッセージング システムを介してやり取りするコンポーネントでなく、アプリケーションの他の部分と密結合されていないそれぞれ独立したコンポーネントからアプリケーションを構築できるようになります。
一般的な設計の原則
懸念事項の分離
開発時の基本原則は、懸念事項の分離です。 この原則では、ソフトウェアが実行する処理の種類に基づいてソフトウェアを分離する必要があると表明しています。 たとえば、ユーザーに表示する注目すべき項目を識別し、さらにそのような項目を特定の方法で書式設定して一段と目立つようにするロジックを備えたアプリケーションを検討します。 書式設定する項目の選定を担当するビヘイビアーと、項目の書式設定を担当するビヘイビアーとは切り離しておく必要があります。これらは別々の懸念事項であり、偶然に関連しているに過ぎないからです。
アーキテクチャの面から見て、インフラストラクチャおよびユーザー インターフェイス ロジックからコア ビジネス ビヘイビアーを切り離せば、この原則に従うアプリケーションを論理的に構築することができます。 理想的には、ビジネス ルールとロジックを独立したプロジェクトに配置し、そのプロジェクトがアプリケーション内の他のプロジェクトに依存しないようにする必要があります。 このように切り離すことで、ビジネス モデルのテストが容易になり、さらに低レベルの実装の詳細に密結合せずに展開させることができます (また、これは、インフラストラクチャの懸念事項が、ビジネス レイヤーで定義された抽象化に依存している場合にも役立ちます)。 懸念事項の分離は、アプリケーション アーキテクチャでのレイヤー使用の背後にある主要な検討事項です。
カプセル化
アプリケーションのそれぞれの部分は、カプセル化を使用してアプリケーションの他の部分と切り離す必要があります。 アプリケーションのコンポーネントとレイヤーでは、外部のコントラクトに違反しない限りは、共同作業者を分断することなく内部の実装を調整できる必要があります。 同じインターフェイスが保持される限りは、オブジェクトとパッケージを代替実装に置換できることから、カプセル化を適切に使用すれば、アプリケーションの設計において疎結合とモジュール化を達成できます。
クラスでは、クラスの内部状態への外部アクセスを制限することによってカプセル化が実現されます。 オブジェクトの状態を操作する外部アクターの場合、オブジェクトのプライベートな状態に直接アクセスするのでなく、適切に定義された関数 (またはプロパティ セッター) を介して操作を行う必要があります。 同様に、アプリケーション コンポーネントおよびアプリケーション自体においても、状態に直接変更を加えることを許可するのではなく、適切に定義されたインターフェイスを公開して共同作業者が使用できるようにする必要があります。 このアプローチにより、パブリック コントラクトが保持される限りは、時間の経過と共に変化するアプリケーションの内部設計が解放され、それによるコラボレーターの分断を心配する必要がなくなります。
変更可能なグローバル状態は、カプセル化と相対するものです。 ある関数の変更可能なグローバル状態からフェッチされた値は、別の関数 (または同じ関数内のさらに他のもの) に同じ値を持つことを目的として依存することはできません。 ステートメントからメソッドやクラスに至るまで、あらゆる場所で使用されるさまざまなスコープの規則に対して C# などのプログラミング言語がサポートされている理由の 1 つに、変更可能なグローバル状態に関する懸念事項への理解があります。 アプリケーション内およびアプリケーション間の統合で、中央データベースに依存するデータ駆動型アーキテクチャで、データベースによって表される変更可能なグローバル状態に依存するように選択が行われていることは、注目に値します。 ドメイン駆動設計とクリーン アーキテクチャでは、データへのアクセスをカプセル化する方法と、永続化形式に直接アクセスすることによってアプリケーションの状態を無効にしないようにする方法が重要な考慮事項です。
依存関係の逆転
アプリケーション内の依存関係の方向は、実装の詳細の方向ではなく、抽象化の方向とする必要があります。 大部分のアプリケーションは、コンパイル時の依存関係のフローがランタイム実行の方向になるように記述され、直接的な依存関係グラフが生成されます。 つまり、クラス A がクラス B のメソッドを呼び出し、クラス B がクラス C のメソッドを呼び出す場合は、図 4-1 に示すように、コンパイル時にクラス A はクラス B に依存し、クラス B はクラス C に依存します。
図 4-1 直接的な依存関係グラフ。
依存関係逆転の原則を適用すると、A は B によって実装された抽象化でメソッドを呼び出すことができます。このことは、実行時に A が B を呼び出し、コンパイル時には B が、A によって制御されるインターフェイスに依存することを可能にします (つまり、通常のコンパイル時の依存関係が "逆転" されます)。 実行時に、プログラム実行のフローには変更がありませんが、インターフェイスの導入により、このインターフェイスのさまざまな実装を簡単に接続できるようになります。
図 4-2 逆転された依存関係グラフ。
依存関係逆転は、疎結合のアプリケーションをビルドする上で重要な部分となります。その理由は、より低いレベルの抽象化ではなく、より高いレベルの抽象化に依存しそれらを実装するように、実装の詳細を記述することができるからです。 したがって、結果として得られるアプリケーションは、テストが容易で、モジュール性が高く、保守も容易です。 依存関係挿入の実施は、次に示す依存関係逆転の原則に従うことで可能になります。
明示的な依存関係
メソッドやクラスでは、正しく機能するために必要となるコラボレーション オブジェクトをすべて明示的に要求すべきです。 明示的な依存関係の原則と呼ばれます。 クラスのコンストラクターは、クラスが有効な状態になるため、および適切に機能するために必要なことを、クラスが識別できるようにします。 構成および呼び出し可能なクラスであるが、特定のグローバルまたはインフラストラクチャ コンポーネントが揃っている場合にのみ適切に機能するクラスを定義した場合、そのようなクラスはクライアントに対して "悪意を持つ" クラスとなります。 コンストラクターのコントラクトは、指定された内容のみ必要であるとクライアントに通知します (クラスでパラメーターなしのコンストラクターのみが使用されている場合は、何もない可能性があります) が、実行時に、オブジェクトが実際に必要としたものは他の内容であったという結果になっています。
明示的な依存関係の原則に従うことで、クラスとメソッドは、機能するために何が必要かを、クライアントに対して正確に知らせるようになります。 原則に従うと、コードの自動文書化が進み、コーディング コントラクトがわかりやすくなります。これは、必要な内容がメソッドまたはコンストラクターのパラメーターの形式で指定される限り、操作するオブジェクトが実行時に正しく動作することを信頼できるようになるからです。
単一責任
単一責任の原則は、オブジェクト指向の設計に適用されますが、懸念事項の分離に類似したアーキテクチャ原則と考えることもできます。 この原則では、オブジェクトの責任は 1 つのみとし、オブジェクトの変更理由も 1 つのみとする必要があると明記されています。 具体的に、オブジェクトの変更が必要な状況は、オブジェクトの単一責任の実行方法を更新する必要がある場合のみとなります。 この原則に従えば、既存のクラスに他の責任を追加するのでなく、さまざまな種類の新しいビヘイビアーを新しいクラスとして実装できるので、疎結合でモジュール性の高いシステムがもたらされます。 新しいクラスを追加する方が既存のクラスを変更するよりも常に安全です。その理由はコードがまだ、新しいクラスに依存していないというところにあります。
モノリシック アプリケーションでは、高レベルにある単一責任の原則をアプリケーション内のレイヤーに適用できます。 プレゼンテーション責任は UI プロジェクト内に留め、データ アクセス責任はインフラストラクチャ プロジェクト内で保持する必要があります。 ビジネス ロジックは、アプリケーション コア プロジェクトで保持する必要があります。そこでは、ビジネス ロジックを容易にテストし、他の責任とは別個に発展させることができます。
この原則がアプリケーション アーキテクチャに適用され、その論理エンドポイントに到達すると、マイクロサービスが得られます。 指定されたマイクロサービスは、単一責任を持つ必要があります。 システムのビヘイビアーを拡張する必要がある場合は、既存の責任にさらに責任を追加するのでなく、別のマイクロサービスを追加することによって目的を達成することをお勧めします。
DRY 原則
アプリケーションでは、特定の概念に関連するビヘイビアーを複数の場所で指定すると、エラーの原因となることが多いので、回避する必要があります。 どこかの時点で要件を変更するには、この動作を変更する必要があります。 ビヘイビアーの少なくとも 1 つのインスタンスが更新に失敗し、システムの動作が不整合になる可能性があります。
ロジックは複製するのではなく、プログラミング コンストラクト内でカプセル化します。 このコンストラクトをこのビヘイビアーに対する単一権限とし、このビヘイビアーを必要とするアプリケーションの他の部分で新しいコンストラクターを使用するようにします。
注意
偶然に発生した繰り返しに過ぎないビヘイビアーを一緒にバインドすることは避けてください。 たとえば、概念的に 2 つの異なる定数がさまざまな内容を参照している場合、両方の定数の値が同じであるからといって、1 つの定数のみを使用すればよいという意味にはなりません。 誤った抽象化への結合ではなく、常に重複をお勧めします。
永続性の無視
永続性の無視 (PI) は永続化を必要とする型を参照しますが、そのコードは永続化技術の選択による影響を受けません。 そのような .NET 内の型は、特定の基本クラスから継承することも、特定のインターフェイスを実装することも必要ないため、Plain Old CLR Object (POCO) と呼ばれることがあります。 永続性の無視は重要です。これにより、同じビジネス モデルを複数の方法で永続化することができ、アプリケーションの柔軟性が向上するからです。 永続化の選択肢は時間の経過と共に、あるデータベース技術から別のデータベース技術へと変化する場合があります。また、アプリケーションを起動したものすべて (たとえば、リレーショナル データベースに加えて、Redis キャッシュまたは Azure Cosmos DB を使用) に加えて、永続化の追加フォームが必要な場合があります。
この原則に対する違反の例を次に示します。
必要な基本クラス
必要なインターフェイスの実装
自身を保存する責任のあるクラス ("アクティブ レコード" パターンなど)
必要なパラメーターなしのコンストラクター。
仮想キーワードを必要とするプロパティ
必要とされる永続化に固有の属性
上記のいずれかの機能またはビヘイビアーがクラスに含まれる必要があるという要件では、永続化する型と永続化技術の選択肢との間に結合が追加されるため、後で新しいデータ アクセス方法を採用することが難しくなります。
境界付けられたコンテキスト
境界付けられたコンテキストは、ドメイン駆動設計で中心となるパターンです。 このコンテキストでは、大規模なアプリケーションまたは組織における複雑さを個々の概念モジュールに分割して、その複雑さに対応する方法を提供します。 各概念モジュールは、他のコンテキストから分離された (境界付けされた) コンテキストを表し、個別に展開できます。 各境界付けられたコンテキストは、コンテキスト内で概念を表す独自の名前を自由に選択できることが理想的であり、独自の永続化ストアへの排他的なアクセス権限を有する必要があります。
少なくとも、個々の Web アプリケーションでは、データベースを他のアプリケーションと共有するのでなく、ビジネス モデルに対して独自の永続化ストアを備えることで、独自の境界付けられたコンテキストになることを目指す必要があります。 境界付けられたコンテキスト間の通信は、共有データベースを介してではなくプログラム インターフェイスを介して行われます。これにより、発生した変更に応じてビジネス ロジックおよびイベントを実行することができます。 境界付けられたコンテキストはマイクロサービスに正確にマップされます。このマイクロサービスもまた、それぞれ独自の境界付けられたコンテキストとして実装されるのが理想的です。
その他の技術情報
.NET