“図書館で本を借りて読む”場合のREAモデル

加藤潤一氏(以下、加藤):ここからはちょっとまた別の話に。寿司以外の話になりますが、本の予約。図書館で本を借りて読むことがあると思いますが、その場合の「なぜ本を読めるか」という所有権、というか読む権利ですね。「本を借りている間は読める」権利を、REAでモデル化するとこうなります。

「本を借りた」というEventがあることで、「誰がその本を読めるか」を説明できるようになります。使い方としてはこう表現できるという話です。なので、Eventが重要になるということです。

ちょっとごちゃっとした図ですけれども、PaymentとSalesがCommitmentとしてあって。またちょっと寿司の話に戻りますが、現金の受領と寿司の販売だけでなく、契約やCommitmentに対応しようすると、さらにこのようなモデルを追加すれば、契約があって、約束事があって、Eventという関係性を表現できます。そのため、このようなモデルにする場合があります。

Commitmentと契約は、予約でよく使われます。「アパートメントを借りる」でも、CommitmentとEventがあれば説明できると言われています。Commitmentは、後に起こるEventに、どんなResourceが関連づくかを表現できると。

例えば、ホテルの予約みたいなものは実際の現物を予約するのではなく、リソースの型に対して予約をして、将来のある時点で一致する、Resource Typeと実際のResourceを予約する、という2段階になることがあります。

ホテル業務だとこのようなことがよくあるらしいです。客が到着する日まで、予約は部屋の仕様、部屋のタイプと関係しますが、当日の朝フロント係がその日に始まる各予約に部屋番号を割り当てるときにResourceTypeに合致するResourceを選択して、具体的な部屋のインスタンスの予約を確定するような考え方です。このように、REAをうまく使うとモデリングができます。

予約に対して集約はどう関係するか

ここからちょっとDDD(ドメイン駆動設計)がわかる人にしかわからないかもしれない用語を使い始めるので、だんだん難しくなるかもしれないです。「予約に対して集約がどう関係するか」という話をしようかと思います。

ホテルの部屋を借りると考えた場合、このようなモデルはなると思いますが、右上がCommitment、約束事ですね。「部屋を借りる」という約束事をしました。つまり予約です。

それに対して、どの部屋のプランなのかがResourceTypeというかたちで関連づきます。実際に部屋を借りたら「部屋を借りた」というEventが発生して、具体的な部屋を4つのモデルで説明できますが、予約状況がこれだとまったくわからないんです。REAだけの観点だとわからなくなってしまうので、DDDのAggregateという考え方を導入するとどうなるかを考えてみました。

教科書どおりにREAを説明してしまうとおもしろくないので、ここからちょっとアドバンスな話題になりますが。ちょっとキーワードがどんどん増えてきているので混乱するかもしれないですが、「EventStorming」という、ドメイン駆動設計でドメインを分析するときに、海外で最近よく使われている手法です。日本でもやっている人が増えてきていますが、この手法を使うと、ドメインモデルをわりと簡単に分析できるようになります。

ただ、Eventが中心です。Eventを見つけた上で、この真ん中の「Aggregate」がドメインモデルですが、それを分析する考え方です。

これも新しいキーワードなので混乱するかもしれませんが、このパターンとは、そもそも「CQRS/Event Sourcing」が前提になっている分析方法です。なので、書き込み側と読み込み側は、完全にモジュールとして分離されます。

まずEventに注目すると、さっきのREAのEventはここに該当します。Commitmentがこの図でどこにどうやって該当するかを考えていきたいかなと。

それで言うと、正しいかどうかはまた別にして、僕の解釈ではこの関係になるかなと。Commitmentはコマンドになり得ると考えていて、このコマンドが与えられると、結果的にEventが発生する。何かにコマンドを与えるとEventが発生するという考え方でモデリングするのが、EventStormingの考え方です。

そのコマンドをどこに与えるかというと、集約、Aggregateというオブジェクトに与えると、Eventが発生します。このAggregateが何かというと、現在の状態を表すものです。

EventStormingではこのような分析方法をやります。そこに先ほどのホテルの予約のような考え方を適用すると、「部屋を借りる」コマンド、これはCommitmentでもありますが、それをAggregateの「部屋の予約」というオブジェクトに与えて、成功すると、「部屋を借りた」というEventが発生する。

REAのCommitmentは、集約に対するコマンドと見なせるのでは、という仮説です。これでモデリングしたほうが、収まりがいいので。

全体像にまとめるとこうです。Aggregateに対して、部屋の予約を命じる。成功すると、「空いている」「空いていない」「プランが有効」「有効じゃない」など、部屋の予約をする時の。ビジネスルールをAggregate内で評価した上で、パスしたらEventが生成されると。このEventによって、現在の状態を説明できるため、Aggregateがそういう目的で存在すると。

ルームのプランを予約している状態と、実際の個別の部屋のインスタンスを予約している状態が別々にあるため、それをモデル化する。サブタイプのようにはなるかもしれませんが、これによってモデルの関連をうまくまとめられるんじゃないかという観点です。

実際に実装するとこうなって、モデル図で見たままの型になります。注目するべきは、ちょっと見にくいんですが、この部屋のプランの型ですね。room typeのような。

部屋のプランの予約の状態のときに、実際に部屋のインスタンスを確定する場合は、コマンドを受け付けるという意味合いになります。そのため、こういった振る舞いをもたせておいて、部屋の状態を変えたり、実際に部屋を借りたというEventを発生させたりを、部屋の予約オブジェクトが責任をもちます。このようにモデル化できるんじゃないか、という話です。

Eventはダブルコミットしてしまうこともある

先ほど見せたモデル図は細々していて、そのまま利用するのには使いにくいため、UseCaseというモジュールにまとめることがほとんどだと思います。部屋を借りるUseCaseですね。プランを予約していますが、実際の部屋に、具体的にこのインスタンスを確定するときのオペレーションは、こういったUseCaseにまとめられると思います。

一見よさそうに見えますが、状態とEventがダブルコミットしているところがあります。Eventをうまく使おうとすると、このように状態を保存してEventをパブリッシュすることがありますが、こういうダブルコミットをやり始めると、技術的にいろいろ難しくなるので。

このような問題を解決するためにイベントソーシングという技術がありますが、これは今回の話題と別の話題になるため、別の機会に話したいと思います。

Eventをそのまま使うといろいろ面倒なことが出てくるので、分析の途中までで止めるやり方もあります。もちろんイベントソーシングする考え方もありますし、そこはこのあたり、で分水嶺になってくるかと思います。

振る舞いのパターン

振る舞いのパターンです。先ほど言ったように、ポリシーと業務遂行レベルの2つの構造的パターンがあり、その上に振る舞いのパターンがあるのですが、これまたREAとはまったく別の話題が出てきます。

いくつかパターンがありますが、代表的なやつを1つ紹介しようと思います。これも「モデルをちゃんと考えて、それに基づいて実装しましょう」という話なんですけれど。

期日が完了したってどういうこと?

期日パターンがあり、書籍に掲載されている説明によると、指定した期日を過ぎているかの判定と、その期日が有効な期限になっているのか。期限が来ているのか、完了しているか、キャンセルしているかという状態遷移があります。

でも、これを見た時に、なんかおかしいなと思ったんです。「期日が状態を持たないで、期日が過ぎているどうかだけ表現すればいいんじゃないか?」と。

いきなりこの本のディスりから始まってしまいますが、期日が関連する状態、左側に「完了した」とか「キャンセルした」とかありますが、「期日が完了したってどういうこと?」のような話です。言葉としては、変な感じがします。

ここでいう期日の状態は、実はタスクの状態ではと思ったので、右側のようにリファクタリングしました。でも、実際に状態は期日にはなく、タスクにあるのではということで、状態のパターンはいくつかありますが、実際はサブタイピングするかたちになるのではと。

しかも、新たな状態を作るのにmutationを起こすって、Javaで説明されていた資料なので仕方ないですが、今時だと、immutableで新しい状態は戻り値で生成して返すというパターンだと思うので、そうすべきなんじゃないかと思います。

DueDate、期日パターンは、タスクなしにはあまり意味がない説明になってしまうので、こういうかたちで考えました。DueDate自体は役割がないので、関数的な動作になっています。

実際のコードはこんな感じで、基本的には期日が過ぎているかどうかぐらいしか中身がないので、パターンとまで言えるのかって話ですが、こういうかたちで判定できればいいのかなと。演算子メソッドや比較演算子などで比較できると、もっと表現力が上がるかもしれませんが。

タスクは、ちょっと崩れていますが、状態を確認したり、期日が過ぎているかどうかの確認をします。期日が過ぎていれば新しい状態を返しますが、そうでなければ変化しない感じにしています。

コンプリートですね。タスクをコンプリートすることに、そんなに関心があるのかって感じですが。これも状態を遷移させることで、Scalaでは、あくまでthisの状態を破壊してはいけないって話です。なので、新しいタスクのインスタンスを返しましょうと。ケースクラスにすれば、パターンマッチも使いやすくなるよというぐらいです。なので、パターンとまで言えるかという話はあります。

『ビジネスパターンによるモデル駆動設計』は教科書におすすめ

最後にまとめですけれども、この本、実は絶版で買えないんです。いい教科書なんですけど。2007年発刊で、だいぶ経っていますが、教材としてはおすすめです。REAの観点はモデリングの入口になると思いますし、リアクティブ界隈だと最近よくイベントソーシングの話も出ますが、イベントソーシングでモデリングするときもREAの観点はすごく使えるヒントになります。

最後の振る舞いのパターンは、あれは実は値オブジェクト、value objectの振る舞いのパターンです。これも設計するときにすごく参考になるので、教科書として見ていただく分にはいいのかなと。

実際に教科書を実用に活かせるとなおよいですが、実際はさっきみたいに「よくよく見ると、よくない実装になりそうだ」という部分もあるので。真に受けるとあまりよくないですが、参考にはなると思うので、ドメイン駆動設計の副読本として読むとおもしろいのではと思います。

ということで、私の発表は終わります。ありがとうございました。