ドメイン駆動って?
久しぶりに記事を書きます。
先日、とある企業の1ヶ月くらいのインターンに参加させてもらってそこでGo言語を学習してAPIの開発をしてきました。その期間での私のテーマの1つとしてドメイン駆動開発について理解することを掲げて取り組みました。まだまだ理解できたとは到底言えないのですが、学習してきたことをアプトプットしておこうと思います。
ドメイン駆動開発を学習してまだ1ヶ月程度の学生の浅い知識なので間違えている部分やおかしな点はご容赦ください。
できるだけ簡単な言葉で言い換えて説明をしているのであまり厳密な定義ではないので気をつけてください。
なんとなくドメイン駆動開発ってこんな感じという内容を説明できるレベルに到達できるレベル感の内容で書いたつもりです。
対象読者
- ドメイン駆動って何?みたいな状態の方
- なんとなく概念は知っているけどどんなふうに実装したらいいのかわからないという方
参考にした文献
この記事は以下の本を主に学習を進めてその内容を自分で噛み砕いてまとめています。
この本は本当にわかりやすい言葉で説明されていておすすめです!実際にそこまで分厚いものではないので2週間程度で読み切れますのでおすすめです!!
それでは行きましょうー
そもそもドメインって?
「ドメイン駆動開発」の文脈で使用されるドメインという言葉には以下のような意味が含まれています。
ドメインとはそのシステムが解決しようとしているビジネスや課題などに関する事柄
つまり、ドメイン駆動開発とはそのシステム特有の機能や、ビジネスの内容をコードの落とし込んで開発をしていきましょうみたいなコンセプトのようです。
じゃあ実際のドメインとして例を見ていきましょう。
- 銀行のドメイン
- 口座
- 取引
- 預金
- 学校のドメイン
- 生徒、学生
- 授業
- 成績
- 先生、教授
- 医療のドメイン
- 患者
- 診察
- 予約
などのようにビジネス領域やその問題領域を指しています。
ドメインはそれぞれの領域の核心的な部分であり、その中に含まれている概念やルールがソフトウェア(コード)の中に反映されていうという流れです。
ドメイン駆動を実践するためにどのように設計するのか?
先述した本の中では以下かのような設計をすることでドメイン駆動設計のパターンを紹介しています。
- 知識を表現するパターン
- 値オブジェクト
- エンティティ
- ドメインサービス
- アプリケーションを実現するためのパターン
- リポジトリ
- アプリケーションサービス
- ファクトリ
- 知識を表現するより発展的なパターン
- 集約
- 仕様
一個ずつ見ていきましょう
知識を表現するパターン
値オブジェクト
値オブジェクトとはドメイン固有の概念などを値として表現するパターンです。
また、不変性の性質も持っています。
これは結構イメージがつきやすいと思います。
例えば住所などです。仮に住所が同じであればそれは同じ住所として捉えられますね(当たり前のことですが、次のエンティティを読むと何を言っているのかわかるかと思います。)
エンティティ
エンティティとは値オブジェクトとの違いが分かりにくいのですが、ライフサイクルにその特徴があります。エンティティはそれが持つ識別子によって追跡されて、その状態は変化することがあります。
例えばユーザーの話です。
ユーザーをアプリケーションの中で管理する際には必ずuser_id
のようなもので追跡をします。このユーザーの住所などは変更できます。
仮に名前が田中というユーザーが二人いたとしてもその二人のidが異なっていたらそのユーザーは異なったユーザーであると認識できますよね?
これが値オブジェクトとの違いです。
ドメインサービス
ここでは値オブジェクトやエンティティではうまく表現できない知識を取り扱います。もう少し言い換えるとエンティティや値オブジェクトに関連するがエンティティ自身の責任にはならない処理をまとめます。
いい具体例がなかったのでchat gptで生成させてみました
シナリオ
オンラインショッピングサイトで、「注文(Order)」と「在庫(Inventory)」という2つのエンティティがあります。購入処理を行う際、注文が在庫を消費する必要があるというビジネスロジックがあります。このロジックは、注文や在庫のエンティティに直接組み込むことは避けるべきです。代わりに、ドメインサービスを作成して、注文と在庫の協調処理を担当させます。
つまり、複数のエンティティなどに関連している処理などをこのドメインサービスに集約させるのですね。自分自身もどの処理をドメインサービスに持たせるべきなのかという適切な責務の棲み分けは実践できていない自覚があるので頑張って勉強していかないといけないと思いました。
知識を表現するパターンのまとめ
ここまで見てきたものだけでアプリケーションが作成できるかといったら絶対にできません。
これまで紹介してきた項目は全てドメインの内容がコードにそのまま落とし込まれているだけです。
それではアプリケーションが成り立たないのでこの次の項目も見ていきましょう
アプリケーションを実現するパターン
リポジトリ
リポジトリとはデータの永続化をする部分を抽象化しているです。データの永続化を行うことでアプリケーションはものすごく柔軟な設計ができるようになります。(後述します)
つまり、リポジトリはDBなどの操作を抽象化するものであるということです。
アプリケーション
ここでのアプリケーションはみなさんがよく知っているアプリケーションという言葉の通りです。
これまでの機能を集めてアプリケーションとして組み立てるのです。
ファクトリ
上の項目のアプリケーションまでで実際にアプリとして動作することが確認できたら次はファクトリです。ファクトリとはオブジェクトを作成するなどの複雑な処理をファクトリで行うことで他の機能(アプリケーションやリポジトリ)などは自身の責務のみに集中するだけで良くなるという利点があります。
この説明では少し抽象的なので例を用いてみましょう。
複雑なドメインの例として、大規模サービスの注文を例にします。
大規模なサービスでは注文というオブジェクト一つをとっても
- 誰が注文したのか?
- 在庫はどのようになっているのか?
- 店は注文を承っているのか?
- 発送されたのか?
- 在庫はあるのか?
などのオブジェクトの生成処理だけでも非常に多くの処理を行う必要があります。
これらの処理を分離することで他の部分はファクトリに関する知識を持つ必要がなくなるため、コードの保守性などの観点からも嬉しいのです。
アプリケーションを実現するパターンまとめ
ここまでアプリケーションを実現するパターンを見てきました。
先述した知識を表現するパターンの内容を利用してアプリケーションとして実装するためのものというイメージですね。これらの内容を抑えると実装の際にどのようなディレクトリの構成にするのかなどのイメージも湧いてきますので頭に入れておくといいと思います!
知識を表現する発展的なパターン
正直ここは対象としている読者の方のレベルや私自身そこまで理解できていないので(すみません)本の内容をそのまま書いて終わりにしようと思います。
集約
整合性を保つ境界です。値オブジェクトやエンティティといったドメインオブジェクトを束ねて複雑なドメインの概念を表現します。
仕様
オブジェクトの評価をします。オブジェクトがある特定の条件下にあるかを判定する評価の振る舞いをそのオブジェクト自身に実装するとオブジェクトが多くの評価の振る舞いに塗れて煩雑になってしまいます。仕様を適用することでオブジェクトの評価をモジュールとしてうまく評価できます。
じゃあ、実際にどうやって実装するの?
ここまで書いてきたことはどれも概念的な話ばかりでどんなふうに実装したらいいのかわからないですよね...
私も最初ここまでの学習を進めた時点でなかなか理解できずに困りました。それではどんなここまでの知識を活かして実際のAPIの構成を見ていきましょう。
しかし、構成を見ていく前に絶対に押さえておかないといけない概念が1つあります。
それは依存関係逆転の法則です。
依存関係逆転の法則
依存関係逆転の法則の定義を以下に示します。
- 上位レベルのモジュールは下位レベルのモジュールに依存してはならない、どちらのモジュールも抽象に依存しなければならない
- 抽象は実装の詳細に依存してはならない。実装の詳細が抽象に依存しなけらばならない
(ドメイン駆動設計開発入門第7章「柔軟性をもたらす依存関係のコントロール」より)
こんな定義だけ見ても何にもわからないですよね。
以下にどのようなものが依存関係逆転の法則を満たしているかを示します。
Go言語のコードになってしまうので他の言語の方は読み替えていただけますと幸いです。
ユーザーサービス(上位レベルのモジュール)とリポジトリ(下位レベルのモジュール)の関係を示したコードです。
type UserService struct {
userRepository repositoryinterface.UserRepositoryInterface
}
func NewUserService(userRepository repositoryinterface.UserRepositoryInterface) *UserService {
return &UserService{
userRepository: userRepository,
}
}
type UserService struct {
userRepository repository.UserRepository
}
func NewUserService(userRepository repository.UserRepository) *UserService {
return &UserService{
userRepository: userRepository,
}
}
NewUserService
関数内で引数にとっているものの違いを確認してください。
Go言語ではインターフェースは実装することを約束するだけで関数の実装はインターフェースには書かないのですが、userUserviceがリポジトリのインターフェースに依存(抽象に依存している)コード(上)とuserServiceがリポジトリの実装(具体に依存している)コードです。
先ほどの定義から言えば
- 上位レベルのモジュールは下位レベルのモジュールに依存してはならない、どちらのモジュールも抽象に依存しなければならない
とのことでしたので、上のコードがいいコードであるということがわかります。
依存関係逆転の法則、なんとなくわかりましたか?
これを実践することで以下のような構成が出来上がります。
これは私がインターンの際に作成したAPIの構成です。レイヤードアーキテクチャを採用しています。
この図の中では依存関係は矢印の向きになっています。
ここで依存関係逆転の法則の定義を再度確認してみましょう。
依存関係逆転の法則
依存関係逆転の法則の定義を以下に示します。
- 上位レベルのモジュールは下位レベルのモジュールに依存してはならない、どちらのモジュールも抽象に依存しなければならない
- 抽象は実装の詳細に依存してはならない。実装の詳細が抽象に依存しなけらばならない
(ドメイン駆動設計開発入門第7章「柔軟性をもたらす依存関係のコントロール」より)
リポジトリ層はリポジトリ層のインターフェースに依存していて、上位レベルのモジュールはインターフェースに依存していることがわかりますね。これが依存関係逆転の法則を実践している部分になります。
今回はリポジトリ層のみをインターフェース化したのですが、本来ならばサービス層やユースケース層もインターフェース化するべきなのかなーと感じました。
こんな感じでアーキテクチャとそれぞれの責務を意識して実装を行うことでものすごく嬉しいことに気づきました。
リポジトリ
リポジトリとはデータの永続化をする部分を抽象化しているです。データの永続化を行うことでアプリケーションはものすごく柔軟な設計ができるようになります。(後述します)
こんなことを書いてありましたよね、
これが柔軟な設計の意味です。このような設計にしておくことで、データベースがMySQLからPostgreSQLに変更になったとしても、アーキテクチャの図の最下層のデータベース操作の実装の部分の変更のみで済むのです!
これがアーキテクチャにこだわることの嬉しさだと思います!
結局ドメイン駆動を学習すると何が嬉しいの?
ここまで見てきたようにドメイン駆動にして何が嬉しいのかはもうなんとなくわかってきていると思いますがもう一度整理してみましょう。
- ビジネスロジックを集中させることができる
- ドメインに焦点を当てて開発を行うのでそのビジネスの概念やルールを明確にします。これによってビジネス側の人間とエンジニア側の人間での意思の疎通が図れるということです。
- ビジネスの価値を反映したコードを書くことができる。
- 変更に強い設計
- ドメイン駆動設計を行うと仕様の変更に強いソフトウェアが作成できます。適切な責務を適切なコードに配置することで、過不足がないコードを書くことができます。
- ビジネス側で新しい要件や条件などが追加されてもそれを管理しているのはドメインなのでドメイン層のみを修正することでその要件変更に対応できます。
- また、先述の通りインフラの変更にも最低限のコードの変更で対応できます(これはレイヤードアーキテクチャの利点といったほうがいいかもしれないです。)
- テストが簡単になる
- ドメイン層がインフラなどから分離しているのでモックを利用したテストが容易になります。
- これにより(TDD)もしやすくなります。
- バグの起こりにくいソフトウェア
- ファクトリなどが最たる例ですが、ドメインモデルの状態が一貫し保持されるので、バグの起きにくいソフトウェアが設計できます。
まとめ
ここまで記事を書いてきてみて、自身でもまだまだ理解が足りないなーと感じました。
ドメイン駆動設計が自分の中で身につけば相当強力なソフトウェアを構築できると思っています。
また、実際に自身の手でドメイン駆動で開発して嬉しいことを試してみてその強力さを実感してみようかなと思います。
あと、2冊目としてこんな本も買いました
多分めっちゃ難しくて分厚いのですが、頑張って読み切りたいと思います。
もし好評でしたら、コードの方もなんとなくこんな感じで書いてみたという記事も出そうかなと思います。
ぜひいいねよろしくお願いします。(モチベ上がります)
ではよきエンジニアライフを!!
Discussion