1. Qiita
  2. 投稿
  3. DDD

苦悩と喜びを与えてくれた[エリック・エヴァンスのドメイン駆動設計]をまとめた

  • 21
    いいね
  • 0
    コメント

はじめに

[エリック・エヴァンスのドメイン駆動設計]この本をエンジニアであれば聞いたことや見たことある方はいるのではないでしょうか?
私も良本という噂だけは知っていたので、一度は読んで読んでおきたいなと思い今回読みました。
私の性格的に、サーっと流し読みはできない性格(流し読みしたところに良いことが書いてあると勿体無いので)なので、しっかりと1文字づつ読んでます。

読んで見て思ったことは、はっきり言って難しすぎる、それなりに設計に関わる技術書は読んでますが、飛び抜けて難しいです。
言い回し、例題、語彙、全てにおいて難しいです。少しでも気を抜くと何言っているのか分からなくなります。
全く中身のないことを、それっぽく言っているだけなのではないかと錯覚をするほどです。
このTED Talksを思い出しました。

[ウィル・スティーヴン 「頭良さそうにTED風プレゼンをする方法」]
https://www.youtube.com/watch?v=ToJD5r2SmwI

しかし、めちゃめちゃ良いことが書いてあります、個人的に目から鱗な情報がたくさんありました。
それゆえ、非常に勿体無い。難しすぎるがゆえに読むためのハードルが高くなり、最後まで読み切ったエンジニアは少ないのではないでしょうか。

この記事の目的はDDDを理解してもらうことではありません(ちょっとした記事を読んで理解できるものではないと分かったから)、[エリック・エヴァンスのドメイン駆動設計]を読んで見たいと思ってもらうことです。
私は、かなーり独特の咀嚼をするので気をつけてください。
また、分かりやすさを優先するために、あえてDDDの重要な要素を省いているところもあります。

何が難しいか分かってもらうために原文のまま抜粋します

ドメイン駆動設計_pdf.png
ね?油断するとヤられますよね?
終始この調子です。

リファクタリングするにあたり、費用対効果を求めるディレクターはクソだ!

リファクタリングをするにあたり、リファクタリングにコストに対しての効果を定量的に求められることはないでしょうか?
それが、正確に出せないためにリファクタリングを断念したこともあるのではないでしょうか?

しかし、エリックはこのように言っています。

断続的なリファクタリングは「ベストプラクティス」と考えられるようになってきたが、ほとんどのプロジェクトチームは依然として慎重すぎる。コードを変更するリスクと、変更にかかる開発者の時間のコストを見込んでいるためだ。しかし、それほど簡単に見抜けないのが、設計をぎこちないままにしておくリスクと、その設計をなんとかするためのコストである。リファクタリングを行いたい開発者は、その決定が正当なものであると証明するように求められることが多い。こうした要求は理にかなっているように見えるが、もともと難しいものを不可能なほど難しくして、リファクタリングを抑制する(または見えない所で行わせる)傾向がある。ソフトウェア開発は、変更することで得られる利益や、変更しないことで生じるコストを正確に割り出せるような、予測可能なプロセスではない

ね。「リファクタリングするにあたり、費用対効果を求めるディレクターはクソだ!」って言ってるでしょ?
そんなこと言うディレクターがいたら、そっとこのリンクを送ってあげてください。

さらに私の痺れたセリフはこちら

現状を絶対視せず、安全でいられる場所を超えて、リファクタリングを支持する方向に向かうこと!!!

「安全でいられる場所を超えて」

これ重要。リスクを取れってことですね。

かっこよすぎるぞエリック。

ドメイン駆動設計とは

ドメイン駆動設計とは「ドメインの概念がそのままコードに反映されている設計」ということです。
「そもそもドメインってなんだ?」と思いましたよね?
本では「知識、影響、または活動の領域」と説明があります。
設計するシステムの業務内容と思ってもらっていいと思います。

ドメインの概念とは、業務で実際にやっていることや、ドメインエキスパート(業務の担当者)との会話の中で出てくる用語を概念化したものです。

ドメインの概念は設計で表現させてください、これは本当に意識しないと必ず、重要な概念をクラス内に隠蔽してます。

また、ドメインの概念に基づいてコードを修正すること、そうでなければすぐに設計とコードは一致しないものとなり、全く意味のないものとなってしまいます。

エンティティと値オブジェクトを明確にする

エンティティとは一意に特定する必要のあるオブジェクト。
値オブジェクトとはエンティティとは反対に、一意に特定する必要のないオブジェクトのこと。

例えば[家オブジェクト]があった場合、「家オブジェクト」はエンティティです。
「家オブジェクト」にコレクションとして保持される「コンセントオブジェクト」は値オブジェクトです。
「コンセントオブジェクト」を他の「家オブジェクト」が保持している「コンセントオブジェクト」とシャッフルしても問題ないですよね?それは値オブジェクトだからです。
コンセントがもし、木彫りの彫刻家が一つずつ彫刻を施しているコンセントであれば、それはエンティティとして保持する必要があるでしょう。

値オブジェクトであるがゆえに、1つのインスタンスを使い回すことができます、これをFlyweightパターンと言います。

値オブジェクトを設計する時には、不変である必要があります。
値オブジェクトを変更したい場合は、新しい値オブジェクトで置き換えることで対応する。

閉じた操作

本書では「1+1=2は実数の元で閉じている」と説明がありました。
「わかりづらいんだよクソが」ってなりますよね?

要するに、状態を持っていたり違う概念が無いということです。
[1+1=100]これは1が何か状態を持っているということです。
[1+1=パッパラパー]これは違う概念が含まれてしまっています。

[1+1=2]は状態を持っていたり違う概念が無いことで、非常に分かりやすいですよね?
これをクラス設計でも応用しましょうよ、ということです。

まず閉じた操作はエンティティには作れません、値オブジェクトだけに作れます。
閉じた操作を行うには、引数と戻り値の型が同じである必要があります。
[1+1=2]は引数と戻り値は同じ実数ですよね?

クラス設計もこのようにすべきです。
また、後でこのメリットを説明します。

コマンドとクエリーの分割

Bertrand Meyerが述べた設計の原則
メソッドはコマンドまたはクエリーのいずれかであり、両方にすべきではないということ。
コマンド → オブジェクトの状態を変更できるものの、値を返さない
クエリー → 値は返すものの、オブジェクトの状態を変更しないもの

副作用のあるものと無いものに明確に分けることにより、考える範囲を狭めることができる。テストもしやすくなるし、重複の排除にもなるはず。

集約を定義する

集約を定義した場合、集約のルート経由でしか、子供の要素へアクセスしてはならない、子要素は、他の集約ルートを参照できる。

例えば、中古車システムで、車が特定されている状態から、そのタイヤの情報が取得できればいいは場合は、車モデルが集約のルートに存在し、タイヤモデルは車モデルを親とする集約内に存在している。
集約外のモデルからはルートの車モデル経由でしかタイヤモデルにはアクセスできない。
car.png

中古車システムの要件の変更があり、特定の期間に製造されたタイヤを表示したいという要件があった場合は、タイヤモデルを車モデルの集約の外に出すということを検討する必要があります。
無題の図形描画 (1).png

この変更こそが、ドメイン駆動設計と言えると思います。

ファクトリーとリポジトリ

ファクトリー → 実態を抽象化して生成する
リポジトリ → データストアをカプセル化する
すいません、説明不足なのは理解しています。本書ではそれぞれ10ページ以上使って説明しているものなので、まとめる能力が自分には無いので雰囲気で捉えてください。

インスタンスの再構成
ドメイン駆動設計_pdf.png

新規インスタンスの保存
ドメイン駆動設計_pdf.png

私が伝えたいのは以下のことです、

ファクトリとリポジトリを組み合わせたいと思わせるものの1つに、「探して、なければ生成する」という機能に対する欲求がある。
そうすると、クライアントがオブジェクトの有る無しを判定する必要がないからだ。しかし、こういう機能は避けなければならない。
せいぜい、少し便利になるに過ぎないのだ。
通常、新しいオブジェクトと既存のオブジェクトを区別することは、ドメインにおいて重要であり、この両者を透過的に組み合わせるフレームワークは、実際には情報を混乱させてしまう。

私この部分を読んで「なるほど、これがドメイン駆動設計かー」と思いました。

最後にドメイン駆動設計を用いて、モデルを改善していきましょう

本書に書かれている塗料店向けに作られたシステムを例に説明します。
このシステムは、標準的な塗料を混ぜ合わせた結果を顧客に見せることができるシステムです。
ドメイン駆動設計_pdf.png

ドメイン駆動設計_pdf.png

理解するのは難しく無いかと思います、塗料クラスに塗料を混ぜるメソッドが有るんだなと思っていただければ十分です。

ここで考えたいのは、混ぜ合わせた後の[塗料2]のことです。混ぜ合わせた後の、Volumeはどうなるでしょうか?
一般的な理屈で考えれば、Volumeは0になるでしょう。しかし、引数である[塗料2]の属性を変更するなんてことは、クソコードの代表格です。
この問題は後で考えましょう。

ドメインの重要な概念を値オブジェクトとして抜き出そう

この塗料オブジェクトにおいて色は重要な概念です。
これを明示的にオブジェクトにして見ましょう。
まず思い浮かぶのは「色(color)」だが、塗料の場合はRGB表示とは異なります。これを名前に反映させる必要があります。

「顔料色(Pigment Color)」とすることでより正確な情報が伝わるようになりました。
ドメイン駆動設計_pdf.png

「顔料色」は値オブジェクトです。
したがって、不変なものとして扱わなければならない。
塗料を混ぜ合わせた際には、塗料オブジェクト自体が変化した。これはエンティティであるからだ。
これとは対照的に顔料色は変わらない。その代わり、混ぜ合わせることで、新しい顔料色オブジェクトができる。
これは前に説明した、「閉じた操作」になっている。mixedWithは顔料色の中で閉じています。

ドメイン駆動設計_pdf.png
ドメイン駆動設計_pdf.png

これで、塗料に置かれた変更を行うコートは可能な限り単純になった。新しい顔料色グラスはドメインの知識を捉えてそれを明示的に伝え、副作用のない関数を提供する。
その結果、理解しやすく、テストをするのも容易で、使うのも、他の操作と組み合わせるのも、安全に行える。

暗黙的な概念

Volumeの問題に戻ってみよう。
[塗料1]のVolumeは増えるのに、[塗料2]のVolumeは増えない。
このように実装したのには理由がある。
「このプログラムは追加された塗料を混ぜ合わせる前の一覧を帳票として出力している」という要件があったからだ。
Volumeを論理的に一貫したものとすると、アプリケーションの要求には適さなくなってしまう。

このようにぎこちなさを感じた場合は、暗黙的な概念が存在している可能性が大きい。それを探し出そう。

どうも塗料には、2つの異なる基本的な責務が与えられているようだ。これを分けてみよう。
ドメイン駆動設計_pdf.png
ここまでくると、コマンドはmixIn()だけである。このメソッドはオブジェクトをコレクションに加えるものであり、直感的である。これ以外の操作はすべて、副作用のない関数だ。

最後に

正直、うまく書けた自信無いです。
私自身も全然理解できなところが多いので…

それでも、[エリック・エヴァンスのドメイン駆動設計]を手に取るきっかけになれば幸いです。

Comments Loading...