社内で展開した記事ですが、つぶつぶと「良かった」という感想をいただいたのでこっちにも投稿
※一旦ハードコピーですがコツコツと見やすいように編集していきます。
DDDに関して
ここ最近の日本のシステム設計の現場では、SI/Web系問わずドメイン駆動設計(DDD)が浸透してきており主流となりつつ(?)あります。
このDDDですが、業務の分析やシステムアーキテクチャ検討の際の業務ドメイン分析…などと様々なシチュエーションで活用されますが、今回は、上記のうち業務分析の方を例に概要と、ついでにCleanArchtectureとの関係性も織り込みながら説明をします。
※ちょっと、わかってる人向けに今回記事にすること/しない事
・する事: DDDなんすかそれ?への回答と、軽い設計例での説明
・しない事: 用語の解説(知りたい人はhttps://nrslib.com/bottomup-ddd/ がおすすめ)
通常、ある業務をシステムに落とし込む時、ヒアリングによる要件抽出をし業務を分析モデルとして落とし込み、その要件を基にした仕様書作成/実装の流れで進んでいきます。
しかし多くの場合、実装上考慮せざるを得ない概念や、ユースケースを実装に落とし込む際に分析モデルには存在しない新たな抽象化が設計では必要になります。
そこで、分析モデルとプログラムの設計を一致させることでこの問題を解決しようというアプローチがDDDです。
具体的には、従来のcontroller-service-repositoryのような目に馴染んでるであろうレイヤードアーキテクチャのように、先に構成(アーキテクチャ)があって、それに合わせて分析モデルをアーキテクチャに則ったクラスに落とし込む方法ではなく、実世界でのモデルの振る舞いに応じてクラスを定義していく。
上記の違いをヤカンを例に説明する。
例えばヤカンの温度に応じて、沸騰しているか否かを判断し、沸騰していれば処理を行うビジネスロジックを実装するとする。
DDDではないベターな設計として、以下が考えやすい設計の例である。
- 色や大きさ、現在の温度などの実世界のヤカンを表す情報を保持する値オブジェクト(DTOクラス)を使用する
- Serviceクラス側で、この値オブジェクトのgetterメソッドを呼び出し、現在の温度を取得する
- 取得した温度が、沸騰しているとみなされる温度に達しているか判定する
- trueが変えれば処理を行う
一方で、DDDに基づいた設計をする場合、以下のような設計ができる ※あくまで例である。
- 色や大きさ、現在の温度などの実世界のヤカンを表す情報を保持し、温度によって沸騰しているか否かを返すメソッドをもつ
- Serviceクラス側で、上記メソッドを呼び出す
- trueが変えれば処理を行う
上記設計の一番の違いは、ヤカンオブジェクトがDTOの責務にとどまっているか、状態を返す責務も担っているかですが、これは単にDDDとは、オブジェクトの状態をオブジェクト自身に返させるということを意味している訳ではありません。
ではなぜ上記のような設計例のなったか解説します。
まず、前提として皆さんは「ヤカンが沸騰している」と実生活で判定を行う時、どうしていますか?
きっと毎度ヤカンに温度計をつっこみ、温度計の数値を確認し閾値を超えているかどうかで沸騰しているか否かを判断する人はいなくて、実際は、ヤカンの中の水の温度が沸騰する温度に達するとブクブク.。o○とし始め、それを観測することで沸騰しているかを判定しているはずです。
要はヤカン自身が沸騰状態であることを、水をブクブクさせることで表現しているのです。
こう考えると、ベターとされていた設計の方が現実世界の概念と乖離していることがわかると思います。
つまりDDDとは超ざっくりい言うと、分析した概念モデルをそのままに現実世界でのオブジェクト(=ドメイン)の振る舞いをロジックの前提としておく設計手法です。
なるほどじゃあ、これからはDTOにロジックを書きまっくてDDDするぞ!と考えてる方、ちょっと待ってください。
DDDは上記青字箇所のようにあくまで設計手法に過ぎません。
じゃあ、どうすればDDDで設計されたシステムを実装するのがいいのかというと、ここでCleanArchtechtureが登場します。
CleanArchtechturenに関して
CleanArchtechtureといえば、以下のドーナツ絵を思い浮かべる方なら割といると思うのですが、
この絵を守ることがCleanArchtechtureの本質ではないです。
重要なのは関心の分離。この関心の分離を実現するために何が必要なのかというと、各レイヤーの依存ルールです。
ここではDDDとの関係性に特化させて説明にするために、UseCase層とEntity層の話が主になります。(他の層や⇒に関して気になった方は連絡ください!説明します!)
UseCase層は主に、「ユースケースの処理の流れを実現すること」や「トランザクション管理」などといったシステムを成立させるためのロジックや、システムであることによって発生したロジックを持つ層です。
レイヤーアーキテクチャでいうところのService層と責務はさほど変わりませんがUseCase層の責務としては、あくまでシステムを成立させるための処理を持つことです。
では、システムを成立させるためも業務上のビジネスロジックはどこに持つのか?というとそれを持つのがEntity層となります。
Entity層はシステム都合ではない、コアなビジネスルールを表現した処理を持つのが責務となります。
ここで一瞬だけ上のドーナツ絵の話に戻りますが、この絵は外部の層は内側の層にのみ依存するという制約があります。(⇒が内側に向いてるのはこの表現)
よって、この絵に則って考えると、UseCase(=システム上のビジネスロジック)はEntity(=ビジネスルール)に依存していることがわかります。
では次にUseCase層とEntity層はどういう堺でクラスとして分解させるべきかですが、Entity層は要は業務ドメインとなるものが該当し、それはメソッドを持ったオブジェクトかもしれないし、あるいはデータ構造と関数の集合かもしれません。
重要なのは、どんなクラスであろうと、Entity層に位置するべきは外部の振る舞いがいかに変わろうとも、変わる事のないコアルールを表現する事です。
DDDの章で言うと「ヤカンの沸騰判定」がそうですね。ヤカンが沸騰してお茶を汲もうが、カップ麺を作ろうが、ヤカンがある温度で沸騰状態になるというコアのルールは変わりません。
つまりEntity層に持つべきロジックは「ヤカンが沸騰しているかの判定」となります。故に、この責務を全うするものが関数であるか、ヤカンを表現したクラスであるかは重要ではないです。
続いてUseCase層は、このビジネスルールによって、「沸騰していればカップ麺を作る」といったシステム要件を満たすロジックを持つことを責務としています。
が、あくまでUseCase層とEntity層との関係は対になっていなければいけない訳ではなく、とあるUseCaseを満たすためのEntityはさらに別のEntityに依存をしていてもよいのです。
どういうことかというと、ヤカンの水が沸騰してお湯になるところまでは説明しましたが、では「お茶を汲む際の粉末の量」はどちらに実装すべきでしょう?
これは業務次第なのですが、例えば「頼まれた濃さに応じてお茶を汲む」ことがシステム要件なのだとしたら、このシステムが満たさなければいけないUseCaseは「Inputに応じたお茶を汲むこと」になります。
もしシステム要件が「カップ麺かお茶を頼まれたら作って運ぶ」だとしたら、このシステムが満たすべきUseCaseは「頼まれたものを運ぶ」です。
よって、頼まれたものをどう運ぶかが何度も変更されようが、「お茶の組み方」や「カップ麺の作り方」はUseCaseを満たすための不変なルールとなり、Entity層よりの責務となります。
このようにCleanArchtechtureは「こういうルールは○○層!」といったような決め打ちの枠組みではなく、システム要件からどの「ドメインがどのような責務をもつのか」、「それは外部の層に依存すべきか」、「不変なものなのか」を分解するための考え方であり、責務を細分化することによってテストの容易性や、変更への耐久性をあげるためのアーキテクチャということになります。
おわりに
いかがだったでしょうか?
「ふーん」となったり「なるほど」となりましたかね?
今回はヤカンが大活躍の単純な例でしたが、実際のシステムはもっと複雑なつくりになってます。
DDDやCleanArchtectureも所詮は手段の一つにすぎませんが、このような考え方を使いってよりよいシステム(テストしやすい!変更しやすい!)を作るには、
DDDのような考え方でコアドメインのロジックとシステムが満たすべきロジックを正しく分解する考え方とそれをアプリのアーキテクチャに確実に落とし込む技術の両方が必要ということです。
以上、読んでいただきたありがとうございました。