社内勉強会で使用したドメイン駆動設計のスライドです
ドメイン駆動設計(DDD)入門2020/11
View Slide
●プロジェクト- バックエンド担当- WEBアプリ基盤サーバーAPI開発/運用- WEBアプリインフラ構築/運用●興味- ドメイン駆動設計- CI/CD●趣味- 読書、カフェ巡り自己紹介
1. イントロダクション2. ドメイン駆動設計(DDD)とはなんなのか3. DDDで使われる専門用語たち4. DDDとアーキテクチャの関係5. DDDと付き合っていく目次
1.イントロダクション
ドメイン駆動設計(DDD)を知ったきっかけこのプロジェクトのアプリは「ドメイン駆動」に沿って作られてるよ(チームリーダー)なるほどですね! ←ぼく
ネットで調べてみたhttps://www.amazon.co.jp/dp/479813161X/ref=cm_sw_em_r_mt_dp_XBoRFb1D9HGH7
書籍購入申請にてこの本は、基本的なソフトウェアデザインパターンの経験と十分な理解を持つ中堅から上級のエンジニアを対象としています。(社内リード)なるほどですね! ←ぼく
書店にて
なにもわからない。。。
Wikipedia contributors. "ダニング=クルーガー効果." Wikipedia. Wikipedia, 11 Oct. 2020. Web. 12 Nov. 2020.1年を通して、コードや書籍を通して得た知見をお話しします
2.ドメイン駆動設計(DDD)とはなんなのか
ドメイン駆動設計(DDD)におけるドメインとは、「プログラムを適用する対象となる領域」のこと!そもそもドメインって?
システムが対象とするモノや領域によって変化するそれってどういうこと?物流システム 車両管理システム貨物倉庫輸送手段車両 ドライバー企業
ドメイン駆動設計(DDD)とは、「ドメインの知識に焦点を当てた設計手法」のこと!つまり、、?
技術指向の開発者であればあるほど疎かにしやすいこのモデルを使えば、万事解決うーん。。実際の業務内容イマイチよく分かってないけど、トレンドのこの技術使お
そもそもソフトウェアって、「利用者の抱えている問題の解決」←大前提
とすると、開発者は、「利用者が直面している問題」を正しく理解しなければいけないつまり、「利用者を取り巻く環境(ドメイン)に向き合う」必要がある
言い換えると、ドメインの概念や事象を理解して、問題解決に役立つものを抽出するそして、「抽出した知識を、ソフトウェアに反映する」
それって、、結局のところ、ドメイン駆動設計(DDD)ってのは当たり前を当たり前に実践するための開発手法という側面もある
3.DDDで使われる専門用語たち
ドメインモデル車両管理システムにおいて、車両とは「走行記録を送信する」ことを表現できれば十分です。車両には他にも、「レバーを操作するとワイパーが出る」「ボタンを押すとサイドミラーが開く」といった操作もありますが、システムにおいて表現する必要がありません。
ドメインモデル抽象的ななにか事象、概念の洗い出し洗い出した要素の抽象化(モデリング)ドメインモデル(抽象的ななにか)とは、「ドメインから事象、概念を抽象化して得られるモデル」ドメイン
ドメインモデル抽象的ななにかドメインモデルソースコード(ドメインオブジェクト)役立つモデルを選別(すべてを実装するわけではない)実装!ドメインオブジェクトとは、「ドメインモデルをソフトウェアとして表現したもの」
ドメインモデル抽象的ななにかドメインモデルソースコード(ドメインオブジェクト)ドメインドメインモデルを媒介して、ドメインの変更が連鎖的にソースコードへ反映される仕組みができる
ドメインモデル(知識)を表現するここからは、コードもちょこちょこ挟んでいきます抽象的ななにかドメインモデル値オブジェクトエンティティドメインサービスドメインオブジェクト
3−1.値オブジェクト
type fullname struct {firstName stringlastName string}type fullname struct {firstName namelastName name}どっちも一緒?firstName, lastNameはstring型だね!えーと、、name型です(そんな型なくね?)
type fullname struct {firstName namelastName name}値オブジェクト値オブジェクトではname型を定義します!値オブジェクトとは、「システム固有の値を表現するためのドメインオブジェクト」
type name string //name型を定義してますfunc newName(value string) (name, error) {// 文字列が「hoge」であるか、初期化のタイミングでチェックできる!if value != "hoge" {return "", errors.New("value is not hoge")}return name(value), nil}固有の型を定義するメリットは?固有の型を定義することで、システムで不要な値を初期化するタイミングで削ぎ落とせる!
type name string //name型を定義してますfunc newName(value string) (name, error) {// 文字列が0文字以上か// 文字列が20字以下か// 文字列に英数字が含まれていないかreturn name(value), nil}つまり?ソースコードがドキュメントになる!→自己文書化を推進する
そもそも「値」とは?- 1, 2, 3, … → 数字- A, B, C, … → 文字- りんご, みかん, ぶどう →文字列これらには、共通する性質が3つあります。- 不変- 交換が可能- 等価性によって比較される値オブジェクトは「システム固有の”値”」なので、この3つの性質が適用されます。
不変とは?値が「可変」だとすると、、"こんにちは".changeTo("今何時?") // こんなメソッドはないfmt.Println("こんにちは")「こんにちは」という文字列を出力しようとしたのに、「今何時?」が出力される「こんにちは」に含まれる別の意味を考えて混乱することになりかねない、、(言葉のウラをよむ??)→ 値は「不変」が良さそうですね
交換が可能って?直感的に、普段以下のような「代入」を書いてることでしょうvar hoge string = "こんにちは"hoge = "今何時?"何気なく使っている「代入」ですが、無意識にも「変数に格納された値を変更(交換)」しているのです(驚)※「こんにちは」という文字列を「今何時?」という文字列に上書きしているのではなく、それぞれ格納されている文字列を交換している ←この感覚をまんま値オブジェクトでも適用します
つまり、、?値オブジェクト、「不変」かつ「交換可能」を適用しないと、、var hogeUser user = user{id: 0, name: "hoge"}hogeUser.name = "fuga"確かにこのコードは正常に動作しますし、なんならよく見慣れた方もいるはず※便宜上、idとnameをint/string型にしていますですが、残念ながらこのコードは、「値は不変」というルールを適用してないので「値オブジェクト」ではありません。→やりがちなパターンです。。
つまり、、?値オブジェクト、「不変」かつ「交換可能」を適用すると、、var hogeUser user = user{id: 0, name: "hoge"}hogeUser = user{id: 1, name: "fuga"}新しいオブジェクトを代入していますね!「値」と同じように、「不変」のオブジェクトを「交換」しているので、これは値オブジェクトです!
メリットある?var hogeUser user = user{id: 0, name: "hoge"}// 途方もない処理(hogeUserの中身を変えたりもしてる)AllowUser(hogeUser.id, hogeUser.name)仮に、userのパラメータにmailAddressなんかがあると、、→ユーザの個人情報が別のユーザに送信されるといったバグになりかねない。。つまり、状態を変更できないようにするというのは、それだけでバグを起こしにくいようにすることができる!
3−2.エンティティ
どっちも一緒?名前:たかし(仮名)年齢:20歳2020年名前:たかし(仮名)年齢:70歳2070年〜50年後〜
ソフトウェアにおいて、属性(年齢とか身長とか体重など)によって区別されないオブジェクトを、同一性(identify)によって識別します。→ 要するにIDで管理するということ!どうやって区別するの?
type user struct {id idfullname fullname}エンティティ値オブジェクトと同じように、固有のid型、fullname型を定義するよ!エンティティとは、「属性でなく同一性によって識別されるドメインオブジェクト」
値オブジェクトと何が違うの?type fullname struct {firstName namelastName name}type user struct {id idfullname fullname}値オブジェクトは、firstName, lastNameが同じであれば、まったく同じオブジェクト→属性によって識別されるエンティティは、fullnameが同じでも、まったく同じオブジェクトとは限らない→属性によって識別されない
エンティティの性質をまとめると値オブジェクト エンティティオブジェクト 不変 可変属性 区別される 区別されない同一性(identify) ない ある
3−3.ドメインサービス
そもそもサービスとは色々な文脈でつかわる言葉ですが、- 相手のために尽くすこと- 奉仕、勤務、接客、提供の意味で使われることが多いと思います。ドメイン駆動設計におけるサービス(ドメインサービス)とは、「値オブジェクトやエンティティに記述すると不自然な振る舞いを解決するオブジェクト」
不自然な振る舞い?var hogeUser user = user{id: 0, name: "hoge"}// ユーザーの重複チェックvar isExist, _ := hogeUser.IsExists(hogeUser)hogeUserがhogeUserの存在チェックをする?※便宜上、idとnameをint/string型にしています一見すると普通のコードに見えますが、コードから読み取れるのは「自身と同じオブジェクトが存在するかを自身に問い合わせて確認している」という情報のみで混乱を招く恐れがあります
???ああ、もちろんだぜもう一人のボクぼくぼくは存在するだろうか?「我思う、故に我あり」
不自然にならないのははい、登録されております役所職員ぼくぼくは存在するだろうか?現実世界の役割をドメインサービスが担う
ドメインサービスを活用するとvar userService := NewUserService()var hogeUser user = user{id: 0, name: "hoge"}// ユーザーの重複チェックvar isExist, _ := userService.IsExists(hogeUser)ここから分かることは、ドメインサービス(userService)というロジックを通してエンティティの存在チェックを行っているということです!つまり、ドメインサービスとは「エンティティ(値オブジェクト)を利用して、ビジネスルールを記述するオブジェクト」と言うことができます。
休憩
4.DDDとアーキテクチャの関係
ぼくの誤解「ドメイン駆動設計」ってアーキテクチャがあって、その他にも- レイヤードアーキテクチャ- クリーンアーキテクチャっていうアーキテクチャがあるんだよね! ←ぼく
ぼくの誤解「ドメイン駆動設計」ってアーキテクチャがあって、その他にも→ない- レイヤードアーキテクチャ- クリーンアーキテクチャっていうアーキテクチャがあるんだよね!→ある ←ぼく「ドメイン駆動設計」はアーキテクチャではない!
アーキテクチャとは、「方針」ですつまり、開発者がアーキテクチャに従うことによって、「何をどこに書くのか」に振り回されないための決まりごとドメイン駆動設計とセットで出てくるアーキテクチャ- レイヤードアーキテクチャ- ヘキサゴナルアーキテクチャ- クリーンアーキテクチャアーキテクチャ
先ほども述べた通り、ドメイン駆動設計がアーキテクチャに求めることは、「何をどこに書くのか」→「ドメインを隔離すること」が重要であるため、必ずいずれかのアーキテクチャに従う必要はありません!ただし、いずれかのアーキテクチャに従ったからといって、ドメイン駆動設計を実践したことにはなりません!アーキテクチャについての注意
伝統的なアーキテクチャ矢印の方向に依存することを許可する※上位のレイヤーから下位のレイヤーをコントロールするようなイメージドメイン層が一番重要!他の層からドメインオブジェクトを隔離する事が目的レイヤードアーキテクチャユーザーインターフェース層アプリケーション層ドメイン層インフラストラクチャ層
MVCとの違い(個人的意見)ユーザーインターフェース層アプリケーション層ドメイン層インフラストラクチャ層ModelControllerView- Modelが肥大化しがち- 役割ぼやけがち
よく見るやつビジネスルール(ロジック)を中心にするのがコンセプト依存性を内側に向けているクリーンアーキテクチャhttps://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
レイヤードアーキテクチャの場合ユーザーインターフェース層アプリケーション層ドメイン層インフラストラクチャ層レイヤードアーキテクチャの場合、ドメイン層がインフラストラクチャ層に依存している↓インフラストラクチャ層が変更される場合、依存しているドメイン層まで変更が入ってしまう
クリーンアーキテクチャではドメイン層インフラストラクチャ層ドメイン層はデータアクセスインターフェースに依存するようになる↓こうする事でインフラストラクチャ層へ変更があったとしてもドメイン層への影響はない何が嬉しいかと言うと、ドメイン層がデータを保存する場所(RDSかDynamoDBかRedisか)といったインフラ部分を気にする事がなくなるこれを「依存性逆転の原則」と呼びます。データアクセスインターフェースアプリケーション層ユーザーインターフェース層
5.DDDと付き合っていく
ドメイン駆動設計は決して開発者だけでは実現しませんこれまで紹介したパターンやアーキテクチャを導入する事でそれっぽく作ることはできます → 「軽量DDD」と呼ばれますドメインの本質に向き合うための手段として以下の概念が存在します。- ドメインエキスパート- ユビキタス言語- 境界付けられたコンテキスト振り返ってみて
ドメインエキスパートドメインエキスパートとは、ドメインの実践者のこと!開発者はドメインエキスパートとのコミュニケーションが不可欠ですドメインエキスパート
ドメインエキスパートから見えている世界(ドメイン)がどういったものなのか知る必要があるドメインの概念を捻じ曲げないための共通の言葉が必要になってくるコミュニケーションを取るにはドメインエキスパートhelloへ、へロー
ユビキタス言語とは、「プロジェクトにおける共通言語」ドメインエキスパートと開発者はユビキタス言語で話し、また改良していく事が求められます。ユビキタス言語ドメインエキスパートhello読めるぞ!!
まとめ
DDDは、今後のエンジニアリングに対するスタンスを変えうるビジネスの問題を解決するために、ビジネスへの理解を深めビジネスを表現するビジネスとコードを結び付けて反復的な改良ができるような枠組みを作る「ソフトウェア開発にとっての”当たり前”を徹底しましょう」まとめ
参考成瀬 允宣. 「ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 」. 翔泳社, 2020 いらすとや.