Skip to content

Commit b189dbc

Browse files
committed
GH-806 - Polish Neo4j archiving mode.
We now explicitly set the completion date property of newly created event publications to null. The copying data manipulation statements now properly copy all properties of the publication notes into the archive.
1 parent 4132ad3 commit b189dbc

File tree

3 files changed

+82
-47
lines changed

3 files changed

+82
-47
lines changed

spring-modulith-events/spring-modulith-events-neo4j/src/main/java/org/springframework/modulith/events/neo4j/Neo4jEventPublication.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.time.Instant;
1919
import java.util.UUID;
2020

21+
import org.springframework.lang.Nullable;
22+
2123
/**
2224
* The event publication entity definition.
2325
*
@@ -32,15 +34,16 @@ class Neo4jEventPublication {
3234
public final Object event;
3335
public final String eventHash;
3436

35-
public Instant completionDate;
37+
public @Nullable Instant completionDate;
3638

3739
public Neo4jEventPublication(UUID identifier, Instant publicationDate, String listenerId, Object event,
38-
String eventHash) {
40+
String eventHash, @Nullable Instant completionDate) {
3941

4042
this.identifier = identifier;
4143
this.publicationDate = publicationDate;
4244
this.listenerId = listenerId;
4345
this.event = event;
4446
this.eventHash = eventHash;
47+
this.completionDate = completionDate;
4548
}
4649
}

spring-modulith-events/spring-modulith-events-neo4j/src/main/java/org/springframework/modulith/events/neo4j/Neo4jEventPublicationRepository.java

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,20 @@
2020
import java.time.Instant;
2121
import java.time.ZoneOffset;
2222
import java.util.ArrayList;
23+
import java.util.Collection;
2324
import java.util.List;
2425
import java.util.Map;
2526
import java.util.Objects;
2627
import java.util.Optional;
2728
import java.util.UUID;
29+
import java.util.function.BiFunction;
2830
import java.util.function.Function;
2931

3032
import org.neo4j.cypherdsl.core.Cypher;
3133
import org.neo4j.cypherdsl.core.Node;
3234
import org.neo4j.cypherdsl.core.ResultStatement;
3335
import org.neo4j.cypherdsl.core.Statement;
36+
import org.neo4j.cypherdsl.core.StatementBuilder.OrderableOngoingReadingAndWithWithoutWhere;
3437
import org.neo4j.cypherdsl.core.renderer.Configuration;
3538
import org.neo4j.cypherdsl.core.renderer.Renderer;
3639
import org.neo4j.driver.Values;
@@ -65,11 +68,14 @@ class Neo4jEventPublicationRepository implements EventPublicationRepository {
6568
private static final String PUBLICATION_DATE = "publicationDate";
6669
private static final String COMPLETION_DATE = "completionDate";
6770

71+
private static final Collection<String> ALL_PROPERTIES = List.of(ID, EVENT_SERIALIZED, EVENT_HASH, EVENT_TYPE,
72+
LISTENER_ID, PUBLICATION_DATE, COMPLETION_DATE);
73+
6874
private static final Node EVENT_PUBLICATION_NODE = node("Neo4jEventPublication")
6975
.named("neo4jEventPublication");
7076

71-
private static final Node EVENT_PUBLICATION_COMPLETED_NODE = node("Neo4jEventPublicationCompleted")
72-
.named("neo4jEventPublicationCompleted");
77+
private static final Node EVENT_PUBLICATION_ARCHIVE_NODE = node("Neo4jEventPublicationArchive")
78+
.named("neo4jEventPublicationArchive");
7379

7480
private static final Statement INCOMPLETE_BY_EVENT_AND_TARGET_IDENTIFIER_STATEMENT = match(EVENT_PUBLICATION_NODE)
7581
.where(EVENT_PUBLICATION_NODE.property(EVENT_HASH).eq(parameter(EVENT_HASH)))
@@ -114,6 +120,7 @@ class Neo4jEventPublicationRepository implements EventPublicationRepository {
114120
.set(EVENT_PUBLICATION_NODE.property(EVENT_TYPE).to(parameter(EVENT_TYPE)))
115121
.set(EVENT_PUBLICATION_NODE.property(LISTENER_ID).to(parameter(LISTENER_ID)))
116122
.set(EVENT_PUBLICATION_NODE.property(PUBLICATION_DATE).to(parameter(PUBLICATION_DATE)))
123+
.set(EVENT_PUBLICATION_NODE.property(COMPLETION_DATE).to(parameter(COMPLETION_DATE)))
117124
.build();
118125

119126
private static final Statement COMPLETE_STATEMENT = match(EVENT_PUBLICATION_NODE)
@@ -123,29 +130,34 @@ class Neo4jEventPublicationRepository implements EventPublicationRepository {
123130
.set(EVENT_PUBLICATION_NODE.property(COMPLETION_DATE).to(parameter(COMPLETION_DATE)))
124131
.build();
125132

126-
private static final Statement COMPLETE_IN_ARCHIVE_BY_ID_STATEMENT = match(EVENT_PUBLICATION_NODE)
133+
private static final Statement COMPLETE_IN_ARCHIVE_BY_ID_STATEMENT = applyProperties(match(EVENT_PUBLICATION_NODE)
127134
.where(EVENT_PUBLICATION_NODE.property(ID).eq(parameter(ID)))
128-
.and(not(exists(match(EVENT_PUBLICATION_COMPLETED_NODE)
129-
.where(EVENT_PUBLICATION_COMPLETED_NODE.property(ID).eq(parameter(ID)))
130-
.returning(literalTrue()).build())))
131-
.with(EVENT_PUBLICATION_NODE)
132-
.create(EVENT_PUBLICATION_COMPLETED_NODE)
133-
.set(EVENT_PUBLICATION_COMPLETED_NODE.property(ID).to(EVENT_PUBLICATION_NODE.property(ID)))
134-
.set(EVENT_PUBLICATION_COMPLETED_NODE.property(COMPLETION_DATE).to(parameter(COMPLETION_DATE)))
135-
.build();
136-
137-
private static final Statement COMPLETE_IN_ARCHIVE_BY_EVENT_AND_LISTENER_ID_STATEMENT = match(EVENT_PUBLICATION_NODE)
138-
.where(EVENT_PUBLICATION_NODE.property(EVENT_HASH).eq(parameter(EVENT_HASH)))
139-
.and(EVENT_PUBLICATION_NODE.property(LISTENER_ID).eq(parameter(LISTENER_ID)))
140-
.and(not(exists(match(EVENT_PUBLICATION_COMPLETED_NODE)
141-
.where(EVENT_PUBLICATION_COMPLETED_NODE.property(EVENT_HASH).eq(parameter(EVENT_HASH)))
142-
.and(EVENT_PUBLICATION_COMPLETED_NODE.property(LISTENER_ID).eq(parameter(LISTENER_ID)))
135+
.and(not(exists(match(EVENT_PUBLICATION_ARCHIVE_NODE)
136+
.where(EVENT_PUBLICATION_ARCHIVE_NODE.property(ID).eq(parameter(ID)))
143137
.returning(literalTrue()).build())))
144-
.with(EVENT_PUBLICATION_NODE)
145-
.create(EVENT_PUBLICATION_COMPLETED_NODE)
146-
.set(EVENT_PUBLICATION_COMPLETED_NODE.property(ID).to(EVENT_PUBLICATION_NODE.property(ID)))
147-
.set(EVENT_PUBLICATION_COMPLETED_NODE.property(COMPLETION_DATE).to(parameter(COMPLETION_DATE)))
148-
.build();
138+
.with(EVENT_PUBLICATION_NODE));
139+
140+
private static final Statement COMPLETE_IN_ARCHIVE_BY_EVENT_AND_LISTENER_ID_STATEMENT = applyProperties(
141+
match(EVENT_PUBLICATION_NODE)
142+
.where(EVENT_PUBLICATION_NODE.property(EVENT_HASH).eq(parameter(EVENT_HASH)))
143+
.and(EVENT_PUBLICATION_NODE.property(LISTENER_ID).eq(parameter(LISTENER_ID)))
144+
.and(not(exists(match(EVENT_PUBLICATION_ARCHIVE_NODE)
145+
.where(EVENT_PUBLICATION_ARCHIVE_NODE.property(EVENT_HASH).eq(parameter(EVENT_HASH)))
146+
.and(EVENT_PUBLICATION_ARCHIVE_NODE.property(LISTENER_ID).eq(parameter(LISTENER_ID)))
147+
.returning(literalTrue()).build())))
148+
.with(EVENT_PUBLICATION_NODE));
149+
150+
private static Statement applyProperties(OrderableOngoingReadingAndWithWithoutWhere source) {
151+
152+
var operations = ALL_PROPERTIES.stream()
153+
.map(it -> EVENT_PUBLICATION_ARCHIVE_NODE.property(it).to(EVENT_PUBLICATION_NODE.property(it)))
154+
.toList();
155+
156+
return source.create(EVENT_PUBLICATION_ARCHIVE_NODE)
157+
.set(operations)
158+
.set(EVENT_PUBLICATION_ARCHIVE_NODE.property(COMPLETION_DATE).to(parameter(COMPLETION_DATE)))
159+
.build();
160+
}
149161

150162
private static final Function<Node, Statement> COMPLETE_BY_ID_STATEMENT = node -> match(node)
151163
.where(node.property(ID).eq(parameter(ID)))
@@ -168,6 +180,7 @@ class Neo4jEventPublicationRepository implements EventPublicationRepository {
168180
private final Renderer renderer;
169181
private final EventSerializer eventSerializer;
170182
private final CompletionMode completionMode;
183+
private final Node completedNode;
171184

172185
private final Statement deleteCompletedStatement;
173186
private final Statement deleteCompletedBeforeStatement;
@@ -187,12 +200,14 @@ class Neo4jEventPublicationRepository implements EventPublicationRepository {
187200
this.eventSerializer = eventSerializer;
188201
this.completionMode = completionMode;
189202

190-
var archiveNode = completionMode == CompletionMode.ARCHIVE ? EVENT_PUBLICATION_COMPLETED_NODE : EVENT_PUBLICATION_NODE;
203+
this.completedNode = completionMode == CompletionMode.ARCHIVE
204+
? EVENT_PUBLICATION_ARCHIVE_NODE
205+
: EVENT_PUBLICATION_NODE;
191206

192-
this.deleteCompletedStatement = DELETE_COMPLETED_STATEMENT.apply(archiveNode);
193-
this.deleteCompletedBeforeStatement = DELETE_COMPLETED_BEFORE_STATEMENT.apply(archiveNode);
194-
this.completedByIdStatement = COMPLETE_BY_ID_STATEMENT.apply(archiveNode);
195-
this.allCompletedStatement = ALL_COMPLETED_STATEMENT.apply(archiveNode);
207+
this.deleteCompletedStatement = DELETE_COMPLETED_STATEMENT.apply(completedNode);
208+
this.deleteCompletedBeforeStatement = DELETE_COMPLETED_BEFORE_STATEMENT.apply(completedNode);
209+
this.completedByIdStatement = COMPLETE_BY_ID_STATEMENT.apply(completedNode);
210+
this.allCompletedStatement = ALL_COMPLETED_STATEMENT.apply(completedNode);
196211
}
197212

198213
/*
@@ -219,7 +234,8 @@ public TargetEventPublication create(TargetEventPublication publication) {
219234
EVENT_HASH, eventHash,
220235
EVENT_TYPE, eventType,
221236
LISTENER_ID, listenerId,
222-
PUBLICATION_DATE, Values.value(publicationDate.atOffset(ZoneOffset.UTC))))
237+
PUBLICATION_DATE, Values.value(publicationDate.atOffset(ZoneOffset.UTC)),
238+
COMPLETION_DATE, Values.NULL))
223239
.run();
224240

225241
return publication;
@@ -249,6 +265,7 @@ public void markCompleted(Object event, PublicationTargetIdentifier identifier,
249265
.bind(identifier.getValue()).to(LISTENER_ID)
250266
.bind(Values.value(completionDate.atOffset(ZoneOffset.UTC))).to(COMPLETION_DATE)
251267
.run();
268+
252269
neo4jClient.query(renderer.render(DELETE_BY_EVENT_AND_LISTENER_ID))
253270
.bind(eventHash).to(EVENT_HASH)
254271
.bind(identifier.getValue()).to(LISTENER_ID)
@@ -279,9 +296,10 @@ public void markCompleted(UUID identifier, Instant completionDate) {
279296
} else if (completionMode == CompletionMode.ARCHIVE) {
280297

281298
neo4jClient.query(renderer.render(COMPLETE_IN_ARCHIVE_BY_ID_STATEMENT))
282-
.bind("").to(ID)
299+
.bind(Values.value(identifier.toString())).to(ID)
283300
.bind(Values.value(completionDate.atOffset(ZoneOffset.UTC))).to(COMPLETION_DATE)
284301
.run();
302+
285303
deletePublications(List.of(identifier));
286304

287305
} else {
@@ -304,7 +322,7 @@ public List<TargetEventPublication> findIncompletePublications() {
304322

305323
return List.copyOf(neo4jClient.query(renderer.render(INCOMPLETE_STATEMENT))
306324
.fetchAs(TargetEventPublication.class)
307-
.mappedBy(this::mapRecordToPublication)
325+
.mappedBy(incompleteMapping())
308326
.all());
309327
}
310328

@@ -319,7 +337,7 @@ public List<TargetEventPublication> findIncompletePublicationsPublishedBefore(In
319337
return List.copyOf(neo4jClient.query(renderer.render(INCOMPLETE_PUBLISHED_BEFORE_STATEMENT))
320338
.bind(Values.value(instant.atOffset(ZoneOffset.UTC))).to(PUBLICATION_DATE)
321339
.fetchAs(TargetEventPublication.class)
322-
.mappedBy(this::mapRecordToPublication)
340+
.mappedBy(incompleteMapping())
323341
.all());
324342
}
325343

@@ -338,7 +356,7 @@ public Optional<TargetEventPublication> findIncompletePublicationsByEventAndTarg
338356
return neo4jClient.query(renderer.render(INCOMPLETE_BY_EVENT_AND_TARGET_IDENTIFIER_STATEMENT))
339357
.bindAll(Map.of(EVENT_HASH, eventHash, LISTENER_ID, listenerId))
340358
.fetchAs(TargetEventPublication.class)
341-
.mappedBy(this::mapRecordToPublication)
359+
.mappedBy(incompleteMapping())
342360
.one();
343361
}
344362

@@ -351,7 +369,7 @@ public List<TargetEventPublication> findCompletedPublications() {
351369

352370
return new ArrayList<>(neo4jClient.query(renderer.render(allCompletedStatement))
353371
.fetchAs(TargetEventPublication.class)
354-
.mappedBy(this::mapRecordToPublication)
372+
.mappedBy(completeMapping())
355373
.all());
356374
}
357375

@@ -391,21 +409,31 @@ public void deleteCompletedPublicationsBefore(Instant instant) {
391409
.run();
392410
}
393411

394-
private Neo4jEventPublicationAdapter mapRecordToPublication(TypeSystem typeSystem, org.neo4j.driver.Record record) {
412+
private BiFunction<TypeSystem, org.neo4j.driver.Record, TargetEventPublication> incompleteMapping() {
413+
return (typeSystem, driverRecord) -> mapRecordToPublication(typeSystem, driverRecord, EVENT_PUBLICATION_NODE);
414+
}
415+
416+
private BiFunction<TypeSystem, org.neo4j.driver.Record, TargetEventPublication> completeMapping() {
417+
return (typeSystem, driverRecord) -> mapRecordToPublication(typeSystem, driverRecord, completedNode);
418+
}
419+
420+
private Neo4jEventPublicationAdapter mapRecordToPublication(TypeSystem typeSystem, org.neo4j.driver.Record record,
421+
Node node) {
395422

396-
var publicationNode = record.get(EVENT_PUBLICATION_NODE.getRequiredSymbolicName().getValue()).asNode();
423+
var publicationNode = record.get(node.getRequiredSymbolicName().getValue()).asNode();
397424
var identifier = UUID.fromString(publicationNode.get(ID).asString());
398425
var publicationDate = publicationNode.get(PUBLICATION_DATE).asZonedDateTime().toInstant();
399426
var listenerId = publicationNode.get(LISTENER_ID).asString();
400427
var eventSerialized = publicationNode.get(EVENT_SERIALIZED).asString();
401428
var eventHash = publicationNode.get(EVENT_HASH).asString();
402429
var eventType = publicationNode.get(EVENT_TYPE).asString();
430+
var completionDate = publicationNode.get(COMPLETION_DATE);
403431

404432
try {
405433

406434
var event = eventSerializer.deserialize(eventSerialized, Class.forName(eventType));
407435
var publication = new Neo4jEventPublication(identifier, publicationDate, listenerId, event,
408-
eventHash);
436+
eventHash, completionDate.isNull() ? null : completionDate.asZonedDateTime().toInstant());
409437

410438
return new Neo4jEventPublicationAdapter(publication);
411439

spring-modulith-events/spring-modulith-events-neo4j/src/test/java/org/springframework/modulith/events/neo4j/Neo4jEventPublicationRepositoryTest.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,8 @@
4242
import org.springframework.modulith.events.core.TargetEventPublication;
4343
import org.springframework.modulith.events.support.CompletionMode;
4444
import org.springframework.modulith.testapp.TestApplication;
45-
import org.springframework.test.context.ContextConfiguration;
4645
import org.springframework.test.context.TestPropertySource;
47-
import org.springframework.test.context.bean.override.mockito.MockitoBean;
46+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
4847
import org.springframework.util.DigestUtils;
4948
import org.testcontainers.containers.Neo4jContainer;
5049
import org.testcontainers.junit.jupiter.Container;
@@ -53,7 +52,12 @@
5352

5453
/**
5554
* @author Gerrit Meier
55+
* @author Cora Iberkleid
56+
* @author Oliver Drotbohm
5657
*/
58+
@Testcontainers(disabledWithoutDocker = true)
59+
@SpringJUnitConfig(Neo4jEventPublicationRepositoryTest.Config.class)
60+
@ImportAutoConfiguration(classes = Neo4jEventPublicationAutoConfiguration.class)
5761
class Neo4jEventPublicationRepositoryTest {
5862

5963
static final PublicationTargetIdentifier TARGET_IDENTIFIER = PublicationTargetIdentifier.of("listener");
@@ -62,17 +66,12 @@ class Neo4jEventPublicationRepositoryTest {
6266
static final Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>(DockerImageName.parse("neo4j:5"))
6367
.withRandomPassword();
6468

65-
@Import(TestApplication.class)
66-
@ImportAutoConfiguration({ Neo4jEventPublicationAutoConfiguration.class })
67-
@Testcontainers(disabledWithoutDocker = true)
68-
@ContextConfiguration(classes = Config.class)
6969
static abstract class TestBase {
7070

7171
@Autowired Neo4jEventPublicationRepository repository;
7272
@Autowired Driver driver;
7373
@Autowired Environment environment;
74-
75-
@MockitoBean EventSerializer eventSerializer;
74+
@Autowired EventSerializer eventSerializer;
7675

7776
CompletionMode completionMode;
7877

@@ -335,5 +334,10 @@ Driver driver() {
335334
org.neo4j.cypherdsl.core.renderer.Configuration cypherDslConfiguration() {
336335
return org.neo4j.cypherdsl.core.renderer.Configuration.newConfig().withDialect(Dialect.NEO4J_5).build();
337336
}
337+
338+
@Bean
339+
EventSerializer eventSerializer() {
340+
return mock(EventSerializer.class);
341+
}
338342
}
339343
}

0 commit comments

Comments
 (0)