Skip to content

Commit f2e77b3

Browse files
Added builder for abstract entity
1 parent 6a9bf70 commit f2e77b3

3 files changed

Lines changed: 100 additions & 4 deletions

File tree

core/src/main/java/org/fuin/ddd4j/core/AbstractEntity.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.fuin.ddd4j.core;
1919

2020
import org.fuin.objects4j.common.Contract;
21+
import org.jspecify.annotations.Nullable;
2122

2223
/**
2324
* Base class for entities.
@@ -65,7 +66,7 @@ public final int hashCode() {
6566
}
6667

6768
@Override
68-
public final boolean equals(final Object obj) {
69+
public final boolean equals(@Nullable final Object obj) {
6970
if (this == obj) {
7071
return true;
7172
}
@@ -110,8 +111,10 @@ protected final ROOT_ID getRootId() {
110111
*/
111112
protected abstract static class Builder<ROOT_ID extends AggregateRootId, ROOT extends AbstractAggregateRoot<ROOT_ID>, ID extends EntityId, TYPE extends AbstractEntity<ROOT_ID, ROOT, ID>, BUILDER extends Builder<ROOT_ID, ROOT, ID, TYPE, BUILDER>> {
112113

114+
@Nullable
113115
private ROOT rootAggregate;
114116

117+
@Nullable
115118
private ID id;
116119

117120
/**
@@ -128,7 +131,7 @@ protected Builder() {
128131
* @return This builder.
129132
*/
130133
@SuppressWarnings("unchecked")
131-
public final BUILDER rootAggregate(@NotNull final ROOT rootAggregate) {
134+
public final BUILDER rootAggregate(final ROOT rootAggregate) {
132135
Contract.requireArgNotNull("rootAggregate", rootAggregate);
133136
this.rootAggregate = rootAggregate;
134137
return (BUILDER) this;
@@ -141,7 +144,7 @@ public final BUILDER rootAggregate(@NotNull final ROOT rootAggregate) {
141144
* @return This builder.
142145
*/
143146
@SuppressWarnings("unchecked")
144-
public final BUILDER id(@NotNull final ID id) {
147+
public final BUILDER id(final ID id) {
145148
Contract.requireArgNotNull("id", id);
146149
this.id = id;
147150
return (BUILDER) this;
@@ -152,6 +155,7 @@ public final BUILDER id(@NotNull final ID id) {
152155
*
153156
* @return Root aggregate.
154157
*/
158+
@Nullable
155159
protected final ROOT getRootAggregate() {
156160
return rootAggregate;
157161
}
@@ -161,6 +165,7 @@ protected final ROOT getRootAggregate() {
161165
*
162166
* @return Unique entity identifier.
163167
*/
168+
@Nullable
164169
protected final ID getEntityId() {
165170
return id;
166171
}
@@ -187,7 +192,7 @@ protected final void resetAbstractEntity() {
187192
* @param name Name of the field.
188193
* @param value Value to test for {@literal null}.
189194
*/
190-
protected final void ensureNotNull(final String name, final Object value) {
195+
protected final void ensureNotNull(final String name, @Nullable final Object value) {
191196
if (value == null) {
192197
throw new RuntimeException("The value of '" + name + "' has not been set");
193198
}

core/src/test/java/org/fuin/ddd4j/core/AbstractEntityTest.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@
2323
import org.fuin.ddd4j.coretest.ARoot;
2424
import org.fuin.ddd4j.coretest.BEntity;
2525
import org.fuin.ddd4j.coretest.BId;
26+
import org.fuin.objects4j.common.ConstraintViolationException;
2627
import org.junit.jupiter.api.Test;
2728

2829
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2931

3032
public class AbstractEntityTest {
3133

@@ -50,4 +52,91 @@ public void testGetRootId() throws DuplicateEntityException {
5052

5153
}
5254

55+
@Test
56+
public void testBuilderBuild() {
57+
58+
// PREPARE
59+
final ARoot root = new ARoot(new AId(1));
60+
final BId id = new BId(2);
61+
62+
// TEST
63+
final BEntity entity = new BEntityBuilder().rootAggregate(root).id(id).build();
64+
65+
// VERIFY
66+
assertThat(entity).isNotNull();
67+
assertThat(entity.getRoot()).isEqualTo(root);
68+
assertThat(entity.getId()).isEqualTo(id);
69+
70+
}
71+
72+
@Test
73+
public void testBuilderReturnsItself() {
74+
75+
// PREPARE
76+
final BEntityBuilder builder = new BEntityBuilder();
77+
78+
// TEST & VERIFY
79+
assertThat(builder.rootAggregate(new ARoot(new AId(1)))).isSameAs(builder);
80+
assertThat(builder.id(new BId(2))).isSameAs(builder);
81+
82+
}
83+
84+
@Test
85+
public void testBuilderRootAggregateNull() {
86+
assertThatThrownBy(() -> new BEntityBuilder().rootAggregate(null))
87+
.isInstanceOf(ConstraintViolationException.class)
88+
.hasMessageContaining("rootAggregate");
89+
}
90+
91+
@Test
92+
public void testBuilderIdNull() {
93+
assertThatThrownBy(() -> new BEntityBuilder().id(null))
94+
.isInstanceOf(ConstraintViolationException.class)
95+
.hasMessageContaining("id");
96+
}
97+
98+
@Test
99+
public void testBuilderEnsureBuildableFailsWhenRootAggregateMissing() {
100+
assertThatThrownBy(() -> new BEntityBuilder().id(new BId(2)).build())
101+
.isInstanceOf(RuntimeException.class)
102+
.hasMessageContaining("rootAggregate");
103+
}
104+
105+
@Test
106+
public void testBuilderEnsureBuildableFailsWhenIdMissing() {
107+
assertThatThrownBy(() -> new BEntityBuilder().rootAggregate(new ARoot(new AId(1))).build())
108+
.isInstanceOf(RuntimeException.class)
109+
.hasMessageContaining("id");
110+
}
111+
112+
@Test
113+
public void testBuilderResetsAfterBuild() {
114+
115+
// PREPARE
116+
final BEntityBuilder builder = new BEntityBuilder();
117+
builder.rootAggregate(new ARoot(new AId(1))).id(new BId(2)).build();
118+
119+
// TEST & VERIFY - the mandatory data was cleared, so a second build fails
120+
assertThatThrownBy(builder::build)
121+
.isInstanceOf(RuntimeException.class)
122+
.hasMessageContaining("rootAggregate");
123+
124+
}
125+
126+
/**
127+
* Concrete builder used to test the abstract {@link AbstractEntity.Builder}.
128+
*/
129+
private static final class BEntityBuilder
130+
extends AbstractEntity.Builder<AId, ARoot, BId, BEntity, BEntityBuilder> {
131+
132+
@Override
133+
public BEntity build() {
134+
ensureBuildableAbstractEntity();
135+
final BEntity entity = new BEntity(getRootAggregate(), getEntityId());
136+
resetAbstractEntity();
137+
return entity;
138+
}
139+
140+
}
141+
53142
}

pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
</issueManagement>
3030

3131
<properties>
32+
3233
<jackson.version>2.18.2</jackson.version>
3334
<esc.version>0.10.0-SNAPSHOT</esc.version>
3435
<objects4j.version>0.11.1-SNAPSHOT</objects4j.version>
@@ -39,6 +40,7 @@
3940
<org-fuin-bom.version>1.0.2-SNAPSHOT</org-fuin-bom.version>
4041
<error-prone.version>2.49.0</error-prone.version>
4142
<nullaway.version>0.13.6</nullaway.version>
43+
4244
</properties>
4345

4446
<dependencyManagement>

0 commit comments

Comments
 (0)