記事の目的
DDD(ドメイン駆動設計)では値オブジェクトはイミュータブルであることが求められる。
エンティティオブジェクトも可能であればイミュータブルとすべきである。
レコードクラスはイミュータブルであることが強制できるので、DDDと相性が良い。
DDDに限らず、オブジェクト指向でプログラミングするのであれば、予期せぬオブジェクトの状態変化でつまらないバグにハマることも無くなるので、オブジェクトは極力イミュータブルになるようにしたほうが良いと思っている。
世の中にはクラスに無条件かつ盲目的にgetter/setterをつくる思考停止なプログラミングが溢れていて、Lombokのようなライブラリがそのような悪いプログラミングの量産に拍車をかけている気がする。
「Lombokイコールすべて悪」とは言わないが、なぜそのフィールドがprivateなのか、本当にsetterが必要なのかを考えること無く、なんとなく動く(だけど危うい)プログラムがラクに書けてしまうので、少なくとも自分の携わるプロジェクトではLombokは利用禁止にしている。
Java16から正式実装されたレコードクラスを積極的に使うことで、少なくともイミュータブルであることが保証できるし、Lombokのようなライブラリを使わなくても、言語レベルでシンプル且つ安全なクラスが作れるようになったのでこの記事で基本的な書き方を扱う。
レコードクラスの効能
- フィールドが
private final
で強制されオブジェクトがイミュータブルになる - コンストラクタ、toString、equqls、hashCode 各メソッドが自動実装されコードがシンプルになるため、本当に必要な業務ロジックに注目できる
前提
Java16以降(Java14でプレビュー版)
本題
一般的なValueObjectを従来のクラスとレコードクラスそれぞれで書いてみる
普通のJavaクラス
import java.util.Objects;
public final class Title {
private final String value;
public Title(String value) {
this.value = value;
}
public String value() {
return value;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (Title) obj;
return Objects.equals(this.value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@Override
public String toString() {
return "Title[" +
"value=" + value + ']';
}
}
上記と同義のレコードクラス
超シンプルになる
public record Title(
String value
) {
}
コンストラクタの上書きもできる
public record Title(
String value
) {
public Title(String value){
this.value = value + "hoge";
}
}
Bean Validationもできる
import javax.validation.constraints.NotEmpty;
public record Title(
@NotEmpty(message = "タイトルを入力してください")
String value
) {
}
勿論メソッドも実装できる
public record Title(
String value
) {
public int length() {
return this.value.length();
}
}