web-dev-qa-db-ja.com

NuGet依存関係の地獄を解決する方法

会社のプロジェクトCompanyName.SDKに統合する必要があるCompanyName.SomeSolutionという名前の機能を備えたライブラリを開発します

CompanyName.SDK.dllはNuGetパッケージを介して展開する必要があります。また、CompanyName.SDKパッケージは、サードパーティのNuGetパッケージに依存しています。良い例として、 Unity を見てみましょう。現在の依存関係は、v3.5.1405-prerelease of Unityです。

CompanyName.SomeSolution.Project1Unityv2.1.505.2に依存します。 CompanyName.SomeSolution.Project2Unityv3.0.1304.1に依存します。

CompanyName.SDKをこのソリューションに統合すると、Unityv3.5.1405-prereleaseへの依存関係が追加されます。 CompanyName.SomeSolutionには、上記の2つとCompanyName.SomeSolution.Applicationに依存する1つの実行可能な出力プロジェクトCompanyName.SDKがあるとします

そして、ここから問題が始まります。すべてのUnityアセンブリは、バージョン指定子のないすべてのパッケージで同じ名前を持ちます。ターゲットフォルダーでは、Unityアセンブリの1つのバージョンのみになります:v3.5.1405-prerelease via bindingRedirect in app.config

Project1Project2、およびSDKのコードは、コーディング、コンパイル、およびテストされた依存パッケージの正確に必要なバージョンをどのように使用できますか?

注1:Unityは単なる例です。実際の状況は、サードパーティのモジュールが別のサードパーティのモジュールに依存しており、3から4のバージョンが同時にあるため、10倍悪化しています。

NOTE2:別のパッケージの最新バージョンではない依存関係を持つパッケージがあるため、すべてのパッケージを最新バージョンにアップグレードできません。

注3:依存パッケージにバージョン間で重大な変更があると仮定します。この質問をするのは本当の問題です。

注4:私は質問について知っています 同じ依存アセンブリの異なるバージョン間の競合について しかし、そこの答えは問題の根本を解決しません-彼らはただ隠しますそれ。

NOTE5:「DLL Hell」問題解決の約束は一体どこにあるのでしょうか?それは別の位置から再び現れています。

注6:GACの使用が何らかの形でオプションであると思われる場合は、ステップバイステップガイドを作成するか、リンクをお知らせください。

46
v.karbovnichy

Unityパッケージは、 Composition Root という1つの場所でのみ使用する必要があるため、良い例ではありません。また、Composition Rootは、アプリケーションのエントリポイントにできるだけ近い場所にある必要があります。あなたの例ではCompanyName.SomeSolution.Applicationです

それとは別に、私が現在働いているところでは、まったく同じ問題が現れます。そして、私が見るところ、この問題はしばしばロギングのような横断的な懸念によってもたらされます。適用できる解決策は、サードパーティの依存関係をファーストパーティの依存関係に変換することです。それを行うには、その概念の抽象化を導入します。実際、これを行うと、次のような他の利点があります。

  • もっとmaintainableコード
  • より良いテスト容易性
  • 不要な依存関係を取り除きます(CompanyName.SDKのすべてのクライアントはUnity依存関係を本当に必要としますか?)

それでは、架空の.NET Loggingライブラリの例を見てみましょう。

CompanyName.SDK.dll.NET Logging 3.0に依存します
CompanyName.SomeSolution.Project1.NET Logging 2.0に依存します
CompanyName.SomeSolution.Project2.NET Logging 1.0に依存します

.NET Loggingのバージョン間に重大な変更があります。

ILoggerインターフェイスを導入することにより、独自のファーストパーティの依存関係を作成できます。

public interface ILogger
{
    void LogWarning();
    void LogError();
    void LogInfo();
} 

CompanyName.SomeSolution.Project1およびCompanyName.SomeSolution.Project2は、ILoggerインターフェイスを使用する必要があります。これらは、ILoggerインターフェイスのファーストパーティの依存関係に依存しています。これで、その.NET Loggingライブラリを1か所で保持し、1か所で行う必要があるため、更新を簡単に実行できます。 .NET Loggingライブラリーの1つのバージョンが使用されるため、バージョン間の変更を壊すことも問題ではなくなりました。

ILoggerインターフェイスの実際の実装は、異なるアセンブリに配置する必要があり、.NET Loggingライブラリを参照する場所にのみ配置する必要があります。アプリケーションを構成するCompanyName.SomeSolution.Applicationで、ILogger抽象化を具体的な実装にマップする必要があります。

このアプローチを使用しており、抽象化と実装を配布するためにNuGetも使用しています。残念ながら、バージョンの問題は独自のパッケージで発生する可能性があります。この問題を回避するには、会社のNuGet経由で展開するパッケージに Semantic Versioning を適用します。 NuGetを介して配布されるコードベースで何かが変更された場合、NuGetを介して配布されるすべてのパッケージを変更する必要があります。たとえば、ローカルのNuGetサーバーには次のようなものがあります。

  • DomainModel
  • Services.Implementation.SomeFancyMessagingLibraryDomainModelおよびSomeFancyMessagingLibraryを参照)
  • もっと...

このパッケージ間のバージョンは同期されます。バージョンがDomainModelで変更された場合、同じバージョンがServices.Implementation.SomeFancyMessagingLibraryで変更されます。アプリケーションが内部パッケージの更新を必要とする場合、すべての依存関係は同じバージョンに更新されます。

14
Arkadiusz K

この問題を解決するには、コンパイル後のアセンブリレベルで作業します...

オプション1

ILMerge を使用してアセンブリをマージしてみてください。

ilmerge/target:winexe /out:SelfContainedProgram.exe Program.exe ClassLibrary1.dll ClassLibrary2.dll

結果は、プロジェクトとその必要な依存関係の合計であるアセンブリになります。これには、モノラルのサポートを犠牲にし、アセンブリID(名前、バージョン、カルチャなど)を失うなど、いくつかの欠点があります。したがって、マージするすべてのアセンブリを自分で構築する場合に最適です。

だからここに来る...

オプション2

代わりに この記事 で説明されているように、プロジェクト内にリソースとして依存関係を埋め込むことができます。関連部分は次のとおりです(重要なのは私のものです)。

実行時に、CLRは依存するDLLアセンブリを見つけることができません。これは問題です。これを修正するには、アプリケーションの初期化時にregister AppDomainのResolveAssemblyイベントを持つコールバックメソッドコードは次のようになります。

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {

   String resourceName = "AssemblyLoadingAndReflection." +

      new AssemblyName(args.Name).Name + ".dll";

   using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) {

      Byte[] assemblyData = new Byte[stream.Length];

      stream.Read(assemblyData, 0, assemblyData.Length);

      return Assembly.Load(assemblyData);

   }

};

これで、スレッドが、依存するDLLファイル、AssemblyResolveイベントのタイプを参照するメソッドを初めて呼び出すが発生し、上記のコールバックコードは埋め込みのDLLリソースが必要です。)を見つけ、引数としてByte []を受け取るAssemblyのLoadメソッドのオーバーロードを呼び出してロードします。

これは私があなたの靴を履いていた場合に使用するオプションであり、初期起動時間をいくらか犠牲にしていると思います。

更新

ご覧ください こちら 。これらの<probing>各プロジェクトのapp.config内のタグは、CLRがアセンブリを検索するときに検索するカスタムサブフォルダーを定義します。

12
beppe9000