次の方法で共有


アーキテクチャの原則

ヒント

このコンテンツは、ASP.NET Core と Azure を使用した最新の Web アプリケーションの設計に関する電子ブックからの抜粋です。 .NET Docs またはオフラインで読み取ることができる無料のダウンロード可能な PDF として入手できます。

ASP.NET Core と Azure eBook の表紙サムネイルを使用して最新の Web アプリケーションを設計します。

「もし建築業者がプログラマーがプログラムを書く方法で建物を建てたなら、最初に来たキツツキは文明を破壊するだろう。
- ジェラルド・ウェインベルク

保守性を念頭に置いてソフトウェア ソリューションを設計し、設計する必要があります。 このセクションで説明する原則は、クリーンで保守可能なアプリケーションにつながるアーキテクチャ上の決定に役立ちます。 一般に、これらの原則は、アプリケーションの他の部分と緊密に結合されず、明示的なインターフェイスまたはメッセージング システムを介して通信する個別のコンポーネントからアプリケーションを構築するためのガイドとなります。

一般的な設計原則

懸念事項の分離

開発を進める際の基本原則は 、懸念事項の分離です。 この原則では、実行する作業の種類に基づいてソフトウェアを分離する必要があることをアサートします。 たとえば、ユーザーに表示する注目すべき項目を識別するためのロジックを含み、特定の方法でそのような項目をより目立たせる書式を設定するアプリケーションを考えてみましょう。 書式設定する項目を選択する動作は、アイテムの書式設定を担当する動作とは別に保持する必要があります。これらの動作は、互いに偶然に関連するだけの個別の懸念事項であるためです。

アーキテクチャ上、コア ビジネス動作をインフラストラクチャおよびユーザー インターフェイス ロジックから分離することで、この原則に従ってアプリケーションを論理的に構築できます。 ビジネス ルールとロジックは、アプリケーション内の他のプロジェクトに依存しない別のプロジェクトに存在する必要があるのが理想的です。 この分離により、ビジネス モデルを簡単にテストでき、低レベルの実装の詳細に密に結合することなく進化させることができます (また、インフラストラクチャの懸念がビジネス レイヤーで定義されている抽象化に依存している場合にも役立ちます)。 懸念事項の分離は、アプリケーション アーキテクチャでのレイヤーの使用の背後にある重要な考慮事項です。

カプセル化

アプリケーションのさまざまな部分で カプセル化 を使用して、アプリケーションの他の部分からそれらを絶縁する必要があります。 アプリケーション コンポーネントとレイヤーは、外部コントラクトに違反しない限り、コラボレーターを中断することなく、内部実装を調整できる必要があります。 カプセル化を適切に使用すると、同じインターフェイスが維持されている限り、オブジェクトとパッケージを代替実装に置き換えることができるため、アプリケーション設計で疎結合とモジュール性を実現できます。

クラスでは、カプセル化は、クラスの内部状態への外部アクセスを制限することによって実現されます。 外部アクターがオブジェクトの状態を操作する場合は、オブジェクトのプライベート状態に直接アクセスするのではなく、適切に定義された関数 (またはプロパティ セッター) を使用して操作する必要があります。 同様に、アプリケーション コンポーネントとアプリケーション自体は、状態を直接変更するのではなく、コラボレーターが使用できるように明確に定義されたインターフェイスを公開する必要があります。 このアプローチにより、パブリック コントラクトが維持されている限り、コラボレーターが中断することを心配することなく、アプリケーションの内部設計を時間の経過と同時に進化させることができます。

変更可能なグローバル状態は、カプセル化とは反抗的です。 ある関数の変更可能なグローバル状態からフェッチされた値は、別の関数 (または同じ関数内のそれ以上) で同じ値を持つことに依存できません。 変更可能なグローバル状態に関する懸念を理解することは、C# などのプログラミング言語がさまざまなスコープ規則をサポートしている理由の 1 つです。これは、ステートメントからメソッド、クラスまで、あらゆる場所で使用されます。 アプリケーション内とアプリケーション間の統合のために中央データベースに依存するデータドリブン アーキテクチャ自体は、データベースによって表される変更可能なグローバル状態に依存することを選択していることは注目に値します。 ドメイン駆動型の設計とクリーン アーキテクチャの主な考慮事項は、データへのアクセスをカプセル化する方法と、永続化形式への直接アクセスによってアプリケーションの状態が無効にされないようにする方法です。

依存関係の反転

アプリケーション内の依存関係の方向は、実装の詳細ではなく抽象化の方向にする必要があります。 ほとんどのアプリケーションは、コンパイル時の依存関係がランタイム実行の方向に流れ、直接の依存関係グラフを生成するために記述されます。 つまり、クラス A がクラス B のメソッドを呼び出し、クラス B がクラス C のメソッドを呼び出す場合、コンパイル時にクラス A はクラス B に依存し、クラス B は図 4-1 に示すようにクラス C に依存します。

直接依存関係グラフ

図 4-1 直接依存関係グラフ。

依存関係反転の原則を適用すると、A は B が実装する抽象化に対してメソッドを呼び出すことができます。これにより、A は実行時に B を呼び出すことができますが、B はコンパイル時に A によって制御されるインターフェイスに依存します (したがって、一般的なコンパイル時の依存関係 を反転 します)。 実行時に、プログラム実行のフローは変更されませんが、インターフェイスの導入は、これらのインターフェイスのさまざまな実装を簡単に接続できることを意味します。

逆依存関係グラフ

図 4-2 逆依存関係グラフ。

依存関係の反転 は、疎結合アプリケーションを構築する際の重要な部分です。実装の詳細は、他の方法ではなく、より高いレベルの抽象化に依存して実装するように記述できるためです。 結果として得られるアプリケーションは、よりテスト可能でモジュール式で保守可能です。 依存関係の挿入のプラクティスは、依存関係反転の原則に従うことによって可能になります。

明示的な依存関係

メソッドとクラスでは、正しく機能するために必要な共同作業オブジェクトが明示的に必要です。 明示的な 依存関係の原則と呼ばれます。 クラス コンストラクターは、クラスが有効な状態になり、適切に機能するために必要なものを識別する機会を提供します。 構築して呼び出すことができるが、特定のグローバルコンポーネントまたはインフラストラクチャコンポーネントが配置されている場合にのみ適切に機能するクラスを定義した場合、これらのクラスはクライアントに対して 不正 です。 コンストラクター コントラクトは、指定された内容のみを必要とすることをクライアントに伝えています (クラスがパラメーターなしのコンストラクターを使用しているだけの場合は何も必要ありません)、実行時にオブジェクトに本当に何か他のものが必要であることが判明します。

明示的な依存関係の原則に従うことで、クラスとメソッドは、機能するために必要なものについてクライアントに正直になります。 この原則に従うと、コードの自己文書化とコーディング コントラクトの使いやすさが高くなります。ユーザーは、メソッドまたはコンストラクター パラメーターの形式で必要なものを提供する限り、実行時に操作しているオブジェクトが正しく動作することを信頼するようになるためです。

1 つの責任

単一責任の原則はオブジェクト指向設計に適用されますが、懸念事項の分離に似たアーキテクチャ原則と見なすこともできます。 オブジェクトには 1 つの責任しか必要とされておらず、変更する理由は 1 つしかないことが示されています。 具体的には、オブジェクトが変更される唯一の状況は、オブジェクトが 1 つの責任を実行する方法を更新する必要がある場合です。 この原則に従うと、既存のクラスに追加の責任を追加するのではなく、多くの種類の新しい動作を新しいクラスとして実装できるため、より疎結合でモジュール化されたシステムを生成するのに役立ちます。 コードはまだ新しいクラスに依存しないため、新しいクラスの追加は、既存のクラスを変更するよりも常に安全です。

モノリシック アプリケーションでは、単一責任の原則をアプリケーションのレイヤーに高いレベルで適用できます。 プレゼンテーションの責任は UI プロジェクトに残る必要があります。一方、データ アクセスの責任はインフラストラクチャ プロジェクト内に保持する必要があります。 ビジネス ロジックは、簡単にテストでき、他の責任とは別に進化できるアプリケーション コア プロジェクトに保持する必要があります。

この原則がアプリケーション アーキテクチャに適用され、その論理エンドポイントに適用されると、マイクロサービスが得られます。 特定のマイクロサービスには、1 つの責任が必要です。 システムの動作を拡張する必要がある場合は、通常、既存のマイクロサービスに責任を追加するのではなく、マイクロサービスを追加することをお勧めします。

マイクロサービス アーキテクチャの詳細

DRY 原則

この方法はエラーの頻繁な原因であるため、アプリケーションでは、特定の概念に関連する動作を複数の場所で指定しないようにする必要があります。 ある時点で、要件を変更するには、この動作を変更する必要があります。 動作の少なくとも 1 つのインスタンスは更新に失敗し、システムの動作に一貫性がない可能性があります。

ロジックを複製するのではなく、プログラミングコンストラクトにカプセル化します。 このコンストラクトをこの動作に対する単一の権限にし、この動作を必要とするアプリケーションの他の部分で新しいコンストラクトを使用します。

偶然に繰り返されるだけの動作を結合しないようにします。 たとえば、2 つの異なる定数の値が同じであるからといって、概念的に異なるものを参照している場合は、定数を 1 つだけ持つ必要があるわけではありません。 重複は、不適切な抽象化に結びつけるよりも常に望ましい。

永続性の無視

永続化無視 (PI) とは、永続化する必要があるが、コードが永続化テクノロジの選択の影響を受けない型を指します。 .NET のこのような型は、特定の基底クラスから継承したり、特定のインターフェイスを実装したりする必要がないため、Plain Old CLR Objects (POCO) と呼ばれることもあります。 永続化の無視は、同じビジネス モデルを複数の方法で永続化でき、アプリケーションの柔軟性が向上するため、価値があります。 永続化の選択は、時間の経過と共に、データベース テクノロジ間で変わる場合があります。また、アプリケーションが開始した内容 (たとえば、リレーショナル データベースに加えて Redis Cache や Azure Cosmos DB を使用するなど) に加えて、追加の形式の永続化が必要になる場合があります。

この原則の違反の例を次に示します。

  • 必須の基底クラス。

  • 必要なインターフェイスの実装。

  • 自身を保存するクラス (アクティブ レコード パターンなど)。

  • 必須のパラメーターなしのコンストラクター。

  • 仮想キーワードを必要とするプロパティ。

  • 必要とされる永続化に固有の属性

クラスに上記のいずれかの機能または動作が含まれているという要件により、永続化する型と永続化テクノロジの選択の間の結合が追加され、将来的に新しいデータ アクセス戦略を採用することが困難になります。

境界付きコンテキスト

境界付きコンテキスト は、Domain-Driven デザインの中心的なパターンです。 大規模なアプリケーションや組織で複雑さに取り組むための方法を提供します。これを別の概念モジュールに分割します。 その後、各概念モジュールは、他のコンテキスト (したがって、境界付け) から分離され、独立して進化できるコンテキストを表します。 境界付けられた各コンテキストは、理想的には、その中の概念に対して独自の名前を自由に選択でき、独自の永続化ストアへの排他的アクセス権を持つ必要があります。

少なくとも、個々の Web アプリケーションは、データベースを他のアプリケーションと共有するのではなく、ビジネス モデル用の独自の永続化ストアを使用して、独自の境界付けられたコンテキストにしようと努める必要があります。 境界付けられたコンテキスト間の通信は、共有データベースではなく、プログラム インターフェイスを介して行われます。これにより、発生した変更に応じてビジネス ロジックとイベントを実行できます。 境界付きコンテキストはマイクロサービスに密接にマップされます。これは、独自の個別の境界付けられたコンテキストとしても理想的に実装されます。

その他のリソース