はじめに
どうも、RikiyaOta です。
「日傘ってすごい発明なんじゃないか?」と気づき始めた今日この頃です。ナツいあつを乗り切りましょう。
今回は、社内での勉強会の一環で「エリック・エヴァンスのドメイン駆動設計」を読んでみたので、その内容を自分なりにまとめてみたいと思います。
社内勉強会を意図して執筆したものを一部改良したものでありますので、その点だけご了承いただければと思います🙇♂️
本書について
「エリックエヴァンスのドメイン駆動設計」というこの書籍は、ドメイン駆動設計の原典になっているようなもので、長く多くのエンジニアに読み継がれてきた本です。
正直、「DDD勉強をするぞー!」で一発目に読もうとするのはお勧めできない気が個人的にはしています。1
が、大事な話がいろんな箇所で語られていて、さらに書籍の後半ではより高度な具体例を駆使して説明がなされていて、とても内容の濃い1冊になっているかと思います。
Part1 でまとめる内容
主に書籍の第1部、第2部にあたる内容です。
- 第1部:ドメインモデルを機能させる
- 第2部:モデル駆動設計の構成要素
「第2部:モデル駆動設計の構成要素」の方は、エンジニアの人が食いつきやすい内容かなと推測しています。
具体的にいうと、モデルを実装に落とし込む上での主要素になるような、
- Value Object
- Entity
- Service
- Repository
などの導入をしていきます。
これはこれで実装をするという意味ではとても重要だし、共通言語として知っておいてほしいです。
ただし、ドメイン駆動設計のコアはここじゃないと認識しています。これはあくまで実装に落とし込む、モデルと実装を紐づけるための戦略であると理解しています。
「第1部:ドメインモデルを機能させる」では、ドメイン駆動設計のコアになる、「そもそもモデルをどう機能させる?」「なんでモデル作るの?」というエッセンスを学んでいきます。
今回の内容は、具体的な実装例は乏しいと思います。お話が多いかと思いますが、そもそも DDD は実装とモデルを紐づけるというビジネス全体・ドメイン全体を巻き込んだ1つの方法論なので、そのつもりで内容をまとめていきたいと思います。
Part2 でまとめること
本書の後半、第3部、第4部にあたる内容を解説する予定です。
- 第3部:より深い洞察へ向かうリファクタリング
- 第4部:戦略的設計
こちらについては、もっと具体的な題材で解説ができるかなと思っています。
(※後でリンクを記載しておきます・・・)
それでは、本題に入っていきます。
ドメイン駆動設計とは何か
「そもそもドメイン駆動設計とは何か?」から考えたいと思います。
「ドメイン」とは
ドメインとは、ソフトウェアに目を向けたものではなく、ソフトウェアが対象とする領域(=ドメイン)のことです。
つまり、ソフトウェアが解決しようとしている問題対象そのもの、そこに存在するユーザーや組織など種々の概念が相互作用する舞台のことをドメインと呼びます。
そもそもなぜ、ドメイン駆動設計が生まれたのか?
エンジニアあるあるかもしれませんが、良くも悪くも技術にしか関心のないエンジニアはいるというのが1つの問題だったみたいです。
つまり、ドメインのことをまともに考えようとせず、複雑な業務知識に関する問題も技術オンリーで解決しようとする姿勢がよくみられていたみたいでした。
この姿勢では、作り上げるシステムが複雑怪奇なものになってしまうのは想像に難くないと思います。
だって簡単なところで言うと、データ構造設計にしても、ちゃんと「ユーザーはユーザー」「組織は組織」として構造を定義してデータを整理しておかないと、プログラムは滅茶苦茶なものになります。さらに、データの更新処理なんかも入ったら、データが散らばってると、ロジックを実装することも複雑になります。
それこそ Relational Database における正規化理論なんかは、データ構造設計の重要性を語る上でのとても良い例だと思います。DDDではその点について、よりドメインに注力したモデリングを行います。2
ここで本書の言葉を引用します:
ソフトウェアの革新は、ドメインに関係した問題をユーザーのために解決する能力である。それ以外の特徴はいずれも、どれほど重要だとしても、この基本的な目的を支えるに過ぎない。ドメインが複雑だと、ドメインに関係した問題を解決するのは難しい作業になり、熟練した有能な人々が集中して取り組まなければならなくなる。開発者は、ビジネスの知識を積み重ねるべく、ドメインに没頭しなければならない。モデリングスキルを研ぎ澄まし、ドメイン設計に精通することが要求される。
そもそも対象にすべきドメインが複雑であれば、まずドメインの複雑な知識を整理し、その知識をモデルに落とし込んでチーム(←このチームは、開発者だけじゃないです。関係者全員)の共通認識・共通言語を作って相互に会話しながらプロジェクトを進めましょう、とということをDDDでは言っています。
だって、↑この共通認識がないと、出来上がったシステムを見て「うん、これで意図したものができたね」って全員が言うことできなくないですか???
もっと言うと、作ってる最中に 「俺たちが作ってるものはまさに俺たちが求めているものだ」 って全員が言うことができなくないですか???
余談:ある種、『当たり前』のことを言ってるように感じるところがDDDにはあるなぁと個人的には思います。ただし、システム開発の歴史としては、いわゆる上流工程の"頭の良い人"たちが「俺たちが完璧に設計したぜ、この通りに作ればできるよ。さあよろしく」と丸投げすることがよくあったそうです。で、実装段階で「ん〜、この設計だとしんどいなぁ。なんとか実装で対処するか」とやっていくと、あっという間に不要な複雑さを含むシステムが出来上がり。
ドメイン駆動設計のアプローチとは
ここまでで述べたことを踏まえて、ドメイン駆動設計はどんなアプローチをとったのか。
効果的なモデリングの要素として、本書では以下のように記載されています:
- モデルと実装を結びつける。
- モデルに基づいて言語を洗練させる。
- 知識豊富なモデルを開発する。
- モデルを蒸留する。
- ブレインストーミングと実験を行う。
1. モデルと実装を結びつける。
ドメインに関して得た知識をモデルという形で共通認識を得たときに、それをできるだけそのまま実装に落とし込む・同期をさせることをドメイン駆動設計では行います。
2. モデルに基づいて言語を洗練させる。
モデルと実装を結びつける上で、ドメインエキスパートと開発者が質の高いコミュニケーションをとれることがとても重要です。
両者のやり取りの中でいちいち用語の変換が必要なのであれば、適切な意思疎通ができないのではないでしょうか。
こうした事態を避けるため、ドメイン駆動設計ではモデルの一部として、ユビキタス言語という、チーム内の共通言語を策定して、その言語を使ってコミュニケーションをとることを推奨しています。
3. 知識豊富なモデルを開発する。
ドメインモデルには、ドメインに関して得た知識を十分に反映させることが必要です。
エキスパート:「実は業務上、こんな手続きをする必要があってね〜〜」
開発者:「それ、図のどこに書いてあります?」
エキスパート:「いや実ははっきりと書いてないんだけど、このユーザーがこういう条件の時に〜〜〜」
開発者:「じゃあ、その業務が存在することがわかるように、概念を整理しましょう」
↑こんなやりとりが生まれていくといい流れなのかなと思います。
4. モデルを蒸留する。
ここで、一般的に『モデル』という言葉の持つ意味を引用します:
モデルとは、科学的方法において、理論を説明し、可視化し、理解する為の簡単で具体的なもの(図形や物体、数式など)。解釈とモデルは、おおよそ、1対1で対応する。ある解釈に対して、それを具体的に示すモデルがある。「モデル」(model)と「近似」(approximation)は、ほぼ同義語として使われる場合がある。
引用:https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%87%E3%83%AB_(%E8%87%AA%E7%84%B6%E7%A7%91%E5%AD%A6)
つまり、対象の事物を理解しやすい形で表現した結果現れるものが『モデル』だと理解することができます。
よって、理解したいやり方・興味のある側面次第では、モデルに取り込むのが適切なものもあれば、不要なものもあります。
従って、ドメインに関する知識を深めていく過程で、自分達が開発するシステムに不要な概念がモデルに現れてくれば、そういったものを削るなりして、洗練させていくことが必要になります。
5. ブレインストーミングと実験を行う。
業務上の概念を整理し、UI や DBの操作もないシンプルなロジックのみのコードを書くことができたりします。
そういったシンプルなコードを書いて実験を行ったり、そのシンプルなコードをもとにドメインエキスパートと会話を進めることを積極的に行うことを推奨しています。3
実際どんな感じでモデリングするの?
ここまでの説明でなんとなくわかるかなと思いますが、ドメイン駆動設計の初歩は、「開発者とドメインエキスパートの会話」です。
これがとても重要。
やれリポジトリだの、やれサービスがどうの、やれアーキテクチャだの言ってるうちは素人(自戒)。
しっかりドメインモデルを確立すること、その過程でドメインそのものについての理解を深めること、そしてチーム内での共通認識・共通言語(ユビキタス言語)を作っていくことが、DDD の核です。
例えば本書では以下のような具体例が挙げられています:
例: プリント基盤(PCB) 4 の設計をするソフトウェアの開発
開発者:「あいつら(=エキスパート)の言ってること訳わかんね〜、電子技術者の資格でも取れってか???」
開発者:「そういえばあいつら、『ネット』ってもののことについてなんか言ってたな」
ネットとは、PCB上で任意の数のコンポーネントに接続できる導体のことということが後で分かった。
接続されたコンポーネントに電気信号を送ることができるらしい。
そこで最初のモデルを得る:
エキスパート:「コンポーネントは、チップじゃなくてもいいんです」
開発者:「では単に『コンポーネント』と言った方がいいですか?」
エキスパート:「我々は『コンポーネントインスタンス』と呼んでいます」→お!ユビキタス言語が出来つつあるね。
エキスパート:「なんでこの図では、コンポーネントもチップも全て四角で書くのですか?」
開発者:「なるほど。実は私たちはクラス図といったものに親しんでいて、それに近い書き方をしております。〜〜〜〜」
(開発者とエキスパートが同じ言語で会話できるよう、図の書き方も大事な共通認識。)
エキスパート:「信号がレフデスに届くだけでは不十分で、ピンも必要です」
開発者:「レフデスってなに?」
エキスパート:「コンポーネントインスタンスと同じです」→こういう『違う言語』を使っちゃうと、意思疎通ができなくなる。
エキスパート:「全てのネットにはトポロジーがあります」
色々ドメイン(ここではPCB基盤の設計)についてのやりとりがあった後、最終的に以下のようなクラス図にまで落とし込んだ:
上記の例で大事だったこと
以下の点が大事だったのだろうと個人的には感じました:
- 開発者がドメインの理解から逃げなかったこと。
- エキスパートと会話をすすめたこと。
- 不明点を質問し、共通認識を作り出したこと。
- 逆に開発者側の専門知識をきちんと説明して、共通認識を強固にしたこと。
- シンプルな図に落とし込んだこと。
ドメイン駆動設計の主要概念
ここではドメイン駆動設計を実装に結びつける上でも重要な以下の概念の概要を説明します:
- Entity
- Value Object
- Service
- Aggregation, Repository
Entity(エンティティ)
よく Value Object と比較されて理解される概念です。
エンティティとは、何かしらの『同一性』によって他のオブジェクトと区別されるような概念を指します。
もっと言うと、「そのオブジェクトの属性が変わっても同一だと見做せる概念」のことをエンティティと言います。
多分これは実例がありふれています。
例えば、僕という人間は、「RikiyaOta」という名前を持っていますが、仮に今度改名して「大田マルクス闘莉王」という名前になったとして、"僕"という人間は、異なる人間になったでしょうか?
おそらく違うはずです。"僕"は変わらず"僕"です。
そのように、属性が変わったとしてもある"同一性(identity)"によって区別されるような概念のことをエンティティと呼びます。
実際のプロダクトでは、何かしらの識別子として ID を割り当てることが多いですね。
その ID だけを比較して、ID が一致するならば同じオブジェクトである!と識別する類の概念をエンティティと呼びます。
なんで"エンティティ"とわざわざ名前をつけるのか?
個人的な感想ですが、『ライフサイクル』を意識する必要があるからかなと思っています。
例えばシステムのユーザーのというエンティティがあったとして、このユーザーは名前を変えるし、何か属性に基づいたアクションをするし、料金プランを変えるかもしれないし、どんな操作をしたか監査ログを扱う必要があるかもしれないし、そして何より、それらのイベントを追跡できるようにしなければいけないかもしれない。
こうしたことは、ドメインに溢れているのではないかと思います。
そのために、同一性を担保するようなものをドメインの中に見つけてシステムに落とし込むか、なければシステム側で対応する必要があるかと思います。
そういったシーンはありふれているので、ある種の"パターン"と皆して名前をつけているのかなと理解しています。
Value Object(値オブジェクト)
エンティティと対称的なものが値オブジェクトです。
エンティティとは違い、そのオブジェクトの属性だけで区別されるオブジェクトのことを値オブジェクトと呼びます。
例えば、弊社では kintone の『ドメイン』を扱うシステムが多くあります。
これって、エンティティでしょうか?値オブジェクトでしょうか?
僕は、これは値オブジェクトではないかと思います。
理由は、{SUBDOMAIN}.cybozu.com
という何かしら構造を持った値であり、なおかつ、この文字列の比較だけで区別をする・しなければならない概念だからです。5
こういった、ドメインモデルに現れる固有の概念で、同一性を持たないようなものを値オブジェクトと呼びます。
値オブジェクトは、それを表すクラスや、あるいは静的型を定義することで、ドメインを豊かに表現するコーディングをすることができます。プリミティブ型ばかり使うコーディングとは一線を画します。
値オブジェクトの特性
- イミュータブル
- コピーが容易
この2つになってくるかなと思います。
イミュータブルであることのメリットというか、デメリットは、例えばこんな(意味のない)例を考えてみます:
ある仮想的なプログラム言語があったとして、この言語では、全ての値に対して.value()
というメソッドがあるとします。
以下のようなイメージです。
"hoge".value()
// -> "hoge"
5.value()
// -> 5
もし値が変更可能でコピーできなくて、こんなふうに困るのではないでしょうか?
5.set(4)
5.value()
// -> 4
これだとかなり突飛な例に見えるかもしれませんが、普通に javascript だとしても、
class FiveDollar {
value = 5
}
というクラスがあったとして、
const fiveDollar = new FiveDollar();
fiveDollar.value = 4;
というように書き換えられてしまったら、FiveDollar
という値オブジェクトが誤解を招く概念に写りはしないでしょうか?
逆にいうと、普段触っているようなプリミティブな型の値たちと同じように自分達が定義した値オブジェクトをイミュータブルに保つと、プログラム中で5
という値の代わりにFiveDollar
という意味のある概念を操作するプログラムを書くことができます。
これは、ドメインモデルを実装と結びつけるのに、とても役立つ考え方だと個人的には思います。
ですので、ぜひドメインに現れる概念を見つけ、しっかりとコードに落とし込んでいただけたらと思います。
よくチームメンバーで会話に現れるような名詞や出来事(ドメインイベント)なんかを意識することが、モデリングの最初の1歩だと思います。
特に開発者視点で言うと、実装中に明らかに意識している概念や条件があるのに、それらがクラスや型として明示的に表現されていないようなものはチャンスな気がします。
Service(サービス)
まず本書の第5章から引用します:
ドメインから生まれる概念の中には、オフジェクトとしてモデル化すると不自然なものもある。こうしたドメインで必要な機能をエンティティや値オブジェクトの責務として押し付けると、モデルに基づくオブジェクトの定義を歪めるか、意味のない不自然なオブジェクトを追加することになる。
イメージとしては、ドメインに現れる『動詞』に着目してモデリングされるものだと思います。
つまり、エンティティや値オブジェクトを"操作"するような業務上の活動なんかを表現するモデリングなのかなと思います。
ありがちな例として、ユーザー(User)がそれぞれ1つの銀行口座(Account)を持っていて、その口座間での送金をするシーンを考えてみましょう。
この『送金する』というドメインの概念は、どう表現するのが良いのでしょう?
User, Account はエンティティだとして、それぞれの振る舞い(=メソッド)として持つのが良いのでしょうか?
class User {
sendMoney() {
}
}
class Account {
send() {
}
}
account.send()
は個人的には「え、ありじゃない?」とも思ったのですが、口座間でのお金のやり取りは、送信元の口座の残高を減らし、送金先の口座の残高を増やすという手続きから成り立っていることを考えると、1つの『銀行口座』が持つ振る舞いとしてはやり過ぎている気がしています。
もちろん、User という人間が上記の振る舞いをするというのも頷けません。
こういった、ドメインモデルに現れる操作を表現するときに、サービスという概念を定義する選択肢があります。
注意:サービスは濫用しない
サービスは便利です。
便利すぎて、何でもかんでもここに持ち込みたくなってきてしまいます。
ですが、モデルに忠実になることがドメイン駆動設計の肝です。
Aggregation(集約), Repository(リポジトリ)
ドメインモデルの中には、ある種のまとまりというか、グループ的なものが存在することがあります。
特にそれらのグループの中に、強い整合性が要求される時、集約という考え方が有用になります。
Aggregation
集約とは、ドメインにおいてある種の整合性・不変条件を満たすべきまとまりとその境界のことを指します。
例えば、車という概念をモデリングするとき、以下のような要求があるとしましょう:
- 車は車両登録番号という識別子を持っていて、これはグローバルに一意。
- 車は最大で4つタイヤを装着することができる。
- 各タイヤの走行距離を管理する必要がある。
この時に、果たして『タイヤ』というものはエンティティとみなすべきだろうか?
例えば、ある車に着目した時、前側の2つのタイヤは別々のものだと認識することには意味がある。なぜなら走行距離や摩耗具合が違うかもしれず、それは交換時期の違いに直結するかもしれない6。
一方で、ある車Aからしてみたら、自動車整備工場に備えてあるタイヤと、自分が身につけているタイヤを区別する必要はあるだろうか?別に興味ないのでは?
つまり、タイヤという概念を区別するのは、車というコンテキストの中だけでいい可能性がある。(もちろん、業務のルールによってはそうとは限らない)。
もっというと、タイヤの情報をキーにして車を調べるということは無いとしよう。
であれば、タイヤという概念は、車という親になるような概念を通してのみしか、このドメインには現れないということになる。
ここには、ある種の整合性が存在する可能性がある。
Relational Database を考えると、タイヤテーブルのデータを更新するときは、必ず車テーブルのデータを更新することになるかもしれない。
そうした、整合性・不変条件を保つ範囲のことを集約と呼びます。
ある概念がローカルに成立するものなのか、それともグローバルに意味を持つものなのかを考えることが重要かなと思っています。
じゃあなんでこんなこと考えるのが嬉しいの?は次の『Repository』で。
Repository
Repository は、倉庫とかそんな感じの意味があります。
リポジトリは、ドメインオブジェクト(エンティティとか)の永続化の責務を持ちます。さらに、集約における整合性の保証もリポジトリが担当します。このため、リポジトリは『集約単位で』実装することが望ましいです。
とっても雑に言うと、DB に保存したりデータを読んだり、API を叩いてデータを取得・更新するような処理をするイメージです。
それだけなら、Data Access Object (DAO) という実装パターンと変わらないのですが、Repository はあくまでドメインオブジェクトを操作することが責務なので、入力としてはドメインオブジェクトを受け取るし、返す値もドメインオブジェクトであるべきです。つまり、DBから取った値そのままとか、APIレスポンスそのままみたいなことをやるのは望ましくないと考えます。
それをしてしまうと、ドメインモデルによる会話ではなく、技術駆動な会話になってしまうからです。
具体的な技術・インフラストラクチャをクライアントから隠蔽することも、リポジトリの大事な責務だと考えています。
補足:Factory
コーディングにおいて、複雑なクラスのインスタンスをメモリ上に生成する責務を担うのが Factory と呼ばれるものです。
Repository はデータの永続化・復元の責務、Factory はメモリ上でのオブジェクトの生成の責務という、似ているが異なる役割があります。
なぜオブジェクト指向なのだろう?
ここまで、モデルと実装を結びつけようという話をしてきました。
このことを高度に実現しようと思うと、モデルを適切に実装に紐づけるようなプログラミングパラダイムが必要不可欠になります。
オブジェクト指向言語は、プログラマに『オブジェクト指向』というパラダイムとそのメモリ上での表現方法(つまり実装方法)を提供してくれます。
一方で手続型の言語では、モデルの概念を表現する手段に乏しいです。
そうなると、開発者とエキスパートが異なる言語を使ってコミュニケーションを取らざるを得なくなります。双方の発言に翻訳が必要になってしまいます。
オブジェクト指向を使いこなし、モデルのメモリ上での表現を高度に実現し、エキスパートと円滑にコミュニケーションを取ることができる状態を目指したいものですね。
その目標のために、オブジェクト指向は、もしかしたら唯一の正解ではないのかもしれませんが、現実的に有用なアプローチを与えてくれているのだろうと理解しています。
まとめ
タラタラと文字をしたためました。
なんとなくドメイン駆動設計のお気持ちが伝わって、もっと具体的な実装を見たときに話がつながってくると嬉しいです。
参考文献
- エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践) 翔泳社 エリック・エヴァンス (著), 今関 剛 (監修), 和智 右桂 (翻訳), 牧野 祐子 (翻訳)
-
例えばドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本や現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法はとても最初の1歩に良い気がします。特に2つ目の増田さんの本はとても好みの書籍です。 ↩
-
データ構造設計の話として正規化理論を持ち出しましたが、DDD自体はメモリ上のアプリケーションの設計を主軸にしたものだと理解しています。 ↩
-
Domain Modeling Made Functionalという書籍があります。こちらは関数型モデリングを通してDDDを実践するという内容の書籍です。この本では、徹底的にドメインに現れるワークフローやプロセスを静的型でまずは表現することを推奨しています。型だけならば、ただの英語なので、ドメインエキスパートとも会話ができます。ドメインに対して理解が間違っていれば、その場で修正ができます。業務について開発者とエキスパートが質の高いコミュニケーションが取れることを目指していきたいものです。 ↩
-
多分これはとても重要なことですが、『プリント基板ってなんやねん、という状態なのは当たり前だから、それを知ろうとするところから始めよう』です。ちなみにプリント基板はこんなものらしい。 ↩
-
実際には、(サブ)ドメインの値を変更することができるので、変更した途端に別会社として見なされるのが不都合だからエンティティだ!と主張したくなるかもなぁと思ったのですが、サブドメイン自体はやはり値オブジェクトで、"会社"をエンティティとしてモデリングした方が自然な気がしています。"会社"エンティティの1つの属性として、サブドメインという"値オブジェクト"を保持しているようなイメージ。 ↩
-
これ書きがなら「正直、車のことなぁんにも知らないんだよなぁ」と困っちゃいました。 ↩