当方がデータベースアプリケーションをWindows Formsで作成するときのアーキテクチャーを紹介します。
レイヤーアーキテクチャー
全体としてはModelとViewを分離するDocument-Viewを使用しています。Viewの役割を表示とキーボード・マウスなどのUIイベント処理に限定すると見通しが良くなります。それに伴い画面遷移などをModel側で処理するようになるとModelの役割が多くなり見通しが悪くなってきましたので、Model内の構造を見直す必要がでてきました。
そこでレイヤーアーキテクチャーを採用してModel内を分割することにしました。アプリケーション層・ドメイン層・データアクセス層に分割しています。アプリケーション層とドメイン層の間にサービス層を追加していたときもありましたが、ユーザの確認を差し挟む処理が書きにくいのでサービス層を無くしました。
コンポーネント | 要約 | コマンド実行 | データバインディング | その他 | Reflectionでの分類 | |
---|---|---|---|---|---|---|
View | データ表示、UIイベントの処理 |
|
アプリケーション層が提供するデータソースを入力・表示部品にバインディング。 |
|
メタレベル | |
Model | アプリケーション層 |
Viewの状態管理、コマンドの実行、ドメインの集約 |
|
データバインディングに必要なエンティティを集約し、データソース取得メソッドを通じて公開。 |
|
- |
ドメイン層 |
エンティティおよびビジネスロジックの提供 |
|
|
|
ベースレベル | |
データアクセス層 |
データベースとの入出力 |
|
|
メタレベル |
レイヤー間の連係
各レイヤー間の連係は下図のようにしています。
データバインディングではViewからドメイン層のエンティティに直接バインディングしています。
コマンド実行では各層で必要があれば下層を呼び出しています。
各層は、Reflectionパターンのベースレベルまたはメタレベルで記述します。ドメイン層ではベースレベルで記述、すなわちエンティティのプロパティをそのまま使用します。Viewやデータアクセス層はメタレベルで記述、Viewでは入力部品やDataGridViewColumnがプロパティを表します。データアクセス層ではカラムオブジェクトがプロパティを表します。
データのロードはリポジトリを通じて行います。データベースの更新はUnit of Work によって行います。XMLやテキストファイルへの保存はリポジトリを通じて行います。
リポジトリにはクエリーを記述しメソッドによって呼び出せるようにします。伝票-伝票明細のような構造を持つ場合、関係表構造(IDataReader)からオブジェクトグラフへの変換(R/O変換)は汎用ライブラリを利用してリポジトリ内で行っています。
画面の継承関係
画面はメニュー・一覧・編集といった風にある程度パターン化しています。それぞれパターンには共通の処理がありますのでこれらをベースクラスに記述し、具象画面は派生クラスに記述します。Viewではもっぱらデザインでの継承階層、アプリケーション層ではメニュー・一覧・編集といった機能による継承階層を持たせています。
これによって各具象画面のコードは少なくなります。単純なCRUDだけですとView側に自分で記述するコードはありません。アプリケーション層もほとんど記述はありません。
画面遷移
画面遷移はModel側のアプリケーション層で管理します。ViewはModelに追従して画面遷移を行います。
下図でCallerModelは遷移元Model、CalllerFormは遷移元View、BaseModelはModelの共通の基本クラス(ここではstaticメソッドを表現)、CalleeModelは遷移先Model、CalleeFormは遷移先Viewです。
遷移元Modelが遷移先Modelの型とオプションを指定してstaticメソッドOpenModelを呼び、それに従ってCalleeModelを作成・初期化します。既に作成済みであれば再利用します。作成済みModelはグローバルなDictionaryによって管理しています。コンストラクタと初期化メソッドを分けているのはメソッドのオーバーライドによって初期化処理を柔軟に実装したいからです。
初期化が終わると、遷移先ModelのCallerプロパティに遷移元Modelを設定し、遷移先Modelでは遷移元ModelのAddCalleeを呼び出します。これを受けて遷移元Modelでは対応する遷移元Viewに対して、CallerAddedによって遷移先Modelが追加されたことを通知します。遷移元Viewは追加された遷移先Modelに対応する遷移先Viewを生成します。作成するViewは一定の規則でModel名からView名を導き出してリフレクションによって作成します。以上で遷移先ModelとViewの作成が完了し、次に遷移先ModelのRunメソッドを実行してModelとしての最初の動作を実行します。通常は対応するViewに対してShowを通知し、表示を促します。
このシーケンスですと画面間のデータの受け渡しは、Model同士で直接行いますのでViewを通じて受け渡すよりもシンプルになります。