導入
実務のMVVMアプリは、1つの巨大なプロジェクトではなく、役割ごとに複数のプロジェクトへ分けて作ります。「どこに何を置くか」「依存はどちら向きに許すか」という判断は、テストのしやすさと将来の変更のしやすさに直結する、実務で最初に学ぶべき設計判断です。このレッスンでは、このコースが実際のVisual Studioでどう構成されるかを詳しく見ます。
説明
タスク管理アプリのソリューションは、次の4プロジェクトで構成します。
flowchart BT
Core["MyApp.Core(クラスライブラリ)<br/>Entity・ValueObject・Enum<br/>ITaskRepository・ITaskService(抽象)・DTO<br/>――外部技術に依存しない――"]
Infra["MyApp.Infrastructure(クラスライブラリ)<br/>InMemoryTaskRepository など<br/>Coreのインターフェースの具体的な実装"]
App["MyApp(WPF実行可能プロジェクト)<br/>View(.xaml)・ViewModel<br/>App起動処理(Composition Root)"]
Tests["MyApp.Tests(xUnitテストプロジェクト)<br/>Core・Infrastructureを検証する"]
Infra --> Core
App --> Core
App --> Infra
Tests --> Core
Tests --> Infra
矢印は「参照する(=知っている)」方向です。Coreだけが誰からも矢印を受けるだけで、どこにも矢印を出していません。これが本レッスンで一番大事な約束事です。
- MyApp.Core:
TaskItem(Entity)・TaskTitle(ValueObject)・Priority(Enum)、そしてITaskRepository・ITaskServiceのようなインターフェース(抽象)、画面用のTaskDtoを置きます。NuGetの外部ライブラリや、DB・ファイル・UIフレームワークへの参照を一切持たない、最も純粋なクラスライブラリです。ここにあるコードは「タスク管理」という業務の本質であり、WPFをやめてWebアプリにしても、ほぼそのまま使い回せます。 - MyApp.Infrastructure: Coreのインターフェースに対する具体的な実装を置きます。今回は
InMemoryTaskRepository(メモリ上のListで保持する素直な実装)だけですが、将来「保存先をファイルやデータベースに変える」となったら、変更はこのプロジェクトの中だけで完結します。Coreを参照しますが、Core側はInfrastructureの存在を知りません(依存性逆転の実践)。 - MyApp: WPFの実行可能プロジェクトです。View(
.xaml)とViewModel、そしてアプリ起動時に「どの実装をどこに注入するか」を決めるComposition Root(第8章で詳しく扱います)を置きます。Core・Infrastructureの両方を参照します。 - MyApp.Tests: xUnitのテストプロジェクトです。Core・Infrastructureを参照してテストを書きます。本物の代わりに使うFake実装(第4章)もここに置くことが多いです。
「何をCoreに、何をInfrastructureに置くか」の判断基準はシンプルです。外部の技術(DB・ファイル・ネットワーク・UIフレームワークなど)に触れるかどうか。触れないものはCore、触れるものはInfrastructure(またはMyApp本体)です。WPFのViewは「UIというインフラ」の一種なので、実行可能プロジェクトであるMyApp本体に同居させます(実務ではさらにMyApp.Uiのように分けることもありますが、本コースはシンプルに保ちます)。
実際にVisual Studioで作るときは、次のようなコマンドになります。
dotnet new sln -o TaskManager
cd TaskManager
dotnet new classlib -o MyApp.Core
dotnet new classlib -o MyApp.Infrastructure
dotnet new wpf -o MyApp
dotnet new xunit -o MyApp.Tests
dotnet add MyApp.Infrastructure reference MyApp.Core
dotnet add MyApp reference MyApp.Core MyApp.Infrastructure
dotnet add MyApp.Tests reference MyApp.Core MyApp.Infrastructure
dotnet sln add MyApp.Core MyApp.Infrastructure MyApp MyApp.Tests
このサイトでの再現方法(重要)
このサイトのC#実行環境は、1レッスン=1ファイルの単発実行という制約があり、複数プロジェクトの参照関係をそのまま再現できません。そこで第2章以降、各レッスンの演習では上の4プロジェクトに対応する名前空間(namespace MyApp.Core { ... } のように)を1つのファイルの中に並べることで、同じ構造を疑似的に再現します。
flowchart LR
subgraph VS["実務のVisual Studio"]
direction TB
P1["MyApp.Core(プロジェクト)"]
P2["MyApp.Infrastructure(プロジェクト)"]
P3["MyApp.Tests(プロジェクト)"]
end
subgraph SITE["このサイトの1ファイル実行"]
direction TB
N1["namespace MyApp.Core { ... }"]
N2["namespace MyApp.Infrastructure { ... }"]
N3["namespace MyApp.Tests { ... }"]
end
VS -.同じ構造を1ファイルで疑似再現.-> SITE
実際にVisual Studioで手を動かすときは、名前空間の宣言を本物のプロジェクト参照に置き換えるだけで、このコースで書いたコードはほぼそのまま使えます。「Coreは何にも依存しない」「Infrastructureの実装はCoreのインターフェース越しにしか呼ばれない」という依存の向きの約束事は、名前空間で表現していても、プロジェクトを分けていても変わりません。
まとめ
- ソリューションは
MyApp(実行可能・View/ViewModel)/MyApp.Core(Entity・ValueObject・抽象)/MyApp.Infrastructure(具体的な実装)/MyApp.Tests(テスト)の4プロジェクトに分ける - 依存の向きは常にCoreへ集まる。Coreはどこにも依存しない
- このサイトでは複数プロジェクトの代わりに名前空間で同じ構造を疑似再現する。実務では名前空間をプロジェクト参照に置き換えるだけでよい
次回: MVVMのView側で最低限知っておきたい、WPFの基礎を駆け足で復習します。