集約はイベントから考えると考えやすい

この記事はドメインモデルが改善されるステップが見えて面白かった。

僕もこのドメインで振る舞い中心のモデリングをしてみた。実装は可能なモデルを書いてみましたが、今回は細かい実装の話はありません。

イベントを抽出

まずイベント(起こった事実)から考えました。日々の勤怠で何が起きるのだろう。出勤したり退勤したり休憩したりと打刻するのは間違いない。システムが何か命令(コマンド)を受理したらこういうイベントが発生するはず。

「出勤した」イベントには、誰がいつ出勤したかを説明する事実が記載されている。「退勤した」や「休憩を開始した」や「休憩を終了した」なども同じ。あと、打刻間違いの修正もあるので「打刻を修正した」もある。ちょっと荒削りだがこんな印象。

  • 出勤した

  • 退勤した

  • 休憩を開始した

  • 休憩を終了した

  • 打刻を修正した

ちなみにイベントとそれ以外のリソース(エンティティなど)との関係には以下のような類型がありますが、今回の場合は1。イベントが必要なリソースと関連するパターンです。

  1. R(Event, Resource): R(受注, 顧客)

  2. R(Event, Event): R(受注,請求)

  3. R(Resource, Resource): R(従業員, 部門)

コマンドを抽出

この適当なイベントを基にコマンドを考えると以下のようなものになる

  • 出勤する

  • 退勤する

  • 休憩を開始する

  • 休憩を終了する

  • 打刻を修正する

こうやって並べてみるとMECEではないことに気付く。「打刻を修正する」と「出勤する」「休憩を開始する」などの概念レベルや用語の用法に不統一感がある。「出勤する」ではなく「出勤時間を打刻する」だよなと。となると以下でよさそう。

  • イベントは「(出勤時間|退勤時刻|休憩開始時間|休憩終了時間)を打刻した」

  • コマンドは「(出勤時間|退勤時刻|休憩開始時間|休憩終了時間)を打刻する」

モデル rev1

本来なら閲覧や読み込み用途のリードモデルを先に考えるがこの時点で考えられる集約を導きだしてみよう。

集約の名前は一旦XXXとして仮置き。コマンドはメソッドの名前と引数になる。コマンドはいつでも受理されるとは限らない。たとえば出勤もしてないのに、退勤時間を打刻できてもよいだろうか。よいことはない。その場合Resultは失敗を返すべき。成功した場合は新しい集約のインスタンスとイベントを返す。イベント名は勤怠イベントにした。

モデル rev2

このモデルの違和感としては以下

  • イベントの個別のサブタイプは必要か

  • コマンドを表すメソッドはそれぞれのパターンが必要か

もっとシンプルに

  • イベントは「打刻した」

  • コマンドは「打刻する」

だけではダメか?ありかもしれない。時間さえ分かれば、今が出勤なのか退勤なのかの状態は計算で判断つくかもしれない。あとは、集約はイベントの集合から最新状態を作り出すのでそれができるならよい。

というわけで見直した。名前は勤怠集約にした(集約の名前どうやって決めるかは長くなるので書かないけど、コマンドやイベントを総称するような名前になることが多いですね)。なんかそれっぽくなってきた。

勤怠集約は誰がいつ打刻したかの関心を扱うので、少なくとも社員ID、勤務日を属性にもっている可能性がある。それがないとイベントが生成できなさそう。また、打刻できるかどうか就業規則に依存しそうなので、就業規則オブジェクトみたいなものも必要そう。さらに、打刻修正するならすでに打刻された時間を属性として保持していないと「打刻を修正する」メソッドの事前条件を検証できなさそう。このメソッドは打刻時間リストに指定された勤務時間の要素が含まれることを正しく検証しなければ打刻を修正できない。

みて分かるとおり、集約はドメイン上の状態を更新するのに必要な属性しか持ちません。

リードモデル

さて、リードモデルを考えよう。閲覧や読み込みのためのデータモデルがリードモデル。

リードモデルは元の記事が参考になります。簡略的に書いてますがほぼそのまま。おそらく発生するイベントから必要なリードモデルは導出可能でしょう。

実装どうなんのよ

実装については長くなったので別の記事にします。あと以下のハンズオンのCfPにみんながスターを付けて採用されれば、そこでも共有します。

※修正 11/30 打刻を修正するメソッドの引数名が間違っていたので修正

@j5ik2o
歳をとっても霜降り肉!をモットーにしております