javaのドメインモデルとdtoを開発しながら問題になる部分を整理しました
1 . DTO とドメインなぜ 可変/不変 を分けるのか?
階層 | 推奨する性質 | 理由 |
---|---|---|
DTO(転送オブジェクト) |
可変でも問題なし ― デフォルトコンストラクター+setter が Jackson/JPA と最も相性が良い |
直列化・逆直列化専用。ビジネスロジックを持たないため、可変でも整合性リスクが小さい |
ドメインモデル |
不変(record や final フィールド) |
生成時にのみ整合性を検証 → 一貫性保証/スレッド安全/テスト容易 |
まとめ ― 不変性は ビジネスルールを抱えるレイヤ にだけ適用すれば十分。
2. Jackson 逆直列化 3 パターン
パターン | コード例 | 特徴 |
---|---|---|
① デフォルトコンストラクター+setter | java @NoArgsConstructor class Foo { String name; void setName(...); } |
実装が最も簡単。final フィールドには使えない |
② @JsonCreator + @JsonProperty |
java @JsonCreator Foo(@JsonProperty("name") String n) |
完全不変 DTO が可能。全パラメータに @JsonProperty 必須 |
③ record (Java 14+) |
java public record Foo(String name,int value) {} |
生成子・getter 自動生成、final フィールド OK。Jackson 2.12+ なら追加設定なし |
-parameters
オプションを付ければ(@JsonProperty
なしでも)通常のコンストラクター名推論が可能。
3 . 実践コード例
3-1. 転送 DTO ― TemperatureSensorDto
(可変)
@Getter @Setter @NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = false) // additionalProperties=false を反映
public class TemperatureSensorDto {
private String name;
private Double value;
private String unit; // "Celsius" | "Fahrenheit" | "Kelvin"
}
3-2. ドメインオブジェクト ― TemperatureSensor
(不変)
public record TemperatureSensor(String name,
double value,
Unit unit) {
public TemperatureSensor {
if (name == null || name.isBlank()) throw new IllegalArgumentException("name");
if (unit == null) throw new IllegalArgumentException("unit");
}
public enum Unit { CELSIUS, FAHRENHEIT, KELVIN }
}
3-3. マッパー(MapStruct)
@Mapper(componentModel = "spring")
public interface SensorMapper {
TemperatureSensor toDomain(TemperatureSensorDto dto);
TemperatureSensorDto toDto(TemperatureSensor domain);
}
4 . レイヤーごとの責務整理
┌─ REST / Kafka / GraphQL ──────────────────────────────────────────┐
│ TemperatureSensorDto (可変) ← JSON 直列化/逆直列化 │
└─────────────▲ Mapper ▼────────────────────────────────────────┘
┌─ Service / Domain ───────────────────────────────┐
│ TemperatureSensor(record・不変) │
│ ビジネスロジック・整合性保証 │
└─────────────────────────────────────────────────┘
- DTO を変更してもドメインロジックへの影響は最小限
-
record
により「不変 + 生成時検証」をシンプルに実現
5 . チェックリスト
チェック項目 | ポイント |
---|---|
DTO に final フィールドはある? |
setter パターンなら final を避ける。不変 DTO が欲しければ record + -parameters か @JsonCreator を使う |
@JsonCreator 利用時 |
各パラメータに @JsonProperty を付与し、クラスにつき 1 個にする |
JSON の "enum"
|
Java 予約語なのでフィールド名を変更し、@JsonProperty("enum") でマッピング |
追加フィールドの拒否 |
@JsonIgnoreProperties(ignoreUnknown = false) で additionalProperties:false を反映 |
record サポートバージョン |
Jackson 2.12 以上 & JDK 14 以上が必要 |