最近、ドメイン駆動設計ってどうやって実践すればいいかなーという質問をよくされるので、この記事が満額回答にはならないと思いますが、書いてみたいと思います。
シンプルな問題はトランザクションスクリプト、いわゆる手続き型で対処できます。問題が小さいのでコードは直接的でわかりやすくなる傾向にあります。 とはいえ、世の中の問題はシンプルなものばかりじゃない。複雑な問題もある。DDDの著者であるEric氏は、複雑な問題はドメインモデルを使って対処すべきと説く。
ドメインとは問題の領域とか知識の範囲をいうのですが、DDDはそのドメインにある概念をモデル(ドメインモデル)として定義して、さらに実装として紐付けていく設計手法です。
モデルクラスは概念ありき
例えば、電車にまつわるドメインで考えたとしたら
- 電車
- 乗客
- 駅
- ダイア
などの概念が登場します。
現実世界に限った話ではなく、仮想世界でもドメインはありますね。 例えば、PRGはどんなドメインになるかというと
- 主人公
- アイテム
- 武器
- 防具
- 仲間
- 街
- 街の人
- モンスタ
- クエスト
というような概念が必要になってくるでしょう。僕らがよく使っているGitだってドメインはあるわけです。Commit, Tree, Blobなど。
モデルクラスの実装を日々見ていると、これがモデルだ!と思ってしまいがちですが、実際のモデルは利用者つまり人間の頭の中にある概念を指しています。モデルとしての実装であるモデルクラスは写像ですね。概念が先にあってモデルクラスがあるということです。
モデルにはシナリオがある
これらのモデルをどのように使うか、シナリオがあります。"主人公は武器や防具を装備できる”というシナリオがあった場合は次のような属性や振る舞いを持つことになるでしょう*1。
trait Character { val name: String val gold: Int val arm: Option[Arm] val protection: Option[Protection] def equipArm(arm: Arm): Character def equipProtection(protection: Protection): Character }
実際はこれだけの実装では済まないと思います。
たとえば、
- 武器と防具はひとつずつしか持てないのか?
- 装備していない武器と防具はどうなるの?カバンのなかに入れることはできないの?
- アイテムはそもそも仲間と一緒に持つものじゃないの?主人公と仲間とはどういう概念?パーティ?
- 仲間も武器と防具を装備できるよね?ということは主人公と仲間を抽象化した人という概念が必要?え、それだと街の人も人だよね???
など、様々なシナリオが見えてきます*2。 シナリオを使えば、ほかの概念との関係も見えてきます。これによって独自の語彙ができあがってきます。これをユビキタス言語と呼びます。
ちなみにシナリオがないモデルは無用の長物です。つまり、使えないモデルを作っても仕方ないわけです。例えば、家の設計図を作成するソフトウェアのドメインで、コンセントクラスがあったとしたら、"電源クラスもあった方がいいね!"ということで実装クラスを簡単に追加してしまいがちなんですが、これは要注意です。その電源クラスには、使えるシナリオがありますか?モデルをどのように使うか、ドメインの言葉で説明できなければなりません。この問いに答えられない場合は、ドメイン層に実装を追加するのは止めた方が無難です。(ただ、そのモデルがドメイン層ではなく、他のレイヤーのクラスだったというのはよくあるので、新しいモデルが必要では?と考えることが自体は悪くない。)
永続化はドメインの知識ではない
ここから蛇足。このシナリオの名詞からモデル、動詞から振る舞いを候補としてあげていくわけですが、先ほどの主人公に次のような振る舞い(saveメソッド)があったとします。
val me : Character = ... // モンスタに攻撃するなど me.save
"主人公が主人公自身を永続化する"というものがドメインモデルのシナリオだと言えるでしょうか。実は永続化という言葉はドメインの言葉ではありません。モデルが持つ状態を永続化する技術について説明しているのであって、RPGのドメインの世界の知識を説明していません。"主人公が冒険の書に出来事を記録する"というシナリオなら、よいかもしれません。 何れにしても、ドメインモデルがドメインにフォーカスするためには、このような永続化の実装はインフラストラクチャ層に追い出し、リポジトリ経由でアクセスするとよいでしょう。
val me : Character = ... // モンスタに攻撃するなど characterRepository.store(me) // -> me を CharacterRecord#save に委譲
ということでActive Recrodパターンを想像したかもしれませんが、Active Recordパターンには罪はありませんね。私はData Mapperの方をよく使いますが、Active Recordはオブジェクトとテーブルをシンプルに関連づける設計パターンとしては十分に使えるものです。PofEAA(エンタープライズアプリケーションアーキテクチャパターン)では、Active Recordは"ドメインモデルである"と記述されています。この書籍では、ドメインモデル="振る舞いとデータの両方を一体化させたドメインのオブジェクトモデル"としているのでDDDとはちょっと意味が違うように思います。DDDでは、ドメインモデルとはユビキタス言語の写像であり、レイヤー化アーキテクチャによって他の層からは隔離される存在です。テーブルに結合してしまうActive Recordとは意味が異なってくる。個人的には、Active Recordで表現するモデルは、インフラストラクチャ層のモデルとして扱う方が適切だと考えます。
どのようなプロセスでモデルを探求するのか
というわけで、話を戻す。シナリオでモデルに揺さぶりをかけていき、ドメインモデルを定義していきます*3。そして、チームメンバでドメインモデルについて議論して、そのモデルを試してみようということになったら、実際に実装コードを書いてテストできるようにします。実際に動かして試さないと評価できないからです*4。そして、また新たなシナリオに挑戦するというサイクルをぐるぐる回すという感じです。
ざっくり説明したのですが、このプロセスは著者であるEricさんによって次の絵にまとめられています。モデル探索のうずまきです!*5
{% img http://domainlanguage.com/ddd/whirlpool/model_exploration_cycle_v2010-06-19.png %} http://domainlanguage.com/ddd/whirlpool/
QCon Tokyo 2013で原田さんが話した資料のP10あたりに日本語化されたものがあります。
テキストとしては次のとおり。
- シナリオ
- ストーリを語る
- 肉付けする
- 難しいところにフォーカスする
- コアドメインにフォーカスする
- モデル
- モデルを提示
- 状態ウォークスルー
- 解決策ウォークスルー
- 言語の探求
- 間違う
- コードによる探査
- シナリオを”テスト”としてコードする
- 厳密さを加える
- 言語を洗練する
- 解決策を探求する
- 間違う
- 収穫&文書化
- 参照シナリオ
- まともなモデルの一部
- ほとんどのアイデアは書かない
まぁ、難しく聞こえるかもしれないけど、とりあえず実践することをおすすめしたい。仲間内でもよいので、ドメインを決めて”うずまき”を回してみるとよいのではないかな。一度でよいモデルというのは得られないと思いますが、繰り返しの経験の中で学習して、次のモデルの改良につなげることが大事ではないかなと思います。
*1:型の名前がCharacterになってますが正しくないかもしれません。
*2:その前に名前とかお金を持っていないとどうしようもないよねってのはあるw
*3:本書ではドメインエキスパートとソフトウェアエキスパートのインタラクションによって、深いモデルを導き出すことが推奨されています。とはいえ、なかなかドメインエキスパートは見つからないので、最後は自分たちがなるしかないとは思います...。
*4:コードを書くときにドメイン以外のコードも書いてしまいがちなのですが、ドメイン層だけに注力することが求められます。ドメイン層は概念とのマッピングを行い知識を表現するレイヤなので、他のアプリケーションレイヤや画面、帳票、HTTPのリクエスト、レスポンス、データベースなどに依存しないように考えることが必要です。
*5:重要なのに、本書に含まれていない。そしてまだDraft0.3ェ…。