ドメインモデルを設計する
概要
この記事は、現場で役立つシステム設計の原則〜変更を楽で安全にするオブジェクト指向の実践技法を読み学習した内容を Ruby on Rails のコードに例を変換してまとめ直したものです。
ドメインモデルはアプリケーションの業務ロジックをオブジェクト指向で整理する技法。(詳しい詳細は前回の記事を参照)
この記事ではそのドメインモデルをどのように設計すれば良いのかについての基本的な考え方とアプローチについて記述しています。
ドメインモデル設計の考え方
ドメインモデルを開発するためには以下の二つの活動が必要。
-
分析: 人間のやりたいことを正しく理解する。
- 要求の聞き取り
- 不明点を確かめるための会話
- 図や表を使っての整理
- 理解した結果を記録するための文書の作成
-
設計: 人間のやりたいことを動くソフトウェアとして実現する方法を考える。
- パッケージ構成と名前
- クラス構成と名前
- メソッド構成と名前
ドメインモデル設計とは業務を理解するための分析とソフトウェアとして実現するための設計が一体となった活動となる。
さらに以下のようなポイントを押さえておくと良い。
・分析・設計を視覚的にわかりやすいようにクラス図を用いて表現する際は、分析・設計それぞれのクラスを個別で考えず一致させる
実装のことを考えずに分析クラスを作成すると自由度が高すぎて現実の詳細を無視した形となり、それを元に設計すると業務ロジックの整理の単位としては不適切な構造に無理やり業務ロジックを押し込めることになる。
分析では業務の関心事の要点をうまく説明することが大切だが、ソフトウェアとして実現することを無視せず、業務ロジックの詳細とそのロジックをプログラミング言語で表現することも視野に入れて分析する。
分析において作成した物事のいろいろな説明のなかから、業務ロジックをプログラムとしてうまく記述できる設計クラスを見つけることがオブジェクト指向の分析設計である。
両クラスを一致させるために分析者と設計者を分離せず、コードを書く人間が分析作業から設計作業まで一貫して行うと自然と両クラスを一致させることができる。
・業務に使っている用語をクラス名にする
ドメインオブジェクトの設計の基本は業務の中で使用される具体的な用語の単位で業務ロジックを整理すること。そのために業務用語とクラス名・メソッド名をまず一致させる設計を行う。
・データモデルではなくドメインモデルを設計する
設計においてデータとロジックを別々で考え、データのみに焦点を合わせたデータモデルを設計することは、プログラム上でデータクラスと機能クラスを分割する「手続型」設計のアプローチであり、これは業務ロジックがさまざまなクラスに散らばってしまいメンテナンスしづらいソフトウェアとなる原因となる。
例えば、「年齢」という業務の関心事を「生年月日」をインスタンス変数として持つ「年齢」クラスとして設計するというようなデータよりもロジックに焦点を合わせた設計となるデータとロジックが一体となるドメインモデルを設計することが重要。
ドメインモデル作成のやり方
具体的なドメインモデルを設計するアプローチの仕方は以下の通り。
部分を作りながら全体を組み立てていく
オブジェクト指向では、まず個々の部品となる小さいドメインオブジェクトから作り始め、それを組み合わせながら段階的に全体を作っていくことを基本方針とする。
全体と部分を行ったり来たりしながら作っていく
部分だけに目を向けると全体としては間違った方向に進む危険性があるので、全体的な俯瞰を意識しておくことも重要。
全体を俯瞰するためには以下のような図を用いるアプローチがある。
- パッケージ図
- 業務フロー図
パッケージ図
パッケージ図は、個々のクラスを隠蔽し、パッケージ単位で全体の構造を俯瞰する手段。
クラス、もしくは業務ロジックのおおよその置き場所をパッケージとして割り振る。
パッケージ図はパッケージ間の依存関係を矢印で表現することができ、この矢印はオブジェクトを参照する方向。これは業務の流れの時間軸を逆に辿った関係となる。
業務フロー図
業務のさまざまな活動を、時間軸に沿って図示したものが業務フロー図となる。
活動の主体ごとにレーンを並べて、それぞれの情報のやり取りを明らかにする。これにより、業務の流れに沿って登場するオブジェクトとして発見できる。
重要な部分から作っていく
全体を俯瞰したら、その中から重要な部分を探す。そして業務的に重要であると判断できた箇所からその部分を実現できるデータとロジックを考えながらクラスを設計・実装する。
独立したプログラミング単位であるドメインオブジェクトを重要な順に開発を進めていくのがドメインモデルを開発する基本のやり方となる。
業務的に重要であるかの判断が誤りであるパターンもあるが、一つのプログラミング単位として独立しているドメインオブジェクトの開発は他のコードに影響を与えず、ほとんどの場合無駄になることはない。
独立した部品を組み合わせて機能を実現する
ドメインモデルは、業務アプリケーションの部品となるドメインオブジェクトを集めて整理した「部品の倉庫」となる。
業務機能を実現したいとなった場合は、ドメインモデルから必要な部品を取り出し、組み合わせて機能を実現する。ドメインオブジェクトを組み合わせるのは3層構造で言うとアプリケーション層の役割となる。
ドメインオブジェクトを機能の一部として設計しない
プログラムを開発する際に機能を中心に考えて、機能を分解しながらプログラム部品を作っていくと、上位機能部品と下位機能部品に分解構造もしくは時間的な前後関係に左右される依存関係が生まれ簡単に切り離せなくなる。
ドメインモデルを構成する個々のドメインオブジェクトの設計では、このような機能の分解構造や時間的な依存関係を持ち込まず、あくまで単体で動作確認できる独立性の高い部品として開発する。
「どうやって機能を実現するか」に注目せず、ある特定の業務データとそれを用いた業務ロジックだけを切り出した独立したオブジェクトを作成する。
例えば、「消費税」クラスは消費税率と税額計算・端数の丸めロジックをまとめた独立した部品として開発する。このクラスは業務機能のどこで使用されるかはっきりしなくても独立して設計し、開発が可能。
ドメインオブジェクトを見つける方法
業務上の重要な関心事を見つけて、それをドメインオブジェクトという部品としてボトムアップで小さく開発していくのがオブジェクト指向開発での基本フローであることを前項までで述べた。
しかし、アプリケーションを開発する上で大量の業務知識の中から重要な点とそうでない点を判別するにはどうすればいいのか?
業務の関心事を分類してみる
まず以下のように、業務の関心事をヒト・モノ・コトに分類すると、業務知識の整理の枠組みとなる。
分類 | 例 |
---|---|
ヒト | 個人、企業、担当者、管理者、など |
モノ | 商品、サービス、店舗、など |
コト | 予約、注文、出荷、など |
・ヒト
業務活動の当事者を表す。意思があり、判断し、行動する主体。
ヒトに対応するドメインオブジェクトは意思/判断/行動についてのデータを持ち、そのデータを使った判断/加工/計算のロジックを持つ。
・モノ
ヒトが業務を遂行するときの関心の対象。物理的なモノだけでなく権利や義務のような概念的なモノも含む。
モノに対する関心事を表現する属性には以下のようなものがある。
- 数量、金額、率
- 説明、注釈
- 状態
- 日月や期間
- 位置
モノに対応するドメインオブジェクトはこれらの属性を表現するデータと、そのデータに対してどういう判断/加工/計算をしたいかをロジックとして持つ。
・コト
業務活動で発生する様々なコト(事象)。コトを記録し、コトの発生を通知するのが、業務アプリケーションの基本的な関心事。
コトを表現するドメインオブジェクトが持つ一般的な属性は以下の通り。
- 対象: 何についての発生した事象か
- 種別: どういう種類の事象か
- 時点: いつ起きた事象か
このような業務の事象を適切に記録し管理することが業務アプリケーションの基本であり、その事象が「起きて良いか」「起きた場合に何をすべきか」の判断は業務の重要な約束事となる。
コトに注目すると全体の関係を把握しやすい
関心事を 3 分類した後はさらに「コト」に注目して整理すると以下のようにそれぞれの関心事の関係を把握しやすい。
- コトはヒトとモノとの関係として出現する(誰の何についての行動か)
- コト同士は時間軸に沿って明確な前後関係を持つ(例: 注文 -> 出荷 -> 支払い)
このような関心事同士の関係が把握できると、ある事象が起きた時の業務アプリケーションが必要なデータやロジックについても範囲を限定でき、関係も把握しやすくなる。
コトから業務ルールを見つける
重要な業務ルールはコトに関するルールを深掘りすることで特定されていく。
例えば販売活動を例に時間軸に沿った一連の業務上のコト(事象)は以下のようになる。
- 受注
- 出荷
- 請求
- 入金
この中で「受注」は他のコトと異なる特徴がある。
- 発生源が外部のヒトである: 買い手のヒトが注文し、売り手のヒトが同意するという二者の合意によって発生する(売買契約の成立)
- 将来についての約束である: 受注により売り手は商品の出荷を約束し、買い手は代金の支払いを約束する
この売り手と買い手の間に発生した約束を記録し、実行を追跡し、約束通りに完了させるのが販売管理アプリケーションの基本目的となる。
しかし、このような約束をするためには以下のような内容の妥当性を確認する必要がある。
- 在庫はあるか(出荷可能か)
- 与信限度額を超えていないか
- 自社の販売方針に違反していないか
- 相手との事前の決め事(取引基本契約)に違反していないか
さらに、これらの妥当性についての業務ルールは以下のような複雑さを持つ。
- 約束の相手が誰かによって、約束して良い範囲が異なる
- どの商品についての約束かによって、金額や納期が異なる
- どのタイミングの約束かによって、金額や納期が異なる
このような様々な妥当性についての業務ルールを特定したら、そのルールを実現するためのデータとロジックの組み合わせを考えていく。
具体的なドメインオブジェクトの発見からドメインモデルとしてまとめるまでの考え方の例
1.「在庫はあるか(出荷可能か)」という妥当性の確認には「数量」に関するロジックが必要なので、それらは「数量」クラスにまとめると良さそう。
2.「数量」は個数単位と箱単位での扱いが必要なので、それらのロジックは「数量単位」クラスにまとめると良さそう。
3.「数量」に関係する上記のクラスは「数量」関連の業務ロジックの置き場所を明確にするために「数量」パッケージにまとめて管理しよう。
このように一つのパッケージを作成したら、同様に「与信」「基本契約」パッケージを作成し、足りない部品を逐一補いながらそれぞれの部品を組み合わせてドメインモデルとしてまとめていくと受注ルールを網羅できるドメインオブジェクトが整っていく。
手続き型とオブジェクト指向の業務ルールの記述の違い
プログラムを書く視点からは、業務ルールの実体は以下のような基本データ型に対する if 文を用いた判断ロジックが最小の単位となる。
- 数値の一致や大小比較
- 日付の一致や前後比較
- 文字列の一致/不一致の判定
これらの判断ロジックの取り扱いについては手続き型とオブジェクト指向で以下のような違いがある。
-
手続き型設計の場合
- データクラスを受け取った機能クラスで、 if 文や switch 文を用いて必要な条件判断と分岐を実行する。
- 条件の組み合わせは基本的には if 文の入れ子構造となる。(トランザクションスクリプト)
- 新しいルールを追加する場合は、元々の if 文の分岐構造に新たな分岐を追加する。業務ルールが増えるほど分岐構造は複雑になる。
-
オブジェクト指向設計の場合
- 判断の元となるデータとロジックごとにオブジェクトを生成する。
- 様々な条件をそれぞれのオブジェクトに自分で条件判断をさせ、それらを適切に組み合わせる。
- if 文の入れ子構造は発生しない。
- 新しいルールを追加する場合は、そのルールの判断の元となるデータを持つオブジェクトに判断ロジックを追加する。変更の影響範囲をそのクラスに閉じ込めやすく、単純に安全に変更できる。
参考文献
この記事は以下の情報を参考にして執筆しました。