何回かトライしたけど消化不良で理解できていなかったのですが、最近また読み進めてみたら前よりも理解できるようになっていたのでアウトプットします。
1章 小さくまとめてわかりやすくする
小さなクラスでわかりやすく安全に
データの種類 | 用途 | Javaでの記法 |
---|---|---|
数値 | 金額、数量 | int,BigDecimal |
日付 | 予定日、注文日、有効期限 | LocalDate |
文字 | 指名、電話番号、説明 | String |
金額や数量をnumber型でそのまま使ってしまうとnumber型ならどんな値でも入ってしまいます。
例えば個人でリンゴを買うとして数量に1000000000(十億)個
というような値も入ってしまいます。
これはアプリケーションとして安全ではありません。
これを防ぐには数量のためのクラスを作る必要があります。
class Quantity {
static final int MIN = 1;
static final int MAX = 100;
int value
Quantity(int value) {
if(value < MIN) throw new
IllegalArgumentException("不正:" + MIN + "未満");
if(value > MAX) throw new
IllegalArgumentException(("不正:" + MAX + "超");)
}
}
上記のようにすることで1000000000(十億)個
というありえない数値が入ることを防ぐことができます。
また、ここに記述することで、例えばバックエンド側のいくつかのコードに以下の記述をしなくても良いことになります。
if(value < MIN) throw new
IllegalArgumentException("不正:" + MIN + "未満");
if(value > MAX) throw new
IllegalArgumentException(("不正:" + MAX + "超");)
}
データの種類 | ビジネスロジック |
---|---|
金額 | 合計の計算、3桁ごとのカンマ編集をして、千円単位を丸めて加工して返す |
また上記のビジネスロジックがあったとして、それをクラスの中に記述して、色々な場所で呼び出して使用することができます。
このような値を扱うための専用クラスを値オブジェクト
と呼びます。
呼び出し側でどうやって使う?
separateEveryThreeDigits
という3桁ごとのカンマ編集をして、千円単位を丸めて加工して返すメソッドが金額クラスであるMoneyにあったとして、
こんな感じでどこでも使えるようになります。
const money = new Money(1000).separateEveryThreeDigits()
正直以下のようなやり方でもいけるんですが、これだと他のメソッドも使用できてしまうので(Number型のため)、Moneyという関心事に対してすべきでない処理もすることができてしまい、安全ではありません。
また、安全であるということに加えて、Moenyという値オブジェクトを参照することで他にどのようなビジネスロジックがあるか知ることができます(型が仕様を語る)。
const money = Number(1000).toLocalString()
3章 業務ロジックをわかりやすく整理する
業務アプリケーションのコードの見通しが悪くなる原因
従来の手続き型の設計では、アプリケーションのクラス構成を、データを格納するデータクラスと、ロジックを記述する機能クラスに分けることが基本になります。
データクラスのいろいろな呼び方
名前 | 説明 |
---|---|
Data Transfer Object | サブシステム間でデータをやり取りするための設計パターン |
Entityクラス | データベースとデータをやり取りするためのデータの入れ物クラス |
Formクラス | 画面とデータやり取りするためのデータの入れ物クラス |
データクラスは
getQuantity()
/setQuantity()
など、いわゆるgetter
/setter
と言われるメソッド群だけを持ちます。
業務アプリケーションでは、業務ロジックを整理するために、画面インターフェース/業務ロジック/データベース入出力の3つの関心ごとを分離するために三層アーキテクチャーが一般的です。
三層アーキテクチャ
名前 | 説明 |
---|---|
プレゼンテーション層 | 画面や外部接続インターフェース |
アプリケーション層 | 業務ロジック、業務ルール |
データソース層 | データベース入出力 |
getterとsetterがあるとどうなるか
- いろいろな場所で同じ業務ロジックが重複して書かれる
- どこに業務ロジックが書いてあるか見通しが悪くなる
修正するときにgrepして頑張って修正するみたいな感じになります。
データクラスを使用してアプリケーション層に業務ロジックを書くとどこに何が書いてあるかの見通しが悪くなりがちです。
そうならないために
データ + ロジックが一体になったクラスを作ります。
設計時には以下のことに気をつけます。(他にもありますが割愛)
- メソッドをロジックの置き場所にする
- ロジックをデータを持つクラスに移動する
メソッドをロジックの置き場所にする
データクラスがうまくいかないのは、自分がデータをそのまま別のクラスに渡してしまうから(getterがあるから)です。
それを防ぐにはgetterをなくして
class Person {
private String firstName;
private String lastName;
String getFirstName() {
return firstName;
}
String getLastName() {
return lastName;
}
}
上記のようなクラスは使用する側にデータを渡すだけのクラスです。
ここにビジネスロジックを追加します。
class Person {
private String firstName;
private String lastName;
...
String FullName() {
return String.format("%s %s", firstName, lastName);
}
}
業務の関心事が欲しいものをそのまま渡します。
例)
リンゴジュースが欲しいと思ったときに
上のコードはリンゴを渡してくる(こっちでどうにかしないといけない)
下のコードはリンゴを絞ってリンゴジュースにして渡してくれる
ロジックをデータを持つクラスに移動する
下のコードのメリット
- データを持つ側のクラスにロジックが増える
- データをgetしていたクラスからロジックが減る
- 使う側のクラスはデータをgetするのではなく、そのデータを使った計算結果を受け取るようになる
三層の関心事と業務ロジックの分離を徹底する
業務データと業務ロジックを1つにまとめたオブジェクトを
ドメインオブジェクト
と呼びます。
ドメインオブジェクトは三層アーキテクチャのどこからでも参照することができます。
なのでデータクラスでgetterして各層で加工/判断/計算
していた箇所をこのドメインオブジェクトのメソッドを使用して使うことができるのです。
このようなアーキテクチャを三層 + ドメインモデル
と呼びます
5章 アプリケーション機能を組み立てる
三層 + ドメインモデルにおけるアプリケーション層のクラスの役割
- プレゼンテーション層からの依頼を受ける
- 適切なドメインオブジェクトに判断/加工/計算を依頼する
- プレゼンテーション層に結果(ドメインオブジェクトを返す
- データソース層に記録や通知の入出力を指示する
アプリケーション層のクラスはプレゼンテーション層に対して、業務サービスを提供します。サービスを提供するという意味で、アプリケーションサービスクラス、もしくはサービスクラスと呼びます。
サービスクラスの設計で気をつけるポイント
- 業務ロジックをサービスクラスに書かずにドメインオブジェクトに任せる
- 画面の複雑さをそのままサービスクラスに持ち込まない
業務ロジックをサービスクラスに書かずにドメインオブジェクトに任せる
業務アプリケーションを段階的に作っていくときにサービスクラスのメソッドに業務ロジックを直接書いてしまうことが、その時点では最もわかりやすく手っ取り早いことはよくあります。
しかし、サービスクラスに業務ロジックを書き始めると、手続き型のプログラミングで起こりがちなコードの重複が始まります。
そうならないために、段階的にコードを追加するときには、いつも設計の改善を考えます。適切なドメインオブジェクトがなければ、ドメインオブジェクトの追加を考えます。
自分が今の段階で疑問なこと
サービスクラスに書いてある条件などはどこからどこまでがビジネスロジック?
ドメインに今サービスクラスに書いてある条件をドメインオブジェクトに組み込んだら半端ない行数になりそうだけどそれで良い?
7章 画面とドメインオブジェクトの設計を連動させる
画面とソフトウェアを関係づける
利用者が何に関心事があり、どういうことをやりたいかは、画面で視覚的に表現されます。利用者の関心事を可視化した画面と、ドメインオブジェクトの不一致は設計改善の良い手がかりです。
- 画面での項目の並び順と、対応するドメインオブジェクトのフィールドの並び順が一致していない。
以下のようなクラスがあって
class Book {
long id;
BookNumber number;
Title title;
Author author;
Publisher publisher;
BookType type;
Price unitPrice;
LocalDate published;
LocalDate registerd;
}
画面上での項目の並び方が以下のように並んでいるときに
- 書名
- 価格
- 発行年月日
- 業者
- 本の種類
画面上での項目の並びはユーザーの関心事と一致するため、ドメインでのインスタンス変数の並びも一致するのが良いと書いてあります。
修正後は以下のようになります。
class BookSummary {
Title title;
Price unitPrice;
LocalDate published;
Author author;
BookType type;
}
画面側での項目の並びが変わっときにドメインオブジェクトのインスタンス変数の並びも更新するのが良い設計だそうです。
8章 アプリケーション間の連携
TBA
感想
今の職場ではDDDを採用しているのですが、前職では手続き型100%で全てのコードを記述していたため、今の職場にきてから最初の頃は「データを取得するためになぜこんな面倒くさいことをしなくてはいけないんだ....」と思っていました。
でも不具合修正の時など、コードを見ずに修正箇所を特定できたり、修正箇所が少なかったりなどメリットを身をもって体験することができ、DDD、オブジェクト指向すげぇってなることが多々ありました。
今まで何回かこの本を読んでは途中でわからなくなって投げ出すということをしていたのですが、それはそもそもDDDの根幹であるクラス、オブジェクト指向について全く知らなかったためであるかなと今になって思います。(今でもそんなに知らないけど...)
クラス、オブジェクト指向については以下の本がめちゃめちゃわかりやすいです。(java知らなくても読める)