ざっくりわかるドメイン駆動設計
2020-05-30
2020/5/29に会社の勉強会で発表した内容をブログとしてまとめなおす。
経緯
-
最近ちゃんと勉強しようと思ってnrslibさんのドメイン駆動設計入門を読み始めた
-
2月に参加したObject Oriented ConferenceでDDDに興味を持ち始めた
-
2020年5月29日時点の知識でドメイン駆動について会社の勉強会で発表した内容をブログとしてまとめる
ドメイン駆動設計=DDDとは
松岡さんのブログよりDomainlanguage.comの引用
ドメイン駆動設計の定義についてEric Evansはなんと言っているのか[DDD] - little hands’ lab
- ドメインの中核となる複雑さと機会に焦点を当てる
- これがもっとも重要な考え方。下はドメイン駆動設計で用いられる代表的なテクニック。
- ドメイン専門家とソフトウェア専門家のコラボレーションでモデルを探求する
- 明示的にそれらのモデルを表現するソフトウェアを書く
- 境界付けられたコンテキストの中のユビキタス言語で話す
ドメインとは
よくわからんけど領域が近そう🤔
勤怠システムのドメイン(領域)にはこのような概念がある
- 従業員
- 勤務時間
- 有給
テックタッチだと
- ガイド、ステップ
ソフトウェアの目的は問題の解決
- ドメインの概念を正確に理解し、問題の解決に役立つ知識をソフトウェアに反映することが重要
- ソフトウェアを開発する上で当たり前の行為とも言える
ドメイン駆動設計 = ドメインの知識に焦点を当てる設計手法
(なんでドメインって言うんだろうね…)
ドメインモデル、ドメインオブジェクトとは
- ドメインモデル:概念を理解し、問題解決に役立つ知識を抽出したもの(モデリング)
- ガイド:操作方法を教えてくれる
- ガイドには複数ステップがある
- タイトルが何文字まで
- リダイレクト設定がある
- ガイド:操作方法を教えてくれる
- ドメインオブジェクト:ドメインモデルをソフトウェアで動作するモジュールとして表現したもの
ドメイン駆動設計では、ドメインの概念・ドメインモデル・ドメインオブジェクトを行き来しながらドメインの理解・ドメインの実装を繰り返す
戦略と戦術
軍事用語の戦略と戦術
戦略:戦いに勝つために兵力を総合的・効果的に運用する方法で、大局的・長期的な視点で策定する計画手段
戦術:戦いに勝つための戦地での兵士の動かし方など、実行上の方策
DDDの戦略と戦術
戦略:ドメインを理解し、それをモデルに落とし込む
- ユビキタス言語
- ドメインモデリング
戦術:具体的な実装パターン
- エンティティ
- バリューオブジェクト
- リポジトリ
- アーキテクチャ
軽量DDDとは
- DDDの中で戦術のみ、実装パターンだけを取り入れる手法は軽量DDDと呼ばれている
- 軽量DDDだけなら開発チームだけで完結可能
- ネガティブに捉えると、DDDの本質はドメインに向き合うことなので戦略のほうが重要なのでこの状態はあまりよくない
- ポジティブに捉えると、DDDの導入として形から入ることは重要 ドメインに向き合うきっかけになる
ユビキタス言語
ステークホルダー間で認識の齟齬や翻訳にコストをかけないために共通言語を使う
ユビキタス=いつでもどこにでも存在する
IoTのことユビキタスコンピューティングって呼んでる時代あったよね
ドメインエキスパート「ユーザを登録する」
- ドメインの言語で話す
開発者「ユーザを新規保存する」
- 開発寄りの言葉を選びがち
ドメインエキスパートの言葉をそのまま使うということではない。
システムにもドメインにも寄り添ってユビキタス言語を作っていく
モデリング
一般的な昔ながらの方法
-
ユースケース図
- アクターから見たアプリケーションのユースケースを図にしたもの
- 一般的なUMLのユースケース図と同じ
-
ドメインモデル図
- クラス図の簡易版
- 代表的な属性だけ記述してメソッドは不要
- ドメイン知識(業務のルール・制約)を吹き出しで記述する
最近はRDRA2.0という手法が流行っているみたい
ドメインオブジェクト
ValueObject 値オブジェクト
- プログラミング言語に用意されているプリミティブな値をそのまま使うのではなく、システム固有の値を表すオブジェクトにしたもの
- 使うことでドメインオブジェクトの語彙を増やすことができる
- 例えば
- 氏名
- 電話番号
- ガイドタイトル
class FullName {
constructor(fistName: string, lastName: string) {
this.firstName = firstName
this.lastName = lastName
}
equals(fullName: FullName) {
return this.firstName === fullName.firstName && this.lastName === fullName.lastName
}
}
- ValueObjectの性質
- 不変である
- changeNameのような操作はない
- 交換が可能である
- 属性を変化するときは再代入しなおすことで表現する
- 等価性によって比較される
- 全ての属性が同じである = 等価
- 不変である
Entity エンティティ
- 値オブジェクトもエンティティもドメインオブジェクトだが、大きな違いは同一性を持っているかどうか
class User {
id: string
fullName: FullName
constructor(fullName: FullName) {
this.id = createId()
this.fullName = fullName
}
changeName(fullName: FullName) {
this.fullName = fullName
// return new User(fullName)
}
equals(user: User) {
return this.id === user.id
}
}
- Entityの性質
- 可変である(コードの書き方の好みによって違いそう)
- 値オブジェクトは不変だったがエンティティは属性が変わりうる
- 同じ属性であっても区別される
- 同姓同名の人がいるように、同じ属性をもっていても別の個体だと認識する必要がある
- 同一性を持つ
- ユーザ名が変わったとしても同じユーザとしてみなされる
- 可変である(コードの書き方の好みによって違いそう)
リポジトリ
- ドメインオブジェクトの永続化が責務
- リポジトリとして永続化、復元を管理するだけでとてもシンプルなコードになる
- 基本的にはfind, save, deleteの3関数になるパターンが多い
- IUserRepositoryというインターフェイスを作っておくことで、IUserRepositoryに従ったUserMemoryRepositoryを作ればテストしやすくなる
class UserRepository implements IUserRepository{
find(name: string) {
// SELECT * FROM USERS WHERE name....
}
save(user: User) {}
delete(user: User) {}
}
なぜドメイン駆動でリポジトリが出てくる?
- たしかにドメインとはちょっと離れた存在
- ドメインオブジェクト単位でリポジトリを管理することでドメインがコードの中で際立つ
- ガイドの保存はGuideRepositoryでやってるはず
- Repositoryの中でガイドの保存部分を探して…
アプリケーションサービス
-
ドメインオブジェクトを協調させてユースケースを実現する
-
サービスは状態を持たない
-
例
class UserService { repository: IUserRepository constructor(repository: IUserRepository) { this.repository = repository } register(name: string) { // ユーザ名の制約に引っかかると例外を投げる const user = new User(name) if (await this.repository.find(name)) { throw new Error('重複してます') } this.repository.save(user) } }
アーキテクチャ
- DDDでよく登場するアーキテクチャは3層+ドメインの構成が基本
- Presentation(UI, Controller)
- Service(Usecase)
- Infrastructure (Repository, DB, API
- Domain
- アーキテクチャの役目
- レイヤーわけしてどこに何を書くべきかという方針を定める
- ソフトウェア特有の事情からドメインオブジェクトを防衛する
- これらのアーキテクチャがあるが共通点として、どれもドメインオブジェクトを防衛するための仕組みを考えている
- レイヤードアーキテクチャ
- ヘキサゴナルアーキテクチャ
- クリーンアーキテクチャ
気持ち
- 要件定義→仕様→設計→実装 ⇒ ドメイン↔ドメインモデル↔コード という変化
- それぞれの境界の継ぎ目をなくす
- 反復的に繰り返すことで洗練していく
- DDDをやっていくには、全員でドメインに向き合う意思を統一し、やっていくための仕組みを作り、反復的に繰り返す必要がある
- 全てはソフトウェアの発展性を良い状態で維持するため