この記事は
「ドメイン駆動設計入門」を読んで内容をまとめているシリーズの第三弾
過去の記事 | 内容 |
---|---|
その1 | 値オブジェクト、エンティティ、ドメインサービス |
その2 | リポジトリ、アプリケーションサービス |
その3=この記事 | 依存関係のコントロール、ソフトウェアを組み立てる |
依存関係のコントロール
「依存性逆転の原則」の話
- 具象クラスを直接参照するのではなく、インターフェイスを参照するようにする
著書では言及されていなかった(読み取れなかった)が、インターフェイスを上位に置くことが重要。インターフェイスが下位モジュール置かれたままでは、依然として上位から下位へ依存の矢印が向いてしまう。
逆転を実現するには、インターフェイスを上位に置かねばならない
項目 | 役割 | 安定度 | 柔軟性 |
---|---|---|---|
上位=アプリケーションサービス | ユースケースを実現する | 低 | 高 |
下位=リポジトリ | データの永続化と再構築 | 高 | 低 |
安定度と柔軟性
安定度が高い=依存される側(infrastructure)。依存する側(usecase)の変更から、影響を受けない。
柔軟性が高い=依存する側(usecase)。依存される側(infrastructure)に、影響を与えず変更できる。
こちらの動画のコメントから引用
依存性逆転に関する参考リンク
ソフトウェアシステムを組み立てる
C# ASP.NETのサンプルコードが何をしているのか順を追ってみてみる
.NET Core 3.1 で新規プロジェクトを立ち上げて、差分をみる
依存関係を設定する
Startup
クラスのConfigureServices(IServiceCollection services)
メソッドでは、IoC Container
を利用して依存関係を登録する
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
+ var factory = new DependencySetupFactory();
+ var setup = factory.CreateSetup(Configuration);
+ setup.Run(services);
+
services.AddControllersWithViews();
+
+ // In production, the React files will be served from this directory
+ services.AddSpaStaticFiles(configuration =>
+ {
+ configuration.RootPath = "ClientApp/build";
+ });
}
引数のservices
に対して直接AddSingleton
などして依存関係の登録をしてもよいが、デバッグ用とプロダクション用で分けたい場合にいちいち書き直す必要がある
開発環境に応じて依存関係を柔軟に変更するために、DependencySetupFactoryを介して、configuration["Dependency:SetupName"]
に応じたセットアップをつかうようにする
構成ファイルは以下のようになっている。キーを:
で繋げて所望の値を取得する。
"Dependency": {
"SetupName": "InMemoryModuleDependencySetup"
},
コントローラーを実装する
構成が決まってservices
に必要なインスタンスが格納されたら、次はコントローラー側でサービスを使う
// 構成ファイルの SetupName にしたがって
// InMemoryModuleDependencySetup()がRun()すると
// services に UserApplicationService が登録される
services.AddTransient<UserApplicationService>();
// MVCフレームワークはIoC Containerと連携しているので
// コントローラーのコンストラクタには UserApplicationService のイスタンスが渡される
public class UserController : Controller
{
private readonly UserApplicationService userApplicationService;
public UserController(UserApplicationService userApplicationService) // <- コントローラーのコンストラクタ
{
this.userApplicationService = userApplicationService; // <- インスンス変数をに詰め替える
}
[HttpGet]
public UserIndexResponseModel Index()
{
var result = userApplicationService.GetAll();
// ---{後略}--
}
コントローラーの責務は、ユーザーからの入力をドメインモデルが扱える形式に変換すること。それ以上の仕事をコントローラーがしているとしたら、ドメインの知識やロジックが漏れ出ている可能性を危惧すべし。
備考:AddTransientやAddSingletonはスコープが違う
ユニットテスト
必ずしもユニットテストがソフトウェアの品質向上になるわけではないが
ユニットテストができるクラスに仕立てることは品質向上の第一歩である
(おまけ)Configureの差分
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
- app.UseExceptionHandler("/Home/Error");
+ app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
+
app.UseHttpsRedirection();
app.UseStaticFiles();
+ app.UseSpaStaticFiles();
app.UseRouting();
- app.UseAuthorization();
-
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
- pattern: "{controller=Home}/{action=Index}/{id?}");
+ pattern: "{controller}/{action=Index}/{id?}");
+ });
+
+ app.UseSpa(spa =>
+ {
+ spa.Options.SourcePath = "ClientApp";
+
+ if (env.IsDevelopment())
+ {
+ spa.UseReactDevelopmentServer(npmScript: "start");
+ }
});
}