-
Notifications
You must be signed in to change notification settings - Fork 0
MS_DI
DI: Dependency Injection(依存性の注入)
- Dependency Injection の Dependency は依存性と訳すと誤解を生む。
- 正確には「使われるオブジェクト」(依存オブジェクト)という意味。
補足(この指摘は重要): 原文が最初に注意している通り、
「依存性の注入」という訳語が理解を妨げている。Dependency Injection =「依存しているもの(=相手のオブジェクト)」を「注入する」 ×「依存性という何かを注入する」 ○「依存先のオブジェクトを、外から渡してもらう」つまり、やっていることは
**「newを自分で書かず、引数で受け取る」**という、
それだけのことである。// DI でない:自分で相手を作る=相手に縛られる public class OrderService { private readonly SqlOrderRepository _repo = new SqlOrderRepository(); } // DI:相手を外から渡してもらう=差し替えられる public class OrderService(IOrderRepository repo) { }
- あるクラスに、固定の定数、変数、インスタンスが入っている状態
- つまりそのクラスは、その定数、変数、インスタンスに依存している。
- そのクラスの外から定数、変数、インスタンスを設定する。
- これにより、動的に動作を変えられるようにする。
移行メモ(表現): 「依存性を無くすために」とあるが、
依存そのものが無くなるわけではない(相手は必ず要る)。
正確には 「具象への依存を、抽象への依存に置き換える」
(依存性反転原則)である。
原文の表現を保ちつつ、この点を注記しておく。
※「依存性がコアの懸念で、AOP のアスペクトとはコアの懸念ではない。」
と言う意見もある。
「依存性反転原則」の実現
依存性反転原則を実現する技術の一つ。
-
モックの差し替え
モックの差し替えなどを実現し、テストを容易にする。 -
フレームワークの差し替え
フレームワークの差し替えを実現し、特定のフレームワークへの依存性を減らす。
モック差し替え。
Session、認証、ASP.NET Identity の config 等を Program.cs とか、Setup.cs に書かせるアレ。
補足(「書かせるアレ」の正体): 原文が指しているのは、
ASP.NET Core の起動時に書く次のようなコードである。// Program.cs builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(); builder.Services.AddAuthentication(...).AddCookie(...); builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(cs)); builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();ここが **合成ルート(Composition Root)**と呼ばれる場所で、
「何を何に差し替えるか」をアプリ全体で 1 箇所に集めるという設計になる。従来の
web.configによる設定と比べると、
web.configProgram.cs(DI)記述 XML(文字列) C#(型チェックが効く) 誤りの検出 実行時 コンパイル時 条件分岐 困難(変換ファイル) 普通に ifが書ける発見可能性 属性名を知る必要 インテリセンス という違いがある。
詳細は .NET Core における DI /
ASP.NET Core における DI を参照。
プログラムに依存性を注入する方法としては、以下のような手法が存在する。
注入用の interface を定義して注入を行う方法
setter メソッドを定義して注入を行う方法
constructor を定義して注入を行う方法
補足(どれを使うべきか): 結論から言うと、
constructor 注入を既定とするのが .NET の定石である。
方式 長所 短所 constructor 注入 必須の依存が明確。 readonlyにできる。未設定で使えない引数が増えると目立つ setter(プロパティ)注入 任意の依存に向く 未設定のまま使われ得る(NullReference) interface 注入 明示的 記述量が多い。現在ほぼ使われない // constructor 注入(C# 12 のプライマリ コンストラクタ) public sealed class OrderService( IOrderRepository repo, ILogger<OrderService> logger) { public void Place(Order o) { logger.LogInformation("..."); repo.Add(o); } }「引数が増えて見苦しい」のは、設計の警告灯である
(そのクラスが多くの責務を持ちすぎている、というサイン)。
隠すために setter 注入やサービス ロケータに逃げるのではなく、
クラスを分割するのが正しい対処になる。なお、ASP.NET Core の標準 DI が既定で対応しているのは
constructor 注入のみである
(プロパティ注入は Autofac 等のサードパーティ コンテナーが必要)。
-
依存関係を作成する専用のクラス。
- 要求された依存関係をインスタンスの作成で行う。
- 依存関係はハードコーディングではなく、構成により宣言。
- 依存関係のある型のインスタンスを提供するファクトリ。
- 依存関係だけでなく、ライフサイクルも管理する。
- 要求された依存関係をインスタンスの作成で行う。
-
以下のように呼ばれる。
- 制御の反転 (IoC) コンテナー
- 依存関係の挿入 (DI) コンテナー
補足(ライフサイクル=最も事故が多い所): 原文が挙げる
「ライフサイクルも管理する」は、実務で最も事故が起きる論点である。.NET 標準 DI の 3 つのライフタイム。
登録 生存期間 用途 AddSingletonアプリ全体で 1 つ 設定、キャッシュ、 HttpClientファクトリAddScoped1 リクエストにつき 1 つ DbContext、リポジトリ、UoWAddTransient要求のたびに新規 軽量・状態を持たないもの 典型的な事故:Captive Dependency(囚われの依存)
builder.Services.AddSingleton<CacheService>(); // ← Singleton が builder.Services.AddScoped<AppDbContext>(); // ← Scoped を持つと… // CacheService(AppDbContext db) ← db が最初の 1 つに固定され、 // ・スレッド安全でない DbContext が並行アクセスされる // ・破棄済みの DbContext を使い続ける(ObjectDisposedException)長生きな側が、短命な側を保持してはいけない。
Singleton → Singleton ○ Singleton → Scoped × ← Captive Dependency Scoped → Transient ○ Transient → Scoped △(呼ばれ方次第)検出方法: ASP.NET Core は開発環境で
スコープの検証を既定で有効にしており、
Singleton から Scoped を解決しようとすると起動時に例外になる。
本番でも有効にしておくと安全である。builder.Host.UseDefaultServiceProvider(o => { o.ValidateScopes = true; o.ValidateOnBuild = true; // 起動時に全登録を検証する });Singleton から Scoped を使いたい場合は、
IServiceScopeFactoryでスコープを明示的に作る。using var scope = _scopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
「IoC」や「AOP」、「依存性反転原則」自体にも言えるが、
同時に全体として協調動作させるときに複雑さが増す。
補足(過剰な DI への警戒): この指摘は現在も有効である。
よくある行き過ぎとして、
- 実装が 1 つしかないのに、すべてインターフェイスを切る
(IOrderService↔OrderServiceが 1 対 1 で、差し替える予定も無い)IFooのためだけにIFooFactoryを作る- コンテナーの機能(デコレーター、条件付き登録)に依存しすぎて、
起動時のコードを読んでも実行時の構成が分からない判断の目安は「差し替える具体的な理由があるか」である。
理由 抽象化する価値 テストで差し替えたい(DB、外部 API、時刻) 高い 実装が複数ある(SQL Server / PostgreSQL) 高い 「将来変わるかもしれない」だけ 低い 近年は、時刻すら
TimeProvider(.NET 8 で標準追加)で
注入できるようになった。テスト容易性のための抽象化は、
標準にあるものを使うのが最も安全である。
-
共通 I/F が無い場合の DI はユーザのレイヤでしか利用できない。
(ユーザが DI したものなので、ユーザが型情報を知っている) -
下位レイヤで利用するには、≒ 共通 I/F が必要。
(ユーザが DI したものなので、下位レイヤは型情報を知らない)
移行メモ(表記): 原文の「共通I/Fが無いDI場合のDI」は
重複の入力誤りと判断し、**「共通 I/F が無い場合の DI」**に修正した。
補足(この論点の意味): フレームワーク開発の観点からの指摘で、
要するに 「抽象(インターフェイス)は、下位レイヤ側に置く必要がある」
という話である。【インターフェイスを上位に置く】 【下位に置く(正しい)】 上位(業務) 上位(業務) IFoo を定義 IFoo を使う ↑ 参照できない ↑ 下位(基盤) 下位(基盤) IFoo を知らない IFoo を定義し、実装も提供 → DI しても使えない → 上位からも下位からも使えるこれはまさに **依存性反転原則**の
実践であり、Clean Architecture / Onion Architecture で
「インターフェイスは利用する側(内側)に置く」と言われるのと同じ論点である。
フレームワーク側が共通の抽象(契約)を定義して公開する
必要がある、というのが原文の主張と読める。.NET では、この「共通 I/F」にあたるものが
Microsoft.Extensions.*の抽象パッケージ
(ILogger、IConfiguration、IDistributedCache、IServiceProvider)
として標準化されている。
-
Spring .NET によりサポートされていた模様。
補足(現在の .NET における DI/最新化): 原文の記述は
ASP.NET Core 登場時点のもので、その後さらに一般化している。
時期 状況 .NET Framework 時代 標準の DI が無い。Unity / Autofac / Spring .NET / Ninject 等 ASP.NET Core 1.0 Microsoft.Extensions.DependencyInjectionが標準搭載.NET Core 3.0 汎用ホストにより、Web 以外(コンソール、Worker)でも標準化 現在 IHostBuilderを使うアプリはすべて DI が前提つまり **DI は「ASP.NET Core の機能」ではなく「.NET の機能」**になった。
// コンソール アプリでも同じ var host = Host.CreateApplicationBuilder(args); host.Services.AddSingleton<IMyService, MyService>(); host.Services.AddHostedService<Worker>(); await host.Build().RunAsync();標準コンテナーは意図的に機能が絞られている
(constructor 注入のみ、プロパティ注入なし、
インターセプトなし、名前付き登録は限定的)。
より高度な機能が要る場合は Autofac 等に差し替えられるが、
まず標準で足りるかを検討するのが現在の順序である。なお、
Microsoft.Extensions.DependencyInjectionは
.NET Framework 向けのnetstandard2.0でも使えるため、
既存の .NET Framework アプリに先行導入して
.NET Coreへの移行 の足がかりにする、
という使い方もできる。
- DI(依存性注入)について
https://www.slideshare.net/yuiito94/di-56742600 - 依存性の注入 - Wikipedia
https://ja.wikipedia.org/wiki/%E4%BE%9D%E5%AD%98%E6%80%A7%E3%81%AE%E6%B3%A8%E5%85%A5
-
猿でも分かる! Dependency Injection: 依存性の注入
https://qiita.com/hshimo/items/1136087e1c6e5c5b0d9f -
「なぜDI(依存性注入)が必要なのか?」
についてGoogleが解説しているページを翻訳した
https://qiita.com/mizunowanko/items/53eed059fc044c5aa5dc -
DI(Dependency Injection) の解説
https://qiita.com/okadabasso/items/066efc2e728a666b8732 -
Dependency Injection: 依存性の注入 のお役立ち例
https://qiita.com/saeki4n/items/22a276dcac9ef537ee25
- .NET での依存関係の挿入
https://learn.microsoft.com/ja-jp/dotnet/core/extensions/dependency-injection - 依存関係の挿入のガイドライン
https://learn.microsoft.com/ja-jp/dotnet/core/extensions/dependency-injection-guidelines
Tags: 移行, プログラミング, .NET開発
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。