Blazorアプリのディレクトリ構成案
前置き
私はBlazorアプリを完成させたことはなく、サンプルでマスタメンテ画面を作ったレベルですが、 こういう感じでプロジェクトのディレクトリを構成したらいい感じになるんじゃないかという妄想。
あくまで1例としてお考えください。
ディレクトリ構成
Root ├─・・・ ├─Data ├─Models ├─Pages ├─Shared ├─・・・
SharedフォルダについてはBlazorアプリのサンプルにあるとおりの使い方になりますので 特に言及はしません。
Data
- データアクセス層。データベース・ファイルなどのデータストレージとモデルの中間に存在し、橋渡しするクラス群。
- データベースから値を読み取ったり、書き込んだりする処理はすべてここに書く。
- コードの大半はORMに関すること。
- データストレージに書き戻す際、「追加なのか、更新なのか、削除なのか」の判断はこのクラスの責任範囲。
- CUD判断をするため、必要に応じて読み込んだ直後の値を保持する必要もある。
- 1画面あたりに1Dataクラスを作り、命名規則は「{画面名}Service.cs」とする。(確証なし)
Models
- データ層から読み取ったものを入れる箱。書き戻すのにも使う。
- プロパティのみ所持。メソッドは持たない。
- プロパティに属性をもたせることでValidate処理がやりすくなる。たとえば、RequiredAttributeで必須チェックを入れたり、MaxLengthで長さチェックを入れたりする。
Pages
- 画面、ページ全体のこと。(共通部品ではないことに注意)
- GUIを持ち、かつ、データクラスを介してデータストレージの内容を画面に表示する。
担当している領域が広すぎるで、以下のように分解する。 * ViewModels * Components
ViewModels
- PageとModelの橋渡し。
- Pageに表示する値とメソッドを管理。
- Modelを内包し、PageはViewModelを通してデータアクセス層にアクセスする。
- 画面制御系の情報(表示判別式、読み取り専用判別式)も管理。
- 端的にいえば、PageはGUIの処理に専念し、それ以外は全部ViewModelにやらせればいい。
PageとViewModelの境目がちょっと分かりづらいと思うので、具体例を挙げます。 例えば、「ある条件を満たしたときだけ、削除ボタンが利用できる」とします。この場合、
- 削除ボタンを利用できるかどうかの判断はViewModelが行う。
- それをどうGUIで表現するかはPageが行う。
ということになります。
更に具体的に言うなら、このようなイメージ。
- ViewModelに「bool CanDelete」というプロパティがあり、表示してよいかどうかを管理している。
- PageはViewModelの「CanDelete」プロパティを見て、Trueである場合のみ「削除ボタン」を表示する。
- PageはCanDeleteがどのように判断されているかは知らないし、ViewModelもCanDeleteがどのようにGUI反映されるかは知らない。(関心事の分離)
Components
- ViewModelを作ったことでPageの担当領域はGUIに特化しましたがそれでもまだ重い。
- 画面を作った場合、他の画面も「このデザインで見せたい」ということはよくある。(古い言葉で言えばユーザーコントロール)
- 再利用できるPageの部品をComponentと呼び、Pageを更に細分化する。
ここで、画面の部品化「Component」が出てきますが、そうは言っても何をComponet化すればいいのか、分かりづらいですよね。 製造を進めた結果、「部品化したほうがいい」と思うものもあるでしょうか、DBアプリケーションで言えば以下は最初から部品化候補で考えたほうがいいでしょう。
- 単票
- 検索グリッド
- 検索行
- 表形式グリッド
- 表形式行
- アイテム
Components - Report(単票)
- 「単票って部品より画面」っぽいですが、色んなところで利用されるケースがありえるのででかい単位ですが部品と考えます。
- よく利用されるのはダイアログ表。リストで表示したものの明細を表示するときに利用されます。
例えば「商品」モデルがあったとします。 この場合、「商品検索画面から、商品の明細を表示したい」、「受注検索画面から、商品の明細を表示したい」という需要は大いに考えられますので、単票自体の部品化を検討するのは悪くないです。
Components - IndexGrid(検索グリッド)、IndexRow(検索行)
- これは敢えて分ける必要があるかは微妙なところです。
- ヘッダー処理と行処理はそれほど依存関係がないので分離することは可能なので、分離したほうがコードが読みやすいなら分離したほうがいいです。
- ヘッダーと行の列数が同じでないとデザイン上崩れるので密接な部分はあるのですが、デザインが崩れるのは見たらすぐわかる話し同一クラスに書いたときとデバッグのしやすさが著しく劣るとも思えず。
Components - TabularGrid(表形式グリッド)、TabularRow(表形式行)
- 検索グリッド、検索行とほぼ同じ。読み取り専用か、編集可能かの違いがあるだけ。
- TabularGridは編集可能であるため、入力項目を全部列に表示しないと行けない。
- IndexGridは読み取り専用であるため、主要項目だけ載せれば良い。よって別Componentにしたほうがいい。
Components - Item(アイテム)
- 「検索ダイアログ付きID入力欄」といったほうがわかりやすい。
- マスタならこのComponentがほぼ必須になる。
Conditions
今まで説明にあげてませんでしたが、作ったほうがいいと思うので挙げときます。 * 検索画面なら検索条件入力欄が必ずあると思います。 * 検索条件はModelではないので、ViewModelで管理することになります。 * 検索条件は「前回の検索条件を復元する」という特性があり、検索条件をシリアル化/逆シリアル化することを考えると「Modelっぽい」側面もあります。っていうかModelです。 * といいつつもリレーションはないわ、「シリアル化/逆シリアル化」しかしないわ、データアクセスクラスは共通化できるわっていう特性を考えると、これ自体を「Condition」と定義してもいいんじゃないかと
まとめ
Root ├─Components │ └─{ViewModelName} │ ├─ {ViewModelName}IndexGrid.cs │ ├─ {ViewModelName}IndexRow.cs │ ├─ {ViewModelName}Item.cs │ ├─ {ViewModelName}Report.cs │ ├─ {ViewModelName}TabularGrid.cs │ └─ {ViewModelName}TabularRow.cs ├─Conditions │ └─{ViewModelName}Conditions.cs ├─Data │ └─{ViewModelName}Service.cs ├─Models │ └─{ModellName}.cs ├─Pages │ └─{ViewModelName} │ ├─ {ViewModelName}Edit.cs │ └─ {ViewModelName}Index.cs └─ViewModels └─{ViewModelName}.cs
めちゃくちゃ多いですよね?これだけ見ると理想と現実は違うねと感じなくもないです。 ただ、上記の分け方で一度コード書いてみてください。1クラス自体はとんでもなくシンプルコードになります。もう、誰が書いても「こうしか書けないだろ」ってレベル。
で、妄言垂れ流して何が言いたいかってことなんですが、上記のクラス、1モデルクラスがあったら多分ジェネレートできるんです。時間がないからジェネレートツール作らないだけで。
だれか時間とお金をください。