WPF Clean Architecture + Modular Monolith + Aspire Design Guidelines
.NET 10 C# + WPF で MVVM Toolkit・クリーンアーキテクチャ・モジュラーモノリス・.NET Aspire を組み合わせて構築します。
参照ドキュメント
| ドキュメント | 内容 |
|---|---|
| shell-pattern.md | IModule・IShellService のインターフェース定義と全実装例 |
| aspire-integration.md | Aspire 統合の詳細設定・コード例 |
| project-settings.md | 各プロジェクトの .csproj 設定とプロジェクト参照の例 |
ソリューション構成
src/
AspireAppHost/ # Aspire オーケストレーター(バックエンドインフラ管理)
ServiceDefaults/ # OpenTelemetry・サービスディスカバリの共通設定
WpfApp/ # WPF シェル(最小限のグルーコードのみ)
Shared/
Shared.Kernel/ # ドメインプリミティブ(WPF 非依存)
Shared.UI/ # UI プリミティブ(IModule・IShellService・共通スタイル)
Modules/
{ModuleName}/
{ModuleName}.Domain/
{ModuleName}.Application/
{ModuleName}.Infrastructure/
{ModuleName}.Presentation/ # Views・ViewModels・IModule 実装
tests/
{ModuleName}.{Layer}.Tests/
モジュラーモノリスのルール
- モジュール間の直接参照は禁止。モジュール間通信は
Shared.Kernel経由(イベント・インターフェース・共通 DTO)のみ。 - UI もモジュールが所有する。Views・ViewModels・ナビゲーション登録はすべて
Presentation層が管理する。 WpfAppは薄いシェルのみ。IModuleの列挙・初期化とシェル UI(ウィンドウ枠)だけを持ち、ビジネス知識を持たない。- 各モジュールは DI 登録拡張メソッドを
Infrastructure層(AddXxxModule)とPresentation層(AddXxxPresentation)にそれぞれ実装する。
クリーンアーキテクチャ 各層の責務
| 層 | 依存先 | 主な内容 |
|---|---|---|
| Domain | Shared.Kernel のみ |
エンティティ・値オブジェクト・集約ルート・リポジトリ IF・ドメイン例外 |
| Application | Domain・Shared.Kernel | コマンド/クエリ・ハンドラー・DTO・バリデーション・ドメインイベントハンドラー |
| Infrastructure | Application・Domain・Shared.Kernel | リポジトリ実装・EF Core・外部クライアント・DI 登録 |
| Presentation | Application・Shared.Kernel・Shared.UI | Views (XAML)・ViewModels (MVVM Toolkit)・IModule 実装・DI 登録 |
MVVM Toolkit 使用規則
- ViewModel は
ObservableObjectを継承したpartial classで定義する。 - プロパティは
[ObservableProperty](フィールド名_camelCase)、コマンドは[RelayCommand]で生成する。 - プロパティ変更時の副作用は
partial void OnXxxChanged(T value)で対応する。 CanExecuteは[RelayCommand(CanExecute = nameof(CanXxx))]で指定する。- ViewModel と View は Presentation 層の
AddXxxPresentation()で DI 登録する。
public partial class OrderListViewModel(IOrderQueryService queryService) : ObservableObject
{
[ObservableProperty] private ObservableCollection<OrderDto> _orders = [];
[ObservableProperty] private bool _isLoading;
[RelayCommand]
private async Task LoadOrdersAsync(CancellationToken ct)
{
IsLoading = true;
try { Orders = new(await queryService.GetAllAsync(ct)); }
finally { IsLoading = false; }
}
}
Shared.Kernel の内容(WPF 非依存)
Entity<TId>・ValueObject・AggregateRoot<TId>・IDomainEvent・Result<T>・共通例外基底・IEventBus
Shared.UI の内容(WPF 依存)
IModule・IShellService・NavigationItem・共通 ResourceDictionary(テーマ・スタイル)・共通コンバーター基底
インターフェース定義と実装例 → shell-pattern.md
WpfApp(シェル)の責務
Generic Host (Host.CreateApplicationBuilder()) で起動し、AddServiceDefaults() を組み込む。
ShellService/MainWindowViewModel/MainWindowを DI 登録- 各モジュールの
AddXxxModule()+AddXxxPresentation()を呼び出す - ビルド後、全
IModuleを取得してmodule.Initialize(shell)を実行 MainWindowを表示する
完全なコード → shell-pattern.md
.NET Aspire 統合
AspireAppHostはバックエンドインフラ(DB・API・キュー等)をオーケストレートする。WPF アプリは登録しない(手動起動)。ServiceDefaultsをWpfAppから参照し、AddServiceDefaults()でテレメトリ・サービスディスカバリを有効化する。- バックエンド API への接続は
https+http://{サービス名}で行う(Aspire のサービスディスカバリ)。
詳細・コード例 → aspire-integration.md
依存方向のルール(厳守)
Presentation → Application → Domain
Presentation → Shared.UI
Infrastructure → Application → Domain
WpfApp → {ModuleName}.Presentation(IModule 列挙のため)
WpfApp → Shared.UI
Shared.Kernel ← すべての層から参照可(UI 依存なし)
Shared.UI ← Presentation・WpfApp のみ参照可
AspireAppHost → バックエンドサービスプロジェクトのみ
モジュール間の直接参照 → 禁止
プロジェクトファイルと参照の設定例 → project-settings.md
コード生成時のチェックリスト
- 新しいモジュール: Domain → Application → Infrastructure → Presentation の順に4プロジェクトを作成し、Presentation から
Shared.UIを参照する。 - ViewModel:
ObservableObject+partial class+[ObservableProperty]/[RelayCommand]。 - ナビゲーション画面の追加:
IModule.Initialize()にNavigationItemを登録し、View と ViewModel を DI 登録する。 - モジュール間通信:
Shared.KernelのIEventBusや共通 DTO を経由する。モジュール直接参照は禁止。 - 新しいユースケース: Application 層にコマンド/クエリ+ハンドラーを追加し、ドメインロジックは Domain 層に保持する。
- バックエンドサービス追加:
AspireAppHostに登録し、Infrastructure 層でhttps+http://{サービス名}でアクセスする。 - 新規プロジェクト作成:
ServiceDefaultsを参照し、ホストビルダーでAddServiceDefaults()を呼び出す。