@Embeddableのネストは実現可能でしょうか? #1397
-
※意図したニュアンスを保って英訳できる自信がなかったので日本語で記載します。 相談したいこといつもDomaをたのしく使わせていただいております!(ありがとうございます!) いま、クラスのモデリングをしていくうちに 起こっていることシンプルな例にしたかったのですが、いい例が思いつかなかったので実際に書いているコードをできるだけシンプルにして掲載させていただきます。 まず、現状のDomaで実現できるコードとしていまこのようなEntityを書いています。 また、MemberPatchはWebAPIのリクエストボディにするクラスでも使いたい(JacksonでJsonからデシリアライズしたい)という都合も裏にあります。 import java.time.LocalDateTime;
import java.util.Optional;
import java.util.stream.Stream;
import org.seasar.doma.Embeddable;
import org.seasar.doma.Entity;
import org.seasar.doma.Id;
import lombok.With;
@With
record Member(
Long id,
String name,
Optional<String> kana,
Optional<String> address) {
public Member apply(MemberEditedEvent event) {
return this
.withName(event.patch().name().orElse(this.name()))
.withKana(event.patch().kana()
.or(() -> event.patch().clearAddressIfEmpty().orElse(false) ? Optional.empty() : this.kana()))
.withAddress(event.patch().address()
.or(() -> event.patch().clearAddressIfEmpty().orElse(false) ? Optional.empty() : this.address()));
}
// ... more code ...
}
@Entity
record MemberEditedEvent(
@Id Long id,
LocalDateTime createdAt,
String createdBy,
Long memberId,
MemberPatch patch) {
}
@Embeddable
record MemberPatch(
Optional<String> name,
Optional<String> kana,
Optional<Boolean> clearKanaIfEmpty,
Optional<String> address,
Optional<Boolean> clearAddressIfEmpty) {
public MemberPatch {
final var hasChange = Stream
.of(
name,
kana,
address)
.anyMatch(Optional::isPresent);
final var hasClearIfEmpty = Stream
.of(
clearKanaIfEmpty,
clearAddressIfEmpty)
.flatMap(Optional::stream)
.anyMatch(clearIfEmpty -> clearIfEmpty);
if (!hasChange && !hasClearIfEmpty) {
throw new IllegalArgumentException("changes are required");
}
}
} そしていま、MemberPatchのclearXxxIfEmptyのあたりが冗長でイマイチなので、 ※ClearableFieldPatchですが、newValueだけだとテーブルから復元(またはJsonからデシリアライズ)するときに import java.time.LocalDateTime;
import java.util.Optional;
import java.util.stream.Stream;
import org.seasar.doma.Embeddable;
import org.seasar.doma.Embedded;
import org.seasar.doma.Entity;
import org.seasar.doma.Id;
import lombok.With;
@With
record Member(
Long id,
String name,
Optional<String> kana,
Optional<String> address) {
public Member apply(MemberEditedEvent event) {
return this
.withName(
event.patch().name().orElse(this.name()))
.withKana(
event.patch().kana().map(ClearableFieldPatch::newValue).orElse(this.kana()))
.withAddress(
event.patch().address().map(ClearableFieldPatch::newValue).orElse(this.address()));
}
// ... more code ...
}
@Entity
record MemberEditedEvent(
@Id Long id,
LocalDateTime createdAt,
String createdBy,
Long memberId,
MemberPatch patch) {
}
@Embeddable
record MemberPatch(
Optional<String> name,
@Embedded(prefix = "kana_") Optional<ClearableFieldPatch<String>> kana,
@Embedded(prefix = "address_") Optional<ClearableFieldPatch<String>> address) {
public MemberPatch {
final var hasChange = Stream
.of(
name,
kana,
address)
.anyMatch(Optional::isPresent);
if (!hasChange) {
throw new IllegalArgumentException("Changes are required.");
}
}
}
@Embeddable
record ClearableFieldPatch<T>(
boolean clear,
Optional<T> newValue) {
public ClearableFieldPatch {
if (clear && newValue.isPresent()) {
throw new IllegalArgumentException("If you're clearing, don't set the value.");
}
if (!clear && newValue.isEmpty()) {
throw new IllegalArgumentException("If you're not clearing, set the value.");
}
}
} こうなったとき、 もちろん、変更前の状態でとどめておいたり、これらのクラスとは別にEntity用のクラスを作って変換するなどすれば対応はできるのですが、 込み入った内容になってしまい恐縮ですが、お力添えいただけるとうれしいです。 |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 9 replies
-
ネストを実装することは技術的には可能なのですが、一旦ネスト以外の方法で意見交換させてください。 例えばですが、次のようにするのはいかがですか? @Entity
record MemberEditedEvent(
@Id Long id,
LocalDateTime createdAt,
String createdBy,
Long memberId,
Optional<String> name,
@Embedded(prefix = "kana_") ClearableFieldPatch kana,
@Embedded(prefix = "address_") ClearableFieldPatch address) {
}
@Embeddable
record ClearableFieldPatch(boolean clear, String newValue) {}
record MemberPatch(Optional<String> name, ClearableFieldPatch kana, ClearableFieldPatch address) {}
@Entity
record MemberEditedEvent(
@Id Long id,
LocalDateTime createdAt,
String createdBy,
Long memberId,
Optional<String> name,
@Embedded(prefix = "kana_") ClearableFieldPatch kana,
@Embedded(prefix = "address_") ClearableFieldPatch address) {
MemberEditedEvent(Long id, LocalDateTime createdAt, String createdBy, Long memberId, MemberPatch memberPatch) {
this(id, createdAt, createdBy, memberId, memberPatch.name(), memberPatch.kana(), memberPatch.address());
}
} ネストはコードの見通しを悪くしてしまうので、できるだけ避けた方が良いのかなと思います。 |
Beta Was this translation helpful? Give feedback.
ありがとうございます。
実現したいことを理解できました。
一番最初に提示いただいたデータ構造を利用する前提で案を2つほど挙げてみます。
案1: ClearableFieldPatchをExternal Domainで扱う方法
ClearableFieldPatch
から@Embeddable
を外し、次のような@ExternalDomain
が注釈されたDomainConverter
の実装クラスでシリアライズ/デシリアライズします。この方法では@Embeddable
のネストが解消され型パラメータも使えます。(データベースがJSON型をサポートしていれば、上記コードを少し変更してJSON型に対応させることも可能です)
案2: ドメインモデルとDBのデータモデルを分ける方法
やりた…