ドメインモデルについて本を読み返しながら頭の中を整理していく#1
「現場で役立つシステム設計の原則」編
まず、ドメインオブジェクトとは関連する業務データと業務ロジックを1つにまとめたオブジェクトのことを指す。例)登録日と更新日から更新日が妥当かを判断するロジックなど
そして、ドメインオブジェクトをまとめて使用することにより、業務フローの実装を実現する。
このときに関連するドメインオブジェクトをまとめたものがドメインモデルである。
ドメインモデルを使用する必要性
- 業務データと業務ロジックをまとめることでアプリケーション全体の見通しが良くなる
- ドメイン層以外の層がシンプルになる(上と似た理由)
- 業務ロジックが重複して存在することを防ぐ
- 業務の関心ごととコードが対応しているので、どこに何が書いてあるかが分かり易い
- 業務ロジックなどに変更があった際に、影響範囲を小さくすることができる
実際に業務で使われている言葉、データ、ロジックを使用するので、業務への深い理解が必要。
ドメインオブジェクトはトップダウンではなくボトムアップで作成していく。つまり、部分部分でどのデータが必要で、どのようなロジックが必要かを考えドメインオブジェクトを作成していく。そして、その小さな部品を集めることによって、やりたいことを実現する。
また、ドメインオブジェクトを小さく保つことで、修正や手戻りが発生した場合にも影響範囲を小さく保つことができる。
このときに気をつけたいことは、機能を分割してそれをドメインオブジェクトに落とし込むようなトップダウンの実装をしないこと。このような実装をしてしまうと、ドメインオブジェクトと他のドメインオブジェクトの依存が発生しやすくなってしまう。
ドメインオブジェクトを先に作成するので、ドメインモデルを使用し業務アプリケーションを設計していく中で足りないものが出てくる。それは問題ではなく後から付け足せば良い。
ドメインオブジェクトを作成するときに注目すべきは、ヒト/モノ/コトの中の”コト”。
ことに着目すると
- 発生源が外部の人である
- 将来についての約束であることがわかる
例)ユーザーが本を買う。つまり、ユーザーが発生源で、ユーザーは代金を払い売り手は本をユーザーに届けることを約束する
このときに、本の在庫はあるか、ユーザーは代金を支払うお金を持っているかなどを調べないといけない(業務ロジック)。これを全てドメインオブジェクトとして書いていけば良い。
自己文書化できていることが目標
→コードで業務要件を表現できている状態
そうするためには、”言葉”が重要。業務で使用する言葉とコードで使用されている言葉が一致していないといけない。この言葉やドメイン知識を得るために、コンテキスト図、業務フロー図、パッケージ図、主要クラス図などを使用する(業務フローの整理ができ、理解が深まる)。
ドメインモデルを使用して機能を実装するときに気をつけること
- Application Serviceに業務ロジックを書かない(ドメインオブジェクトが足りていないときに起こりがち)
→足りなければ付け足す、修正する。つまり、ドメインオブジェクトを改善していく。 - 画面の要求が複雑になり、それに伴ってApplication Serviceを複雑にしない
→登録と参照のクラスを分離する。例)引き出す金額を入力、残高を更新、残高を表示、と画面が分かれている場合は、登録クラス(残高を更新)と参照クラス(残高が足りているか更新前に確認、残高を表示)などに分ける
契約による設計
→サービス提供の約束事を決め、設計をシンプルに保つ。
例)残高を確認後に金額を引き出す場合に、残高を確認しているのだから、引き出す金額が残高以下になることは少ないとし、例外処理として書くなど。
契約の例
- nullを渡さない/nullを返さない
- 状態に依存する場合は、先にユーザーに事前に確認させる
- それでも異常が起こった場合は例外で通知する
防御的プログライング
→ユーザーは何をしてくるか分からない前提でプログラムを組む。つまり、様々な防御ロジックが実装される。
→いくら防御的に書いても例外は発生するので、意味のないことが多い
Service Applicationは基本Serviceと複合Service(シナリオクラス)に分けるとController(プレゼンテーション層)に業務ロジックが漏れにくい
複合サービスのメリット
- プレゼンテーション層と業務ロジックを分離できるので、単体テストを書きやすくなる
- シナリオクラスが業務手順書になる
- 小さなServiceクラスをまとめることによってどこに何が書かれているかなど整理できる
業務ロジックとデータベース操作を分けることを意識する。
例)注文保存処理が走ったときに、テーブルAとBにそれぞれデータが挿入される場合は、テーブルAとテーブルBにデータをそれぞれ挿入処理を書くのではなく、注文保存処理は注文保存処理で1つしか存在してはいけない。テーブル操作に引きづられてメソッドを作成してはいけない。
テーブル設計
- NOT NULL制約を徹底する
→NULLが許容される場合は設計がおかしい。どうしてもNULLを入れる必要がある場合はテーブルを分ける - 一意性制約を徹底する
→言わずもがなデータが重複などしていたら大変。。 - 外部キー制約を使う
→データの整合性を担保する
テーブル設計のときにも”コト”を意識する
- データの更新タイミングが異なる場合にはテーブルを分ける
- コトを記録する場合はUPDATEを使用せず、新しいデータをINSERTする(赤黒処理)
- カラムを追加する場合はテーブルを追加する(外部キー制約を使う。NULLを許容しないため。)
- 状態の記録とコトの記録は分離する
ステートソーシング
→残高10万円を記録する
イベントソーシング
→入出金を記録して残高を計算する
オブジェクトとテーブルの設計は分離すること
画面との連動
- 画面(View)に業務ロジックを流出させない
// 悪い例
if (サイズL) {
// 強調cssを追加
}
- 画面を複雑にしない
- タスクベースのUIにする(登録画面で複数データを登録させるのではなく、1データごとに登録画面を用意するなど)
ドメインオブジェクトをそのまま画面に表示するかビュー専用のオブジェクトを別途用意するかで言うと、できるだけオブジェクトと画面を一致させたいので、ドメインオブジェクトをそのまま使用するようにしたい。
7章はあんまり理解できていない。。
8章はWeb APIについての説明。Web APIについては別途まとめたいので、ここではメモを割愛する。
Web API: The Good Parts読みたい
9章はAgile的な考えについて。こちらも別途まとめたい。
10章はオブジェクト思考をどう学ぶかについて。読み返すのめんどいのでおすすめ本だけ適当に書いておく。
既存のコードを改善する→「リファクタリング」
オブジェクト指向を体に馴染ませる(オブジェクト思考エクササイズ)→Thought Worksアンソロジー
オブジェクト思考らしい考え方を身につける
実践パターン
オブジェクト指向入門
Evans本