前提条件
本稿はWPFアプリケーションのアーキテクチャ設計について記載したものです。見積編を前提に記載しているので、まだ未読であればそちらからお読みください。
本稿にはサーバーサイドの設計も一部含まれていますが、見積編にも記載した通り、サーバーサイドについてはWPFアプリケーションを設計する上で、必要最低限の範囲に限定しています。サーバーサイドの実現方式は、オンプレ環境なのかクラウド環境なのか? といった容易さなどで大きく変わってきます。そしてWPFアプリケーションから見た場合には本質的な問題ではありません。サーバーサイドまで厳密に記載すると話が発散し過ぎてしまい、WPFアプリケーションのアーキテクチャにフォーカスすることが難しくなるため、あくまで参考程度にご覧ください。
また本稿ではAdventureWorks社の業務のうち、発注業務システムのアーキテクチャとなります。特定業務は発注業務に限定されますが、認証などの複数の業務にまたがったアーキテクチャの実現についても言及します。
本稿は以下の環境を前提に記載しています。
- Visual Studio 2022 Version 17.4.0
- Docker Desktop 4.14.0
- Docker version 20.10.20
- SQL Server 2022-latest(on Docker)
- ComponentOne for WPF
- SPREAD for WPF 4.0J
- Test Assistant Pro 1.123
- .NET 6.0.11
本稿のサンプルは .NET 6で構築しますが、.NET Framework 4.6.2以上(.NET Standard 2.0水準以上)であれば同様のアーキテクチャで実現可能です。ただし一部利用しているパッケージのバージョンを当てなおす必要があるかもしれません。
想定読者
次の技術要素の基本をある程度理解していることを想定しています。
- C#
- WPF
- Docker
- SQL Server
これらの基本的な解説は、本稿では割愛しますが、知らないと理解できないわけでもありません。また下記の2つも概要が理解できていることが好ましいです。
- Clean Architecture
- ドメイン駆動設計(DDD)
Clean Architectureについては、筆者のブログである「世界一わかりやすいClean Architecture」をあわせて読んでいただけると、本稿のアーキテクチャの設計意図が伝わりやすいかと思います。
ドメイン駆動設計の適用範囲については、本文内でも解説いたします。
アーキテクチャ設計の構成
過去に記載した「実践WPF業務アプリケーションのアーキテクチャ【概要編】 ~ マイクロソフト公式サンプルデータベースAdventureWorksを題材に」では、アーキテクチャをRational Unified Process(RUP)にて提唱された4+1ビューを用いて記載・説明しました。
ただ4+1ビューでは次のような点で設計が導きにくいと感じてきました。
- ドメイン駆動設計との統合
- 非機能要件
- データの永続化手段と利用方法
- バージョン管理やCI/CD
これらを踏まえると、次のような視点(ビュー)でアーキテクチャを表現するのが良いと考えています。
No. | ビュー | 説明 | おもなモデル |
---|---|---|---|
1 | ドメインビュー | 境界付けられたコンテキストとコンテキストマップを用いてドメインとコンテキストを設計する。 | 境界付けられたコンテキスト、コンテキストマップ |
2 | ユースケースビュー | アーキテクチャを決定するための代表的なユースケースを選択・設計する。 | クラス図、シーケンス図 |
3 | 非機能要件ビュー | アーキテクチャに影響を与える非機能要件を特定・設計する。 | クラス図、シーケンス図 |
4 | 論理ビュー | ソフトウェアの論理レイヤー構成を設計する。レイヤー内の代表的なオブジェクトを抽出し、依存関係を設計する。 | パッケージ図、クラス図 |
5 | 実装ビュー | 論理ビューで抽出された代表的なオブジェクトをコンポーネントに分割配置する。 | コンポーネント図 |
6 | 配置ビュー | システム全体の論理ノード構成と、ノード上へのコンポーネントの配置を設計する。 | 配置図 |
7 | データビュー | システムが扱うデータの永続化方法、利用方法を設計する。 | ER図、クラス図、シーケンス図 |
8 | プロセスビュー | 並行性やパフォーマンス要件で特別な検討が必要と考えられるアーキテクチャを設計する。 | アクティビティ図、シーケンス図 |
9 | 開発者ビュー | システムの開発プロセスやツールを設計する。バージョン管理、CI、自動テストなどを含む。 | 配置図、シーケンス図 |
ドメインビュー
アーキテクチャ設計書のドメインビューでは、境界付けられたコンテキストとコンテキストマップを用いてドメインとコンテキストを設計します。
予算編で仮の境界付けられたコンテキストを作成しましたが、まずは対応するコンテキストマップを作成します。コンテキストマップではコンテキスト間の関係の種類を明確にします。関係の種類には共有カーネルやカスタマー・サプライヤー、腐敗防止層などが含まれます。
関係の種類を考慮して、論理ビュー・実装ビュー・配置ビューを設計します。
ユースケースビュー
4+1ビューが提唱されたRUPにおけるユースケースビューには、すべてのユースケースやアクターの抽出を含む、ユースケースモデルの完成ととれる意図が含まれていると思います。
ただそこまでいくと実質的な要件定義となってしまいます。それがダメという訳ではないのですが、アーキテクチャ設計の一部として記載することに個人的には違和感があります。
これは要件定義のリーダーと、アーキテクチャ設計のリーダーが異なることも多く、プロセスとしても要件定義とアーキテクチャ設計は部分的にオーバーラップして実施されることも多いからです。
要件は要件で文書化し、その要件を満たすアーキテクチャを設計するという流れが個人的にはしっくりきます。まず利用者に提供すべき価値(機能・ユースケース)があり、その手段としてアーキテクチャがあるはずです。そのためドキュメント体系としては、アーキテクチャ設計書の前にユースケース定義書(要件定義書)があるべきだと考えています。
ただアーキテクチャ設計書のユースケースビューには、ユースケースの実現を設計する役割もあります。
そこでユースケース定義書(要件定義書)によって設計されたユースケースを実現する上で、アーキテクチャ的なパターンを絞り込み、パターン別の実現方法を設計する役割をユースケースビューに残すこととしました。
非機能要件ビュー
従来の4+1ビューでは、機能要件に対する実現は明確に記載されていましたが、非機能要件の実現が考慮しきれていませんでした。そこで、機能要件に対するユースケースビューと同様に、非機能要件に対する非機能要件ビューを追加しました。
非機能全体の定義は、ユースケースビューと同様に、非機能要件定義書のような文書で定義されている前提条件とします。非機能要件には、たとえば保守・運用に関する要件なども含まれます。そのため非機能要件ビューでは、定義ずみの非機能要件からアーキテクチャ上考慮が必要な要件を特定し、その実現方法を設計します。
論理ビュー
システムを実現するための代表的なオブジェクトを抽出し、それらを配置する論理的なレイヤー構成を決定します。
他のビューでも同様ですが、論理ビューに記載する内容は、論理ビュー内の設計ですべて完成させることはできません。とくにユースケースや非機能要件の実現を設計する中で新しいオブジェクトが登場してきます。そのため複数のビューを行ったり来たりしながら設計を進めていくことになります。
実装ビュー
論理ビューで抽出されたオブジェクトを、どのようにコンポーネントに分割配置するか設計します。つまりVisual Studio上のプロジェクト(.csproj)をどのように分割して、どのクラスをどのプロジェクトに配置するのか決定します。
.NETの場合、プロジェクトの分割によって厳密な依存関係を規定できるため非常に重要です。安易にプロジェクトを統合してしまうと、すぐに好ましくない依存関係を実装してしまいがちだからです。
ViewとViewModelで考えると非常に分かりやすいでしょう。
MVVMパターンで設計する場合、ViewはViewModelに依存しますが、その逆をしてしまうと循環参照になってしまいます。しかし細かな実装をしていると、ついついViewModelからViewを操作してしまいたくなります。それで正しく動作することもありますが、WPFの場合はListなどが仮想化されている関係上、Viewを直接操作してしまうと不具合のもとになることもあります。また単純に依存関係が双方向になると、コードが追いきれないきれいなスパゲッティなコードになって、後日のメンテナンスで苦労しがちです。
そのため依存関係を適切に制御するため、プロジェクトをどう分割するかは、非常に重要な設計になります。
配置ビュー
どのノードに、どのコンポーネントを配置するか設計します。このとき実装ビューで抽出したコンポーネントだけでなく、サードパーティのライブラリやランタイム・OS・ミドルウェアなども記載します。ノードは論理的なノードとして扱い、物理的なノード設計はアーキテクチャ設計とは別に設計します。物理的な設計はインフラの詳細な設計にフォーカスするためです。
データビュー
システムが扱うデータの永続化方法、利用方法を設計します。
- 永続化先はファイルシステムなのか? RDBなのか? NoSQLなのか?
- RDBだとしたらスキーマをどのように設計するのか? 接続時のユーザーはどのように割り当てるのか?
- RDBをどのように利用するのか? Entity Frameworkか? Dapperか?
そういった内容を設計します。データベース全体のER図のような、詳細な設計は含めず、別途データベースの詳細設計書などに記載します。
プロセスビュー
並行性やパフォーマンス要件で特別な検討が必要と考えられるアーキテクチャを設計します。ほとんどの場合は、.NET(async/awaitなど)やASP.NET Coreなどが担ってくれるため、それらを単純に使うだけなら特別な設計は不要です。
今回のケースではWPFのプロセスを起動する際のDependency Injection(DI)コンテナーの初期化に関連する設計が必要になります。
開発者ビュー
システムの開発プロセスやツールを設計します。
- IDEには何を使うのか?
- ユーザーの開発環境に必要なランタイムやミドルウェアはなにか?
- バージョン管理には何を使うか?
- Gitを使うとした場合、そのブランチ戦略は?
- Unit Testフレームワークには何を使うか?
- CIはどのように行うか?
- CI時の自動テストは?
そういった日々の開発者体験に直結するプロセスやツールを決定します。ある意味では開発者にとって一番大切な部分でもあります。