大量のメソッドを保有し、数千、数万行単位にぶくぶく膨れ上がった巨大クラス。別名「神クラス」とも「大きな泥団子」とも呼ばれる、長大で複雑で密結合で極めて変更が困難なアイツ。
そんな巨大クラスの退治に有効な、ドメイン駆動設計を基本思想とする「役割駆動設計」を紹介致します。
解決したい課題、狙う効果
- 数千、数万行単位の巨大クラスの登場を抑止する。
- 小さくシンプルな構造に落とし込み、堅牢で変更容易性の高い設計へ昇華させる。
例1:筆者をモデリング
分かりやすくなるよう、まず私を例にモデリングしてみます。私は以下のような特徴があります。
- IT企業の従業員
- 家族がいる(妻, 子供)
- 趣味でゲーム制作している
ダメな設計
何も考えずに人クラスとして設計すると、よく以下のような構造になりがちです。
従業員として仕事をする、父親として家族サービスする、趣味としてゲーム制作する、それぞれのメソッドが備わっています。
しかしこの構造には問題があります。各メソッドの操作に必要な機密情報、個人情報、ゲーム制作知識を同じクラスのフィールドとして持ってしまっている、という点です。
社外に漏らしてはいけない機密情報を「家族サービスする」メソッドで触れてしまうし、一方で知られたくない個人情報を「仕事をする」メソッドでも触れてしまう。発表まで秘密にしておきたいゲーム制作知識を、「仕事をする」メソッドで知られてしまう可能性もあります。
ある処理を実行する上で不要な情報を取得できたり、破壊できたりしてしまいます。
この例のクラスはまだ小さいため、開発者がそれなりに注意を払えば意図しない結果を防ぐことができるでしょう。
しかし実際の製品コードで見られるのは、いくつものフィールドやメソッドを抱え、数千、数万行に膨れ上がった巨大クラス。そうした巨大クラスでは様々な要素が密結合になって混迷を極め、開発者の注意力限界を超え、極めて変更が困難です。
解決方法
そこで、この課題を解決するのが役割駆動設計。
課題を整理します。
人でもモノでも、時と場合(コンテキスト)により役割が異なります。それを「人クラス」というたったひとつのクラスに無理に押し込んでしまっているのが原因。
つまり役割ごとにクラスを区分けすることが解決のポイントです。
もう一度立ち返ってドメイン分析すると、私には以下3つの役割があることが分かります。
- 仕事をする「従業員」
- 家族サービスをする「お父さん」
- ゲーム制作する「ゲーム制作者」
これらの役割ごとにクラスを起こしてみます。
これにより、各役割を振る舞うにあたり必要なデータ、ロジックが分離されました。
機密情報や個人情報は「従業員」「お父さん」クラスそれぞれに隠蔽されているため、他の役割のメソッドにより破壊されることはありません。
データ、メソッドそれぞれが疎結合高凝集になり、極めて安定した作りであることが、クラス図レベルでもご理解頂けると思います。
「別々のクラス、別々のインスタンスにしてしまっては、システム上異なる人物と認識されるのでは?」と心配になるかも知れませんが、それは各クラスに識別IDを持たせ、同じ人物に対し同じIDを付与することで一意性を識別するようにすれば良いです(詳細はドメイン駆動設計のEntity)。
どこに罠があるのか
モデリング初心者の陥りがちな罠は、役割ではなく**「モノに着目してしまう」**という点です。
「ユーザー」「商品」「金額」……これらは単なるモノの名前であって、何ら役割を表現していません。
例えばECサイトでは、**「ユーザー」は「発注者」や「受注者」、「出荷担当」「支払者」など場面に応じて様々な役割があります。「商品」も場面により「出品物」「発送品」「納品物」などになりますし、「金額」**も「希望小売価格」「割引価格」「支払い金額」など状況により様々な顔を覗かせます。
それらを「ユーザー」「商品」「金額」という「モノに着目した」命名をしてしまうとどうなるかは、先の例で示した通りです。
名前を「役割で縛ってない」ため、際限なく様々な役割を吸収し、数千数万行単位の化け物と化してしまうのです。
モデリングにおいて、役割の概念を表現していない名前で安易に命名するのはアンチパターンです。
設計のポイント
前述した罠を踏まえた上で、役割駆動設計のポイントを説明します。
先の例では、人やモノは状況(コンテキスト)により役割が異なる、ということを説明しました。
この考え方を図式化すると以下のように表現できます。
「モノ、コンテキスト、役割」の関係は、「モノ、照明、影」で例えられます。
モノに照明を当てると、影が出来上がります。影は、照明を当てる角度により形が異なります。
つまり、照明=コンテキスト、影=役割とすると、
- 仕事コンテキストでは従業員
- 家庭コンテキストではお父さん
- 趣味コンテキストではゲーム制作者
それぞれの影が出来上がります。つまり、
「ある照明をモノに当てたときに、どんな影が出来上がるか」
「あるコンテキストにモノを置いたときに、どんな役割として振る舞うか」
を分析するのがモデリングのポイントとなります。
モノ本体をモデリングしてはいけません。出来上がった影=役割をモデリングするのが肝要です。
役割とはその名の通り「役を割る」こと。
たったひとつのモデルに多くの役を背負わせてはいけません。
役を割って、役ごとにモデリングする。これが役割駆動設計です。
例2:フリマアプリのモデリング
この役割駆動設計に基づいて、もうひとつの例に関してモデリングしてみます。
次はメルカリなどのフリマアプリをお題にします。
フリマアプリでは、ユーザーは商品を出品することもできますし、逆に購入することもできます。アプリ上で、ユーザーは以下の操作ができるものとします。
- 出品側として、出品する商品情報を入力する。
- 出品側として、商品を出品する。
- 購入側として、商品を選択し購入手続きをする。
- 購入側として、代金を支払う。
- 出品側として、商品を発送する。
- 購入側として、商品を受け取る。
- 購入側として、出品者を評価する。
ダメな設計
一番やっちゃダメなパターンを見てみましょう。未熟な設計では以下のようになることでしょう。
何が課題でしょうか。
- 例1と同様に、「ユーザー」というたったひとつのクラスが役割を背負いすぎています。
- ある処理を実行するにあたり、不要なデータにまでアクセス可能な構造になってしまっています。
- 出品する人と発送する人が同じとは限りません。違う人が担当している可能性があります。同様に購入者と受取人が同じとも限りません(「知人にプレゼントを送る」ユースケースがそれに当たる)。この「ユーザークラス」のまま「役割ごとに関係者が異なる」状況に対応するには、別途識別IDを増やしたり途中住所を変更するなどいびつな構造に陥ったり、そもそも変更が困難になる可能性があります。
解決例
私の考える解決例は以下のようになります。
役割とメソッドが1:1で紐付いているような構造になります。
「分割しすぎているんじゃないか」と考える方もおられるのではと思いますが、それぞれのクラスがメソッドの処理に必要な最小限のフィールドのみ所有している形となっているので、データ安全性の面でこれで良いと考えます。
また、各役割ごとに別々クラス、別々のインスタンスになっているので、例えば購入者と受取人が同じでも違っていても対応可能な構造になっています。
【オマケだけど重要な考え方】
なお、専ら「~者」というクラス名になっていますが、「出品」クラスや「購入」クラスというように動名詞で名付けたクラスにしても良く、その場合例えば「出品とは、○○を××することである」というように出品のビジネス的な意味を定義するようにロジックを実装することで、理解容易性が高く色鮮やかなデザインのドメインオブジェクトとなるでしょう。私はこちらの方が好きです。
加えて、「出品する」「購入する」など動詞を耳にするとすぐにメソッドとして設計する方が多く見られますが、その考え方は「何か適当なクラスにメソッドをぶら下げる」発想になり、役割過多(責務過多)な巨大クラス化の要因になるため、**「動名詞でクラスを命名する」**のは小さくシンプルな設計を目指す上で重要だと私は考えます。
結局「役割駆動設計」とは何なのか
役割駆動設計は、私が新たに考案したアイデアではなく、実はドメイン駆動設計における「境界付けられたコンテキスト」を、私が言い直したもの、ないし再解釈したものです。
書籍「実践ドメイン駆動設計(IDDD)」p.60~61において、書籍のモデリングを題材に「書籍を扱う統一モデルを作ると混乱するから、コンテキストそれぞれに対応する、書籍を表す型を作れ」とあります。また、ユーザーのモデリングに関しても、「コンテキストに応じて演ずる役割、つまり投稿者、所有者、参加者、モデレータと定義した」と記述があります。
IDDDで既に役割について言及されており、新しい考えでも何でもありません。しかし「境界付けられたコンテキスト」と聞いてもいまいちピンとこなかったり、では実際どういう観点で区分けすれば良いのか、を考えた場合、やはり「境界付けられたコンテキスト」という言葉の裏に隠れていた**「役割」**をもっと前面に打ち立てた方が分かりやすいのではないかと考え、わざわざ「役割駆動設計」という言い方、考え方に捉え直したのです。
また役割駆動設計は、増田亨氏の下記スライドp.51における「区分」と発想が同じです。
ドメイン駆動設計 本格入門
また、同氏が提唱する「目的特化オブジェクト」とも同じです。
まとめ 役割駆動設計の設計要件
最後に役割駆動設計の設計要件を以下にまとめます。
設計要件 | 意図 |
---|---|
モノに着目してモデリングしないこと。 | 役割を際限なく吸収し、巨大クラス化してしまうため。 |
取り扱うコンテキスト(状況、場面)を洗い出すこと。 | コンテキストに対応する役割を洗い出すため。 |
各コンテキストに対応する役割をモデリングし、役割を冠した名前をクラスに付与すること。 | 役割以外のロジック混入を防止するため。 |
各役割クラスには、役割を振る舞うのに必要な最小限のフィールド、メソッドを定義すること。 | 設計をシンプルにし、安全性や変更容易性を向上するため。 |
参考文献
ドメイン駆動設計
実践ドメイン駆動設計
ドメイン駆動設計 本格入門