ドメインオブジェクトの責務について


設計するとき、「このオブジェクトの責務は何だろうか?」とか「この責務に名前をつけるなら何か?」とか、責務について考えることがよくあります。そもそもその責務とは何か、という根源的な疑問について再確認すると共に、ドメイン駆動設計の観点からドメインオブジェクトの責務についても考えてみたいと思います。

責務とは

困ったときの古典引用。もう絶版になった、オブジェクトデザインという、書籍を紐解いてみましょう。DDDからの引用が多い書籍で、DDDの設計スタイルは、この書籍で紹介する「責務駆動設計(responsibillity-driven design)」の原則に従うことが大きいとされています。

この書籍によると、「責務」には以下が含まれるそうです。

「4.1 責務とは何か」

  • オブジェクトが行う動作
  • オブジェクトが持つ知識
  • オブジェクトが他に影響を与える主要な判断

これらの言葉を身近な言葉で置き換えるなら、動作は振る舞い、知識は属性、判断は制御フローでしょうか。

物理的なオブジェクトは責務次第で知性を持たせることができる

物理的なオブジェクトは、知性の高いソフトウェアオブジェクトとは異なり、主に仕事を行うか、ものや情報を保持するだけです。電話帳は物理的なオブジェクトですが、何も行いません。サーモスタットは実在し、判断を行い、制御信号を送信します。やかんは実在しますが、ほぼ容器の役割を務めるのみです(時おり、笛を鳴らし合図を送ります)。物理的なオブジェクトは、通常、情報に基づいた判断を行いません。これに対して、犬は人間の最良の友人であり、仲間であり、犬自らのためにさまざまなことを行います。ソフトウェアオブジェクトは、これらの両極端なオブジェクトの中間に位置します。つまり、ソフトウェアオブジェクトは意識を持ちませんが、どのような責務を与えるか次第で活発にもおとなしくもなりえます。

知性のあるオブジェクトにするかしないかは、 設計者がどのような責務を与えるかで決まるということだと思います。この責務の定義からすると、情報しか保持しない、電話帳オブジェクトでも、分析の結果次第で振る舞いを起こせる可能性があります。

やかんオブジェクトにも動的な側面がある

Alexanderは、著書『Notes on the Synthesis of Form(邦題:形の合成に関するノート)』において、やかんにとって適切な形はどのようなものか、と問いかけています。やかんは水を漏らさずに、その水を沸騰するまで温めることができます。人間は沸騰したお湯でいっぱいのやかんを安全につかみ上げ、1杯のお茶を注ぎます。そして、世の慣わしに従うなら、やかんは水が沸騰したときに笛を鳴らして合図します。これらの特徴は、次のような一般的な責務に言い換えることができます

  • 水をこぼしたり、しぶきを上げたりせずに中身を注ぐ
  • 水を漏らすことなく、沸騰するまで温めることができる
  • 水の沸騰を知らせる
  • 安全な方法で運ぶための便利な手段を提供する

やかんでいうところの「水を格納する容器」としての責務は、静的な側面を捉えたものです。一方で、「水の沸騰を知らせる」という責務は、オブジェクトの動的な側面を表現しています。知性を得たやかんオブジェクトは沸騰イベントを外部のオブジェクトに通知するかもしれません。このように考えることで、貧血症オブジェクトのようにデータを保持するだけの責務しか持たないのではなく、固有の知識を持ちより活発に活動できるオブジェクトを設計できます。

無論、ユースケースによっては静的な側面しか持ち得ないドメインモデルもあるでしょう(台帳的なユースケースの場合はあり得る)が、だからといって動的な側面を分析しない理由にはならないので、設計者としてはこの観点は肝に銘じたいところです。

責務を与えることで貧血症を回避する

この考え方を、ドメインモデルに当てはめて考えてみましょう。たとえば、「顧客が、ATMを介して、銀行口座に現金を入金する」というユースケースの際に、顧客オブジェクトに入金メソッドを持たせる設計を考えてしまいがちです。しかし、この設計では、入金など銀行口座にまつわる、ドメイン知識を表したコードが分散するのは目に見えています。いわゆる、貧血症オブジェクトと言えます。銀行口座を理解するのに、散らばったコードをあちこち調べて頭の中でつなぎ合わせる作業が必要になります。

class Customer(id: CustomerId, name: CustomerName, ...) {
  // 主語のオブジェクトに振る舞いを単に割り当てると、ドメイン知識を集約できなくなる
  def deposit(money: Money, to: BankAccount): BankAccount = ...
}

class BankAccount(id: BankAccountId, ...) {
  // 入金メソッドはここにはない
}

実は顧客はユースケースのアクターであり、システムの外部に存在するので、ドメインモデルではありません。よく混同する例としては、ユースケースのアクターとしてのユーザと、ドメインモデルとしてのユーザアカウントです。これは全く別ものですが、ときとして混同されて利用されることがあるので注意が必要です。この例では顧客はシステムの外にいるアクターです。そして、ドメインモデルはシステムの内部に存在します。

私なら顧客はドメインモデルとなり得ないので、銀行口座オブジェクトに入金メソッドを持たせます。これならドメイン知識は分散しません。おそらく、銀行口座という物理的なオブジェクトに振る舞いを持たせるのに違和感があるかもしれませんが、上述の話を思い出してください。設計者がオブジェクトにドメイン知識が集中する責務を与えるべきです。

class BankAccount {
  // 銀行口座に関するドメイン知識を集約する
  def deposit(money: Money): BankAccount = ...
}

class Customer(id: CustomerId, name: CustomerName, ...) {
  // 銀行口座ではなく、顧客に関する知識を表現する振る舞いを定義する
}

責務をどこから見つけるのか

さて、その責務をどこから見つければよいのでしょうか。「4.2 責務はどこから見つかるか」に以下の記載があります。

  1. ユースケースに記述または暗示されている、システムの責務を識別する
  2. ユースケースや他のシステム記述に内在する抜けを埋め合わせるために、より低いレベルの責務を追加する
  3. テーマと設計ストーリーから、さらにシステムの振る舞いを見つける
  4. 「…であれば…となるが、どのように実現されるか」という推論の連鎖をたどる
  5. ロールステレオタイプに適合する責務を識別する
  6. 各候補の内部的な性質を探す
  7. 候補間の関連や依存をサポートする責務を識別する
  8. オブジェクトの主要な「生涯イベント(life event)1」と関連する責務を識別する
  9. 特定のソフトウェア環境2に適合するために、オブジェクトが担う必要のある技術的な責務を識別する

この本の著者らは、責務を見つけるときに、このような作業を行うようです。これの観点から独断と偏見でピックアップして考察します。

責務は、ユースケース中のシステムの振る舞いに関する記述文やそこに暗示されているものから見つかる

ユースケース記述とオブジェクトの責務との間には抜けがあります。責務は、オブジェクトが知っており、行い、判断することについて大ざっぱに記述したものです。ユースケース記述は、システムの振る舞いと、アクターとシステムとの相互作用に関する記述文です。ユースケースは外部の観察者の観点からソフトウェアを説明し、どのように達成するかについて言及しません。ユースケースは、システムがどのように動作するのかという大ざっぱなアイデアと、システムに必要になるタスクを提供します。設計者は、ユースケースで発見した記述を、アクション、情報保持、判断を行う責務という明確な記述文に変換することによって、この抜けを埋めます。

つまり、こういうことだろうか。

  • ユースケース記述とオブジェクトの責務にはギャップがある
  • ユースケース記述は、アクターとシステムの相互作用を表現したもの
  • ユースケースは、アクターの観点からシステムがどのように動作するかタスクレベルで説明したもの
  • 設計者は、ユースケース記述をもとに、アクション(何らかの演算)、情報保持、判断を行う責務に変換できる

ユースケース記述からオブジェクトに対する責務を見つけ出せるという解釈ができると思います。この章では、「講義にオンラインで登録する」というユースケースを分析しながら、責務を見つける例が記載されています。しかし、私には形式的な手法には見えません。無知から始めても分析できる方法はないでしょうか?

私のおすすめは、ICONIXのロバストネス分析を行い、ユースケースとシーケンス図のギャップを埋めることです。この分析作業は上述に関連するものだと思います。以下のスライドをみるとイメージが掴めます。対象のドメインに詳しければ、イメージしやすいでしょうが、そうでなければこういう手法で分析するとよいでしょう。ドメインオブジェクトの責務が明確でないというときは、関連するユースケース記述があるか、明らかにすることをお勧めします。

https://speakerdeck.com/j5ik2o/tomeinmoterinkufalseshi-mefang?slide=37
https://speakerdeck.com/j5ik2o/tomeinmoterinkufalseshi-mefang?slide=38

より詳しく知りたい方は、ユースケース駆動開発実践ガイドを読むとよいと思います。

「…であれば…となるが、どのように実現されるか」という推論の連鎖をたどる

洞察をさらに得るためには、さまざまな要求が設計にどれほどの影響力を持つかを検討する必要があります。これには、他の情報源から責務を発見する場合よりも、さらに高いレベルに思考を持ち上げる必要があります。この場合、「ローンを支払う」などの具体的なタスクや、「履修単位の合計を検証する」などの具体的なアクションから始めてはいけません。
そうではなく、「ソフトウェアは定期メンテナンス時のみオフラインとなるべきである」などの高いレベルの目標から、その目標を達成する一連のアクションやアクティビティヘと続く道筋をたどる必要があります。このときに初めて、システムが結果として何を個別に行う必要があるかを表した記述文を作成できます。いったん、これらの個別の結論を考え出せば、具体的な責務を定めることができます。

責務を求めるのであれば、具体的なユースケース(記述)から始めるのではなく、要求にたち戻るべきということのようです。推論の連鎖というのは、要求・ユースケース・責務の関係に整合性があるかという視点でしょうか。要求が不明であれば、何のための責務か説明できなくなるので、この考えは非常に合理的です。

リレーションシップ駆動要求分析(RDRA)

私がドメインモデリングする際に推奨している、リレーションシップ駆動要求分析(RDRA, 書籍はモデルベース要件定義テクニックです)という要求分析の手法でも、この考え方をさらに強化できます。

以下のスライド資料には、システムに内在するドメインモデルを語るのに必要不可欠な要素が何かを説明しています。RDRAは周辺の知識を整合性・網羅性・表現力という観点で体系化します。まさにこれは「推論の連鎖」を検証する手法だと思います。詳しくは書籍を読んで見てください。

責務は、オブジェクトが担うロールステレオタイプから自然と思いつく

オブジェクトが「知っている」のか、「行う」のか、「制御する、判断する」のかは、主としてそのロールステレオタイプに基づきます。オブジェクトの性質を探求することによって、初期の責務の集合が導き出されます。

振る舞いに注目し続ける要素

オブジェクトデザインでは、振る舞いに注目するための設計要素は、以下のように示されています。

  • アプリケーション
    • 相互作用するオブジェクトの集合
  • オブジェクト
    • 1つ以上のロールの実装
  • ロール
    • 関連する責務の集合
  • 責務
    • タスクを実行する義務、または情報を知っている義務
  • コラボレーション
    • オブジェクトもしくはロール(またこの両方)の相互作用
  • 契約
    • あるコラボレーションを行う際に満足すべき条件を要約した取り決め

代表的なロールステレオタイプ

ロールには以下のステレオタイプがあります。

  • 情報保持役(Information Holder)
    • 情報を知り、情報を提供する
  • 構造化役(Structurer)
    • オブジェクト間の関係と、それらの関係について情報を維持する
  • サービス提供役(Service Provider)
    • 仕事を行うが、一般に演算サービスを提供する
  • 調整役(Coordinator)
    • 他のオブジェクトにタスクを委譲することでイベントに対応する
  • 制御役(Controller)
    • 判断を行い、他のオブジェクトのアクションをしっかりと指示する
  • インターフェイス役(Interfacer)
    • システム内の異なる部分間で情報やリクエストを変換する

これらを踏まえると、ロールよって責務が明確になり、責務によって属性や振る舞いが明確になると考えてよいでしょう。振る舞いを考えることが難しい場合は、ロールから考えてもよいかもしれません。

責務はオブジェクトのライフサイクルにおいて起こる重要なイベントと関係している場合がある

どう反応するかという観点から責務の大部分が形成されるようなオブジェクトもあります。これらのオブジェクトは特定のイベントによって刺激を受けます。制御役と調整役はこのような特徴に合致します。つまり、制御役と調整役が行う作業のほとんどは、それらが解釈した刺激への対応として行われます。

ここでいうイベントの定義が不明なのですが、命令に対してどう反応するかを分析すれば、責務の大部分がわかるということでしょうか。

Event Stormingというモデリング手法

最近、Event Stormingという分析手法に注目しています。詳しくは以下のブログ記事を参照してみてください。簡単に説明すると、ドメインオブジェクト内部で発生したドメインイベントを基に、振る舞いやそのドメインオブジェクト自体の名前を見つけるモデリング手法です。「どう反応するかという観点」をうまく利用した分析方法の一つだと考えています。

RDRA,ICONIXなどは割とマクロな視点でドメインモデル像に迫りますが、Event Stormingはミクロな視点としてドメインイベントから分析していくようです。前者の方式は静的な側面の後に動的な側面を分析しますが、Event Stormingはいきなり動的な側面から分析が始まるのが特徴的です。どちらか一方だけを利用するのではなく、必要に応じて組み合わせるとよさそうです。

新しいモデリング手法: EventStormingをはじめるための準備

まとめ

オブジェクトデザインの責務駆動設計には、ドメインオブジェクトを分析するためのヒントが込められているので、RDRA,ICONIX,Event Storimgを実践する人は、事前に読んでおくとドメインモデリングの手助けになるかもしれません。


  1. オブジェクトが生成されてから削除されるまでの期間で発生するイベントのうちで重要なものを意味する。 

  2. EJBのような特定の実装技術の意味