背景と全体の見通し
オリジナルのWebサービスを作ってみたいと思って取り掛かってみたのですが、UI層とドメイン層のデータ連携方法についてモヤモヤした部分がありました。例えば下記です。
- 値の詰め替えが無駄に多い気がする
- エンティティの仕様がFormオブジェクトなどに散らばっている気がする
- hidden属性の考慮が面倒くさい
そこで自分なりに納得できる考え方を整理してみた内容が当記事です。結果として参考文献#2#3で学んだDPO(DomainPayloadObject)の考え方を活用したフレームワークとして整理することができましたので共有させて頂きます。
前提条件
- フレームワークはSpring
- 参考文献#1の前提環境を踏襲
- Spring: 5系
- Spring Boot: 2.4.1
当初の考え方
大まかには下記のようなフローを想定していました。ただし、
- ThymeleafのようなテンプレートエンジンはUI層に含めています。
- 例としてエンティティの更新を行う場面を想定しています。
- ユーザーはUI経由でエンティティの更新開始リクエストを送る(更新対象のidなどと共に)(①)。
- Contorollerは更新対象のエンティティを取得するためにApplicationServiceに依頼を送る(②)。
- ApplicationServiceはRepositoryからエンティティを取り出し(③、④)、Controllerへ送り返す(⑤)。
- ContorollerはUIへの送信用にFormを作成する(⑥)。
- エンティティからFormへ値の詰め替えを行う(⑦)。
- 設定後のFormをUIへ送る(⑧)。このとき更新画面のHTMLの指定も行う。
- ユーザは更新が必要な項目の入力を行う。また、idのような更新は行わないが送信が必要な項目はhidden属性として送付する必要があるので、考慮の上でHTMLの実装に取り込まれている必要がある(⑨)。
- 更新実行リクエストを送る(⑩)。
モヤモヤした事柄
「目的はエンティティの操作だ」という視点で考えると、無駄や非効率に思われる事が色々気になってきました。
バックエンド側では、Formオブジェクトへの詰め替えや作成単位など考えることが多い
- SpringではFormオブジェクトに単項目チェックの仕様(
@NotNull
など)を書くが、Form(ここではDTO的な役割)の本体はエンティティなのだとすると@NotNull
等はエンティティクラスに書いた方が仕様が一ヶ所にまとまってシンプルではないか。 - Formの作成単位はどのように考えたら良いか。エンティティ単位?画面単位?その他の単位?。画面単位と考えると、似て非なる画面に合わせて似て非なるFormが複数出来そう。エンティティ単位で良いのであれば詰め替えが無駄ではないか。場合によるという考え方は方針自体が無いように感じられてシックリこない。
フロントエンド側では、hidden属性のinput項目など気にすることが多い
- 例えば上図の⑨でidのような更新は行わないが送信が必要な項目をhidden属性として送付する必要があるという部分は、場面に合わせてエンティティの項目の扱いを変えなくてはいけないという事であり、複雑さを作り出しているように思われる。
- エンティティがUIとコントローラ(その奥のドメイン層)の間を自由に行ったり来たりできればシンプルなのでは無いか。
目指す姿
目指すのは操作対象のエンティティをFormオブジェクトに登録するというルールのみ守れば、UI層とコントローラ(またはドメイン層)でエンティティを共有できるというシンプルな方針です。これが出来れば、煩雑な値の詰め替えやFormの作成単位、hidden項目の取り扱い等に頭を悩める必要が無いと考えました。また、エンティティの仕様がFormなどに分散せず(単項目チェック、など)、エンティティ定義の中で完結するようにもしたいです。当初の考え方との差は下図の通りです。
- ユーザーはUI経由でエンティティの更新開始リクエストを送る(更新対象のidなどと共に)(①)。
- Contorollerは更新対象のエンティティを取得するためにApplicationServiceに依頼を送る(②)。
- ApplicationServiceはRepositoryからエンティティを取り出し(③、④)、Controllerへ送り返す(⑤)。
- 操作対象のエンティティの参照を設定する用のForm(DPO1)を作成する(⑥)。
- Formにエンティティの参照を登録する(⑦)。項目レベルの詰め替えは不要。
- 設定後のFormをUIへ送る(⑧)。このとき更新画面のHTMLの指定も行う。
- ユーザは更新が必要な項目の入力を行う。エンティティ自体を共有しているのでhidden項目等の項目レベルの場面に応じた対応を気にする必要は無い(⑨)。
- 更新実行リクエストを送る(⑩)。
実装
あくまで試行レベルですがフレームワーク的に実装したものを以下に示します。使用方法や設計思想などをREADME.mdに記載しています。
README.mdではエンティティの登録先としてFormではなくDpoという用語を使用しています。formと言ったらHTMLのformタグのことになります。
適用例
作成中ですがDomainPayloadObjectTrialと合わせて作成しているWebサービスのリンクを下記に示します。いくらか初期データも用意してあるので、data.sqlを参照してみて下さい。現状としてはEclipse上でWorkFlowMapからDomainPayloadObjectTrialを参照する形で動作させています。
コンセプトや概要をREADME.mdに記載しましたが詳細な仕様書のようなものは無いので、何となく汲み取って頂けたらありがたいです。画面入力のパターンに合わせて、どのようにフレームワークとThymeleafを組み合わせているか分かると思います。
今後の展望
- 当初の目的であるWebサービスを充実させていく中で、フレームワークもブラッシュアップ(もしかしたら根本からの見直し)していきたいです。
- テストコードが無いのでテスト自動化の考え方を整理しつつ、コード整備も行いたいです。
ここまでの経験で「自由な発想で仕組みを考えていくのは楽しい」ということを再確認できました。今後も気ままなこだわりで見直しを行っていきたいと思います。
参考文献
- 後悔しないためのSpring Boot 入門書:Spring 解体新書(第2版)(田村達也 (著) )
- 実践ドメイン駆動設計(ヴォーン・ヴァーノン (著), 高木 正弘 (翻訳) )
- 「実践ドメイン駆動設計」から学ぶDDDの実装入門(WINGSプロジェクト 青木淳夫 (著), 山田 祥寛 (監修) )
-
DPO(Domain Payload Object)
UIに必要な複数の集約への参照を保持する入れ物(詳しくは参考文献#2#3参照)。GitHubに格納したプロジェクト名はDPOから取りました。 ↩