モジュラーアーキテクチャと再利用可能なコード



私は常に再利用可能な全体的なコードの開発に興味を持っています。 しかし、再利用可能なコードの問題は、別のインフラストラクチャへの移植の段階で始まります。 アプリケーションがプラグインによって展開される場合、プラグインは特定のアプリケーション用に作成されます。 しかし、アプリケーションロジックをプラグイン(以降、モジュールと呼びます)に配置し、アプリケーションインターフェイスをコントロールリンクからモジュールによって管理されるコンポーネントに変換するとどうなりますか。 私の意見では、このようなシナリオで最も重要なタスクは、基本的なインターフェイスを最小限に簡素化し、インフラストラクチャ全体の断片を個別に書き換えまたは拡張する機会を提供することです。 モジュラーコードのアイデアから生まれたものに興味があるなら、catへようこそ。

アイデア


今後のシステムの最初の条件は、個々のモジュールを再コンパイルせずにシステムを動的に拡張できることです。 これは、ホストとモジュールの両方に適用されます。


ソリューションの任意の部分(基本的なインターフェイスを除く)を書き換えて、動的に統合できます。 インターフェイスを使用してモジュールを拡張する可能性に加えて、任意のモジュールで使用可能なパブリックメソッド、プロパティ、およびイベントに動的にアクセスできるようにしたいと考えました。 したがって、アクセシビリティによってパブリックとしてマークされている基本的なIPluginインターフェイスを実装するクラスのすべての要素は、他のモジュールから外部から見える必要があります。


任意のモジュールを削除してインフラストラクチャに追加できますが、同時に、あるモジュールを別のモジュールに置き換えることを決定する場合、削除したモジュールのすべての機能を実装する必要があります。 つまり モジュールは、AssemblyGuidAttribute属性によって識別されます。この属性は、プロジェクトの作成時に自動的に追加されます。 したがって、同じ識別子を持つ2つのモジュールはロードされません


各モジュールは軽量である必要があります。これにより、ベースインターフェイスを絶えず更新する必要がなくなり、必要に応じて、モジュールをシステムから削除し、リンクを介して通常のアセンブリとしてアプリケーションに統合できます(参照)。 幸いなことに、CLRは遅延読み込み(LazyLoad)を通じて依存アセンブリを読み込むため、モジュール式のインフラストラクチャアセンブリは必要ありません。


そして最後の条件として、システムは開発者に機能の段階的な拡張を提供して、エントリのレベルが十分に低いレベルになるようにする必要があります。


同時に、システムはアプリケーションからアプリケーションへと繰り返されるルーチンタスクを自動化する必要があります。 すなわち:



解決策


蓄積されたソリューションと個々のコンポーネントが単一の原則に基づいて機能する結果、インフラストラクチャ全体の共通のビジョンがまとめられました。


  1. メインインターフェイスの最小要件、
  2. モジュールをロードするための独立したソースを備えたモジュール式インフラストラクチャ
  3. 共有設定リポジトリ
  4. アプリケーション(UI、サービス)の実装からのソリューションの独立性:
    1. 執筆時点でのホスト:

特定のアプリケーションとプログラム自体の両方から開発の独立性を提供するために、次の主要コンポーネントが登場しました。



既製の基本ビルド


これらの要件の結果、次の基本的なアセンブリが形成されました。



システムを最小限に機能させるには、SAL.Coreへのリンクを追加し、必要に応じて拡張機能を実装または使用し、対応するインターフェイス拡張機能セットへのリンクを追加します。 または、必要な抽象化を使用して、最小限のインターフェイスセットを個別に拡張します。


ホストを起動するときに最初に行うことは、設定と外部プラグイン(LoaderProviderとSettingsProvider)を読み込むためにホストに組み込まれた基本モジュールを初期化することです。


プラグインプロバイダーが最初に初期化され、次に設定プロバイダーが初期化されます。 ホストに組み込まれたローダーは、アプリケーションフォルダー内のすべてのプラグインを検索し、依存アセンブリの検索イベントにサブスクライブします。 次に、ホストに統合された設定プロバイダーが、ユーザープロファイルにあるXMLファイルから設定を読み込みます。 両方のプロバイダーは階層継承インフラストラクチャをサポートし、次のプロバイダーを発見すると、新しいプロバイダーの親になります。 プロバイダーが必要なリソースを見つけられない場合、リソース要求は親プロバイダーに宛てられます。


すべてのプロバイダーの初期化プロセスが完了すると、すべてのカーネル、残りのプラグインが初期化されます。 他のモジュールとは異なり、カーネルプラグインは最初に初期化され、他のプラグインのロードをキャンセルする機能を備えた他のプラグインのロードイベントにサブスクライブする機会を得ます。


この動作は、他のタイプのプラグインをロードする階層を観察する必要がある場合、ホスト上で書き直すことができます。 現在、ロードモジュールのシーケンスをカーネルに移動することを考えています。


アセンブリをダウンロードする


標準のLoaderProviderは、リフレクションを通じて、IPluginを実装するすべてのパブリッククラスを探しますが、これは正しいアプローチではありません。 実際には、コードで特定のクラスが呼び出されるか、リフレクションを介して特定のクラスが呼び出され、このクラスがサードパーティアセンブリを参照しない場合、 AssemblyResolveイベントは発生しません。 つまり、モジュールインフラストラクチャからアセンブリを削除し、リンクを追加することで通常のアセンブリとして使用できます。SAL.dllの必要性はなくなります。 ただし、基本的なモジュールプロバイダーは、現在のフォルダーとすべてのアセンブリオブジェクトをスキャンするという原則に基づいて実装されているため、すべての参照アセンブリのAssemblyResolveイベントは、モジュールの読み込み時に発生します。


この問題を解決するために、単純なブートローダー用のいくつかのオプションを作成しましたが、動作は異なります。 一部では、事前にアセンブリのリストを指定する必要があり、一部ではフォルダー自体をスキャンする必要があります。


将来、この問題を解決するためのオプションの1つとして、以下で説明するPEReaderアセンブリを使用できます。


SAL.Core


開発を簡素化するための抽象クラスに実装された基本的なインターフェイスと小さなコード。 フレームワークのフレームワークの最小バージョンとして、.NET Framework v2.0バージョンが選択されました。 最低限必要なバージョンを選択すると、このバージョンのフレームワークをサポートする任意のプラットフォームでベースを使用できます。また、下位互換性(起動時にランタイムを選択)により、.NET Coreまでのベースを使用できます(これまでは除く)。


理論的には、基底クラスは、あらゆる状況で使用できるようにするための基本的な基盤である必要があります。 実際には、確実に拡張する必要がある条件があります。 この場合、抽象クラスのコード全体を書き換えて、独自の実装でインターフェースを拡張できます。 したがって、このアセンブリでは可能な限り最小限のコードです。


執筆時点では、基本的なインターフェイスを継承する唯一のホストはWinServiceアプリケーションのホストです。


SAL.Wndows


これは、WinFormsおよびWPFに基づいてアプリケーションを記述するための基礎を提供する基本クラスのセットです。 この構造には、抽象メニュー、ツールバー、ウィンドウを操作するためのインターフェイスが含まれています。



SAL.EnvDTE


拡張の観点から、Visual StudioのアドインとしてのホストはSAL.Windowsインターフェイスを拡張し、VS固有の機能を追加します。 依存プラグインがVisual Studioと相互作用するカーネルを見つけられない場合、限られた機能で引き続き動作できます。


SAL.Coreインターフェースをサポートするすべての記述ホストは、次の機能を自動化します。




これらのインターフェイスには、次のホストが実装されています。



イベントログは、標準のSystem.Diagnostics.Traceを通じて実装されます。 MDI、Dialog、およびWinServiceホストでは、app.configに登録されたリスナーは、受信したイベントをシングルトン経由でアプリケーションに送り返そうとします。イベントは、イベントに応じてログウィンドウ(OutputまたはEventList)に表示されます。 devenv.exeの場合、トレースリスナーをapp.configに登録することもできますが、この場合、ホストアセンブリをロードしてからアドインとしてロードします。 したがって、トレースリスナはプログラムでコードに追加されます(VS Output ToolBarまたはモーダルウィンドウに表示されます)。


記述されたインフラストラクチャにより、HTTPアプリケーションの方向で開発できますが、このためには、少なくとも認証、承認、およびキャッシュを提供するモジュールの一部を実装する必要があります。 以下で説明するTTManagerアプリケーションの場合、WEBサービス用の独自のホストが実装され、必要なすべての機能が実装されましたが、残念ながら、汎用アプリケーションとしてではなく、特定のタスク用に作成されました。


ロギングして個別のモジュールに分割するこのアプローチにより、新しい環境で起動するときにボトルネックを簡単に特定できます。 たとえば、Windows 10にモジュールの配列を展開するとき、他のバージョンのOSよりも読み込みに時間がかかることがわかりました。 私の古いWinXPマシンでも、35モジュールのロードには最大5秒かかります。 しかし、Win10では、1つのモジュールをロードするプロセスに非常に長い時間がかかりました。



独立したアーキテクチャのおかげで、問題のモジュールを即座にローカライズすることができました。 (この場合、問題はWindows 10でv2.0ランタイムを使用していました)。


既製のモジュール


インフラストラクチャの最初のバージョンは2009年に登場しました。 テスト用と仕事用の簡単なタスクの実行を高速化するために、さまざまなタスクを自動化する多数の多様で独立したモジュールが蓄積されています(すべての画像はクリック可能で、モジュールはプロジェクトページでダウンロードできます)。


Webサービス/ Windows Communication Foundationテストクライアント



このアプリケーションの中心にあるのは、Visual Studio-WCFテストクライアントに付属するアプリケーションです。 私の意見では、元のソースには多くの不快な瞬間があります。 WCFへの移行時までに、私はすでに通常のWebService'ahで多くのアプリケーションを作成していました。 ILSpyを介してプログラムの原則を研究した後、WCFだけでなくWSクライアントの機能を拡張することにしました。 その結果、メインプログラムを分析して、次の高度な機能を備えたプラグインを作成しました。


  1. WebServiceアプリケーションのサポート(Soapヘッダーを除く)、
  2. 古いバインディングを使用してサービスをテストする機能(開かれたとき、プロキシクラスは自動的に更新されませんが、UIからの要求時にのみ更新されます)
  3. Visual Studioからの独立(ILMergeによる依存アセンブリの組み合わせ)
  4. 追加されたすべてのサービスをツリー形式で表示します。1つのサービスのみでは機能しません。
  5. ツリーのすべてのノードの検索機能、
  6. サービスリクエストフォームにタイマーが追加され、リクエストの完了にかかった時間を追跡し、
  7. テストフォームまたはアプリケーション全体を閉じて開くときの送信されたパラメーターの回復を追加しました。
  8. メソッドテストフォームのボタンをクリックして、パラメーターをファイルに保存およびロードする機能を追加しました。
  9. メソッドパラメータを自動保存およびロードする機能が追加されました(Plugin.Configurationモジュール→入力値の自動保存が必要になります[False])
  10. SvcConfigEditor.exeを使用して.configファイルを編集する機能が壊れています


RDPクライアント



繰り返しますが、プログラムの主なソースはM $プログラマーでした。 このプログラムはRDCManプログラムに基づいていますが、メインプログラムとは異なり、接続されたサーバーウィンドウをダイアログインターフェースに埋め込むことにしました。 また、リモート設定リポジトリは、関係するすべての同僚のサーバーのリストを最新の状態に保つのに役立ちました。



PE情報



このアプリケーションのソースには、他のアプリケーションでは見つけられなかった新しい自動化のアイデアがあります。 このようなアプリケーションを作成する目的は次のとおりです。


  1. ほとんどのディレクトリとメタデータテーブルを含むPEファイルのコンテンツを表示するためのインターフェイスを提供します(ただし、RT_DIALOGリソースの出力は元のものとは大きく異なります)。
  2. PE / CLIファイルの構造による検索
  3. ファイルシステムからだけでなく、WinAPI LoadLibrary関数を介してPEファイルの読み込みを有効にします。 LoadLibraryを介してロードする場合、アンパックされたPEファイルを読み取る機会があり、 RVAを計算する必要はありません。

実行ファイルに特定の機能が実装されていることが何度か判明しましたが、この機能は時代遅れであるか、誰も使用していませんでした。 特定のオブジェクトの使用について、異なる言語のアプリケーションのソースコードを検索しないようにするために、このアプリケーションが作成されました。 たとえば、一般的なリポジトリにアセンブリがあり、このアセンブリから1つのメソッドを削除することにしました。 この方法が同僚による他のプロジェクトの現在の依存ビルドで使用されているかどうかを調べる方法は? すべてのソースコードを確認するように依頼したり、ソース管理検索で検索したり、コンパイルされたアセンブリ内で同じ名前のメソッドを検索したりできます。 次の2つのコンポーネントで構成されます。


  1. PEReaderアセンブリ(安全でないトークンなしで記述)、そのソースコードはGitHubで利用可能です。
  2. SAL.Windows抽象化レイヤーを使用する、SALインフラストラクチャのプラグインであるクライアント部分。

PE、DEX、ELF、およびByteCodeファイルの階層を検索するために、インフラストラクチャに完全に適合する別のモジュールReflectionSearchが作成されました。 リフレクションを介してオブジェクトを検索するロジック全体がこのモジュールで取り除かれ、実行可能プログラムを読み取るためのモジュール内のいくつかのパブリックメソッドのおかげで、コードの再利用性を実現することができました。


残り


個別のアイテムごとに既製のモジュールのリスト全体を説明しないために、残りのモジュールを1つのリストで説明します。


  1. ELFイメージ情報 -PE情報と同様にELFファイルを分解します。 GitHubのElfReader
  2. ByteCode(.class)情報 JVM .classファイルを逆アセンブルします。 GitHubのByteCode Reader
  3. DEX(Davlik)情報-Androidアプリケーションで使用されるDEX形式の分解。 GitHubのDexReader
  4. Reflection Search-リフレクションを介してオブジェクトを検索するためのアセンブリ。 以前はPE Infoモジュールの一部でしたが、他のモジュールの出現により、PE、ELF、DEX、ByteCodeモジュールのパブリックメソッドを使用して別のモジュールに転送されました。
  5. .NETコンパイラ -現在のAppDomainのリアルタイム.NETコードコンパイラ。 コード(TextBox)を記述し、コンパイル済みアプリケーションをホストし、コンパイル済みコードをキャッシュし、コンパイル済みコードを別のアセンブリとして保存する機能を提供します(HTTP Harvesterアプリケーション自動化の2回目の繰り返しで使用[以下で説明])。
  6. ブラウザ -DOM要素への高度なXPath受信機能( HtmlAgilityPackに似た自己記述 )を備えたTridentのホスティング。 (アプリケーション自動化HTTPハーベスタの3回目の反復で使用されます[以下で説明])。
  7. 構成 -プラグイン設定を編集するためのユーザーインターフェイス。SAL.Windowsを使用する場合、UIを介してすべての設定にアクセスできるわけではないためです。
  8. メンバー -外部呼び出しに使用できるパブリックUIプラグイン要素に表示します。
  9. DeviceInfo-互換性のあるデバイスからSMART属性を読み取ることができ、安全でないトークンなしで動作するアセンブリ。 すべてのデータを取得するには、WinAPI関数DeviceIOControlが使用さます 。アセンブリ自体のソースコードはGitHubで入手できます
  10. 単一インスタンス -アプリケーションを単一インスタンスに制限します(キー交換は.NET Remotingを介して実行されます)。
  11. SQL設定プロバイダー -MSSQLから設定を保存およびロードするためのプロバイダー。 (コードはADO.NETとストアドプロシージャで書かれており、統一の範囲が広いため、個々のDBMSについては、ストアドファイルの独自の実装を記述する必要があります)、
  12. SQL Assembly scripter-.NETアセンブリからMicrosoft SQL Serverスクリプトを作成して、MSSQLにマネージコードをインストールします(安全でないアセンブリではテストされていません)。
  13. Winlogon-このモジュールは、 SENSインターフェイスのパブリックイベントを提供します。 最初のバージョンはWinlogonを使用していましたが、サポートされなくなりました。
  14. EnvDTE.PublishCmd-このモジュールの詳細をここで説明しました
  15. EnvDTE.PublishSql-手動公開の前後に、ADO.NETを介して任意のSQLクエリを実行し、テンプレート値を表示します。

残りはここにあります (合計で約30のモジュールが投稿されています)。 すべてのモジュールの画像はこちら


既製のソリューション


モジュラーアーキテクチャで複合体全体を構築することの利便性を示すために、さまざまな原則に基づいて構築された既製のソリューションをいくつか紹介します。



TTManager



基本的にタスクのさまざまなソースを使用する機能を持つ動的拡張システムを使用したタスクシステム用のアプリケーション。 その結果、さまざまなソースからタスクを作成、エクスポート/インポート、表示できる統一されたインターフェイスが作成されます。 現在、ソースとしてMSSQL、WebServiceおよびMegaplanタスク(広告ではない)の部分的にREST APIをサポートしています。 WebServiceは、SAL.Web基本クラスを使用して、同様の方法で作成されます。 そのため、WebService自体もソースとしてMSSQL、Megaplan、またはWebServiceを使用できます。


仕組み

カーネルアプリケーションプラグイン、すべてのタスクソースプラグイン(DAL)の遅延読み込み検索。 データにアクセスするための複数のプラグインが見つかった場合、クライアントは使用したいプラグインを選択するよう求められます(SAL.Windowsでのみ、ユーザーインターフェイスのないホストで-エラーでクラッシュします)。 依存プラグインは、カーネルモジュールを介して選択したDALプラグインにアクセスします。


興味深い点

この例では、カーネルプラグインは他の依存プラグインからのインターフェイスによって抽象化されています。 この場合、別のカーネルモジュールを記述する(または現在のモジュールを書き換える)ことができます。 または、一般的なプラグインを書き換えて、タスクの複数のソースを同時に操作できるようにします。

タスクのステータスに関する問題を解決するために、一部のDALプラグイン内にステータスのマトリックスが縫い付けられています(または、タスクのソースから取得されます(存在する場合))。 この場合、1つのソースから別のソースへのデータ転送に問題はありません。


HTTPハーベスター




このアプリケーションでは、既製のプラグインを使用して、TridentまたはWebRequestを通じてサイトを解析できます。 解析にはいくつかのレベルの抽象化が利用可能です。 最下位レベルでは、DOMを使用した応答またはサーバーからの応答を開いて解析する追加のプラグインを作成できます。 上記のレベルでは、ランタイムで.NETコードを記述できます。これは、プラグイン「.NETコンパイラ」を介してコンパイルされ、ランタイムでTridentに表示されるページの結果に適用されます。 最高レベルには、トライデントに表示されるサイトのページ上の要素のUIによる表示が含まれます。 そして、テンプレートのxpath(自己記述バージョン)を適用した後、「。NETコンパイラ」プラグインから.NETコードを処理または実行するために、ユニバーサルプラグインに転送します。


仕組み

このモジュールは、カーネルプラグインに応じて、既製の出力インターフェイスとデータをダウンロードするための基本的なユーザーインターフェイスのいずれかを選択するために提供されます。 ログを記録する機能を備えたTridentまたはWebRequest。 カーネルは、インターフェイスだけでなく、個々のモジュールごとにポーリングタイマーも提供します。


出力インターフェイスは、テーブル内の最後のオープン位置を保存する機能を備えた、データ出力コンテナを備えた標準のGridViewを提供します。 デフォルトでは、コンテナは画像またはテキストデータの表示をサポートしています。


興味深い点

この場合、インターフェイスでカーネルプラグインから外すことはなく、すべての依存プラグインは、ロードされたプラグインの配列で特定のカーネルプラグインを見つけることを期待しています。


アプリケーションは3回の反復で作成されました(SAL.Windowsのみ):


  1. カーネルプラグインで説明されているTridentを操作するための基本的なコントロールとメソッドの配列を使用して、プラグインを記述する機会が作られました。
  2. これで、Plugin.Compilerで生成および編集されたランタイムコードを使用して、プラグインのコードを置き換えることができます
  3. Trientで、UIを介してHTMLノードへのパスを指定できるようになりました。 その結果、ランタイムまたはオンラインコードのキー/値配列が提供されます。値は、 HtmlAgilityPackの実装と同様のHTML要素へのパスです。


すでに古くて削除されているもの


  1. Office 2010のホストを削除しました。コンテキストメニューからTTManagerのタスクを作成するためだけに作成されましたが、松葉杖が豊富で機能が制限されているため、さらなるサポートは実用的ではありませんでした。
  2. ATL経由でEnvDTEにウィンドウを作成する機能を削除しました。 VS 2007より前は、スタジオでウィンドウを作成する機能は、ATLとCOMによってのみ実現されていました。 その後、すべてを.NETで実行できるようになりました。
  3. アドインとして実装されたEnvDTEの非推奨ホスト


既知のバグ


EnvDTEホストは、英語のスタジオでのみ検証されています。 ローカライズ版で問題が発生する場合があります(VS11でロシア語のローカライズをテストした後)


Winlogon(SENS)プラグインがロードされ、ユーザーがアドインマネージャーを介してホストをアンロードすることを決定した場合、EnvDTEホストはスタジオを閉じます。 (Windows 10に対応)。


なぜなら ホストは完全な拡張としてではなく、アドインとして記述されているため、EnvDTEに基づく他の製品との互換性はありません。


今後の開発の予測は何ですか


組み込みクラスSystem.Web.Caching.CacheおよびSystem.Runtime.Caching.MemoryCacheに加えて、キャッシュ機能を使用する場合は、リモートキャッシュを使用できます。 たとえば、AppFabric。 キャッシング用の基本的なクライアントインターフェイスを記述することにより、各タイプのキャッシュに対応するモジュールの配列を開発し、必要に応じて目的のモジュールを選択できます(発行時点では、すでに記述されていますが、レイアウトされていません)。


作成時のモジュールは、ファイルシステムからファイルシステムからメモリにロードし、XMLファイルをTOCとして使用してネットワーク経由で更新できます。 さらなる開発により、ファイルシステムからリポジトリとして使用できるだけでなく、nugetをリポジトリとして使用したり、モジュールをリモートで実行できるホストを実装したりできます。


ユーザーのパーソナライズは、ロールとクレームの両方で可能です。 ただし、OpenId、OAuth、OpenId Connectを使用する場合、多くのプロバイダーがあり、各プロバイダーはSystem.Security.Principal.IIdentity(役割ベースの認証を使用する場合)またはSystem.Security.Claims.ClaimsIdentity(クレーム認証を使用する場合)を取得する必要があります。 したがって、LinedInのクライアントを作成すると、再コンパイルせずに任意のアプリケーションで使用できます。


メッセージキューを使用する場合、ServiceBusの機能を実行するモジュールと一連のインターフェイスを記述できます。特定のキューを実装するモジュールは、メッセージの送受信を既に担当しています。


SSISまたはBizTalkサービスと同様に、パブリックモジュールメソッドを動的にバインドするためのUIインターフェイスを作成できます。

Source: https://habr.com/ru/post/J303032/


All Articles