3月第4週の振り返り

3月第4週(3/16-3/22)

ASP.NET Core MVC

MSDNのドキュメントを読んでいて学んだ、Dependency InjectionのAddScopedの正確な挙動と、DIを用いて疎結合にすべきとされていた意外なものについて書く。

AddTransientは供給要求のたびに新しいインスタンスが生成される。ステートレスで軽量なサービス(ここでは供給する実装クラスのことを指すと考えられる)に向くとされる。

一方でAddScopedはクライアントのリクエストごとに生成されると書いてあり、これだけではTransientとの区別がつけづらい(Transientであっても、リクエストごとに生成されるControllerインスタンスの中で、供給されたサービスを使い捨てず変数に格納して利用することが主であろう)。正確にはscopedの名の通りスコープの中で同一のインスタンスが供給される。ここでのスコープとはブレースに囲まれたブロックではなくusingステートメントにおいて

using(var scope = services.BuildServiceProvider().GetRequiredService<IServiceScopeFactory>().CreateScope()){
...
}

で生成されたscopeである。scope.ServiceProvider.GetService<IYourInterface>();で要求すると毎回同一のものが供給される。

ASP.NET Core MVCではリクエストの処理に際してAddScopedの対象となるサービスを自動的にこのscopeを通じ供給するようにしているため、外見上リクエストごとに生成されているとみなせるようである。

DbContextはAddDbContextにおいてScopedな供給のされ方をしているので、先週の記事で述べたリポジトリクラスもAddScopedで供給するのがよい*1


このドキュメントではDateTime.NowをControllerのメソッド中で直接使うことを戒めている。それはテストするタイミングによってそれが変動してしまい、常に同じ結果が得られるとは限らないからである。記事を予約投稿する(指定時刻まではアクセスしてもNot Foundにする)機能を持ったブログアプリケーションを考えたとき、当然指定時刻以前と以後で違うレスポンスが得られるかテストするだろう。

アクセスをController内部のDateTime.Nowで制御すると、テスト時刻の前に公開時刻をセットしたものは見られて逆は404になるよう期待するため、モック記事モデルの公開時刻をテストのたびに書き直すか、あるいはテストコード中でDateTime.Nowから足し引きすることになる。任意の時刻を設定できるモックを基準にするならまだよいが、Controllerの中で直接「日曜日だけ…」などとやっていたとしたら自動テストによる動作保証は絶望的である。

そこでこの記事では抽象化した時刻提供サービスを供給することが推奨されている。単体テスト時には都合の良い時刻を返すモック時刻提供サービスを供給することで常にリクエスト発生時刻を任意のタイミングに指定(仮定)できるというわけである。

これまでDependency Injectionを拡大する方向で進んできたが、それはリポジトリにおけるデータベースコンテキストのように事情が変われば交換可能な依存先について適用すべきもので、帰属関係のあるものにまで適用すべきでないということが言われている。大規模なアプリケーションの経験がないためどういったところで内部newのままにすべきなのかつかめていないが留意しておきたい。

*1:

ASP.NET - Writing Clean Code in ASP.NET Core with Dependency Injection | Microsoft Docs "Scoped is appropriate because my DinnerRepository depends on a DbContext, which also uses the Scoped lifetime."