GH-301 - Polishing.

Formatting, visibility. Some refactorings in Neo4jEventPublicationRepository.
This commit is contained in:
Oliver Drotbohm
2023-09-20 16:53:16 +02:00
parent 4e069b98ea
commit ffb9495e15
12 changed files with 471 additions and 336 deletions

View File

@@ -1,58 +1,58 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0"
<modelVersion>4.0.0</modelVersion> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.modulith</groupId> <groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-events</artifactId> <artifactId>spring-modulith-events</artifactId>
<version>1.1.0-SNAPSHOT</version> <version>1.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<name>Spring Modulith - Events - Neo4j-based repository</name> <name>Spring Modulith - Events - Neo4j-based repository</name>
<artifactId>spring-modulith-events-neo4j</artifactId> <artifactId>spring-modulith-events-neo4j</artifactId>
<properties> <properties>
<module.name>org.springframework.modulith.events.neo4j</module.name> <module.name>org.springframework.modulith.events.neo4j</module.name>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>${project.groupId}</groupId> <groupId>${project.groupId}</groupId>
<artifactId>spring-modulith-events-core</artifactId> <artifactId>spring-modulith-events-core</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.data</groupId> <groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId> <artifactId>spring-data-neo4j</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId> <artifactId>spring-boot-starter-data-neo4j</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.testcontainers</groupId> <groupId>org.testcontainers</groupId>
<artifactId>neo4j</artifactId> <artifactId>neo4j</artifactId>
<version>1.19.0</version> <scope>test</scope>
<scope>test</scope> </dependency>
</dependency>
<dependency> <dependency>
<groupId>org.testcontainers</groupId> <groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId> <artifactId>junit-jupiter</artifactId>
<version>1.19.0</version> <scope>test</scope>
<scope>test</scope> </dependency>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -1,15 +1,30 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.events.neo4j; package org.springframework.modulith.events.neo4j;
import java.time.Instant; import java.time.Instant;
import java.util.UUID; import java.util.UUID;
/** /**
*
* The event publication entity definition. * The event publication entity definition.
* *
* @author Gerrit Meier * @author Gerrit Meier
* @since 1.1
*/ */
public class Neo4jEventPublication { class Neo4jEventPublication {
public final UUID identifier; public final UUID identifier;
public final Instant publicationDate; public final Instant publicationDate;
@@ -19,12 +34,13 @@ public class Neo4jEventPublication {
public Instant completionDate; public Instant completionDate;
public Neo4jEventPublication(UUID identifier, Instant publicationDate, String listenerId, Object event, String eventHash) { public Neo4jEventPublication(UUID identifier, Instant publicationDate, String listenerId, Object event,
String eventHash) {
this.identifier = identifier; this.identifier = identifier;
this.publicationDate = publicationDate; this.publicationDate = publicationDate;
this.listenerId = listenerId; this.listenerId = listenerId;
this.event = event; this.event = event;
this.eventHash = eventHash; this.eventHash = eventHash;
} }
} }

View File

@@ -12,14 +12,19 @@ import org.springframework.modulith.events.config.EventPublicationConfigurationE
import org.springframework.modulith.events.core.EventSerializer; import org.springframework.modulith.events.core.EventSerializer;
/** /**
* Auto-configuration to register a {@link Neo4jEventPublicationRepository}, a default {@link Configuration} and a
* {@link Neo4jIndexInitializer} if enabled.
*
* @author Gerrit Meier * @author Gerrit Meier
* @since 1.1
*/ */
@AutoConfiguration @AutoConfiguration
@AutoConfigureBefore(EventPublicationAutoConfiguration.class) @AutoConfigureBefore(EventPublicationAutoConfiguration.class)
public class Neo4jEventPublicationAutoConfiguration implements EventPublicationConfigurationExtension { class Neo4jEventPublicationAutoConfiguration implements EventPublicationConfigurationExtension {
@Bean @Bean
Neo4jEventPublicationRepository neo4jEventPublicationRepository(Neo4jClient neo4jClient, Configuration cypherDslConfiguration, EventSerializer eventSerializer) { Neo4jEventPublicationRepository neo4jEventPublicationRepository(Neo4jClient neo4jClient,
Configuration cypherDslConfiguration, EventSerializer eventSerializer) {
return new Neo4jEventPublicationRepository(neo4jClient, cypherDslConfiguration, eventSerializer); return new Neo4jEventPublicationRepository(neo4jClient, cypherDslConfiguration, eventSerializer);
} }

View File

@@ -1,21 +1,20 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.events.neo4j; package org.springframework.modulith.events.neo4j;
import org.neo4j.cypherdsl.core.Cypher;
import org.neo4j.cypherdsl.core.Node;
import org.neo4j.cypherdsl.core.Statement;
import org.neo4j.cypherdsl.core.renderer.Configuration;
import org.neo4j.cypherdsl.core.renderer.Renderer;
import org.neo4j.driver.Values;
import org.neo4j.driver.types.TypeSystem;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.modulith.events.core.EventPublicationRepository;
import org.springframework.modulith.events.core.EventSerializer;
import org.springframework.modulith.events.core.PublicationTargetIdentifier;
import org.springframework.modulith.events.core.TargetEventPublication;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.List; import java.util.List;
@@ -24,6 +23,30 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.neo4j.cypherdsl.core.Cypher;
import org.neo4j.cypherdsl.core.Node;
import org.neo4j.cypherdsl.core.ResultStatement;
import org.neo4j.cypherdsl.core.Statement;
import org.neo4j.cypherdsl.core.renderer.Configuration;
import org.neo4j.cypherdsl.core.renderer.Renderer;
import org.neo4j.driver.Values;
import org.neo4j.driver.types.TypeSystem;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.lang.Nullable;
import org.springframework.modulith.events.core.EventPublicationRepository;
import org.springframework.modulith.events.core.EventSerializer;
import org.springframework.modulith.events.core.PublicationTargetIdentifier;
import org.springframework.modulith.events.core.TargetEventPublication;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
/**
* A {@link Neo4jClient} based implementation of {@link EventPublicationRepository}.
*
* @author Gerrit Meier
* @since 1.1
*/
@Transactional @Transactional
class Neo4jEventPublicationRepository implements EventPublicationRepository { class Neo4jEventPublicationRepository implements EventPublicationRepository {
@@ -35,20 +58,75 @@ class Neo4jEventPublicationRepository implements EventPublicationRepository {
private static final String PUBLICATION_DATE = "publicationDate"; private static final String PUBLICATION_DATE = "publicationDate";
private static final String COMPLETION_DATE = "completionDate"; private static final String COMPLETION_DATE = "completionDate";
private static final Node eventPublicationNode = Cypher.node("Neo4jEventPublication").named("neo4jEventPublication"); private static final Node EVENT_PUBLICATION_NODE = Cypher.node("Neo4jEventPublication")
.named("neo4jEventPublication");
private static final Statement INCOMPLETE_BY_EVENT_AND_TARGET_IDENTIFIER_STATEMENT = Cypher
.match(EVENT_PUBLICATION_NODE)
.where(EVENT_PUBLICATION_NODE.property(EVENT_HASH).eq(Cypher.parameter(EVENT_HASH)))
.and(EVENT_PUBLICATION_NODE.property(LISTENER_ID).eq(Cypher.parameter(LISTENER_ID)))
.and(EVENT_PUBLICATION_NODE.property(COMPLETION_DATE).isNull())
.returning(EVENT_PUBLICATION_NODE)
.build();
private static final Statement DELETE_BY_ID_STATEMENT = Cypher.match(EVENT_PUBLICATION_NODE)
.where(EVENT_PUBLICATION_NODE.property(ID).in(Cypher.parameter(ID)))
.delete(EVENT_PUBLICATION_NODE)
.build();
private static final Statement DELETE_COMPLETED_STATEMENT = Cypher.match(EVENT_PUBLICATION_NODE)
.where(EVENT_PUBLICATION_NODE.property(COMPLETION_DATE).isNotNull())
.delete(EVENT_PUBLICATION_NODE)
.build();
private static final Statement DELETE_COMPLETED_BEFORE_STATEMENT = Cypher.match(EVENT_PUBLICATION_NODE)
.where(EVENT_PUBLICATION_NODE.property(PUBLICATION_DATE).lt(Cypher.parameter(PUBLICATION_DATE)))
.and(EVENT_PUBLICATION_NODE.property(COMPLETION_DATE).isNotNull())
.delete(EVENT_PUBLICATION_NODE)
.build();
private static final Statement INCOMPLETE_PUBLISHED_BEFORE_STATEMENT = Cypher
.match(EVENT_PUBLICATION_NODE)
.where(EVENT_PUBLICATION_NODE.property(PUBLICATION_DATE).lt(Cypher.parameter(PUBLICATION_DATE)))
.and(EVENT_PUBLICATION_NODE.property(COMPLETION_DATE).isNull())
.returning(EVENT_PUBLICATION_NODE)
.orderBy(EVENT_PUBLICATION_NODE.property(PUBLICATION_DATE))
.build();
private static final Statement CREATE_STATEMENT = Cypher.create(EVENT_PUBLICATION_NODE)
.set(EVENT_PUBLICATION_NODE.property(ID).to(Cypher.parameter(ID)))
.set(EVENT_PUBLICATION_NODE.property(EVENT_SERIALIZED).to(Cypher.parameter(EVENT_SERIALIZED)))
.set(EVENT_PUBLICATION_NODE.property(EVENT_HASH).to(Cypher.parameter(EVENT_HASH)))
.set(EVENT_PUBLICATION_NODE.property(EVENT_TYPE).to(Cypher.parameter(EVENT_TYPE)))
.set(EVENT_PUBLICATION_NODE.property(LISTENER_ID).to(Cypher.parameter(LISTENER_ID)))
.set(EVENT_PUBLICATION_NODE.property(PUBLICATION_DATE).to(Cypher.parameter(PUBLICATION_DATE)))
.build();
private static final Statement COMPLETE_STATEMENT = Cypher.match(EVENT_PUBLICATION_NODE)
.where(EVENT_PUBLICATION_NODE.property(EVENT_HASH).eq(Cypher.parameter(EVENT_HASH)))
.and(EVENT_PUBLICATION_NODE.property(LISTENER_ID).eq(Cypher.parameter(LISTENER_ID)))
.set(EVENT_PUBLICATION_NODE.property(COMPLETION_DATE).to(Cypher.parameter(COMPLETION_DATE)))
.build();
private static final ResultStatement INCOMPLETE_STATEMENT = Cypher.match(EVENT_PUBLICATION_NODE)
.where(EVENT_PUBLICATION_NODE.property(COMPLETION_DATE).isNull())
.returning(EVENT_PUBLICATION_NODE)
.orderBy(EVENT_PUBLICATION_NODE.property(PUBLICATION_DATE))
.build();
private final Neo4jClient neo4jClient; private final Neo4jClient neo4jClient;
private final Configuration cypherDslConfiguration; private final Renderer renderer;
private final EventSerializer eventSerializer; private final EventSerializer eventSerializer;
Neo4jEventPublicationRepository(Neo4jClient neo4jClient, Configuration cypherDslConfiguration, EventSerializer eventSerializer) { Neo4jEventPublicationRepository(Neo4jClient neo4jClient, Configuration cypherDslConfiguration,
EventSerializer eventSerializer) {
Assert.notNull(neo4jClient, "Neo4jClient must not be null!"); Assert.notNull(neo4jClient, "Neo4jClient must not be null!");
Assert.notNull(cypherDslConfiguration, "CypherDSL configuration must not be null!"); Assert.notNull(cypherDslConfiguration, "CypherDSL configuration must not be null!");
Assert.notNull(eventSerializer, "EventSerializer must not be null!"); Assert.notNull(eventSerializer, "EventSerializer must not be null!");
this.neo4jClient = neo4jClient; this.neo4jClient = neo4jClient;
this.cypherDslConfiguration = cypherDslConfiguration; this.renderer = Renderer.getRenderer(cypherDslConfiguration);
this.eventSerializer = eventSerializer; this.eventSerializer = eventSerializer;
} }
@@ -66,28 +144,18 @@ class Neo4jEventPublicationRepository implements EventPublicationRepository {
var event = publication.getEvent(); var event = publication.getEvent();
var eventType = event.getClass().getName(); var eventType = event.getClass().getName();
var eventSerialized = (String) eventSerializer.serialize(event); var eventSerialized = eventSerializer.serialize(event).toString();
var eventHash = DigestUtils.md5DigestAsHex(eventSerialized.getBytes()); var eventHash = DigestUtils.md5DigestAsHex(eventSerialized.getBytes());
var createStatement = Cypher.create(eventPublicationNode) neo4jClient.query(renderer.render(CREATE_STATEMENT))
.set(eventPublicationNode.property(ID).to(Cypher.parameter(ID))) .bindAll(Map.of(
.set(eventPublicationNode.property(EVENT_SERIALIZED).to(Cypher.parameter(EVENT_SERIALIZED))) ID, Values.value(identifier.toString()),
.set(eventPublicationNode.property(EVENT_HASH).to(Cypher.parameter(EVENT_HASH))) EVENT_SERIALIZED, eventSerialized,
.set(eventPublicationNode.property(EVENT_TYPE).to(Cypher.parameter(EVENT_TYPE))) EVENT_HASH, eventHash,
.set(eventPublicationNode.property(LISTENER_ID).to(Cypher.parameter(LISTENER_ID))) EVENT_TYPE, eventType,
.set(eventPublicationNode.property(PUBLICATION_DATE).to(Cypher.parameter(PUBLICATION_DATE))) LISTENER_ID, listenerId,
.build(); PUBLICATION_DATE, Values.value(publicationDate.atOffset(ZoneOffset.UTC))))
.run();
neo4jClient.query(renderStatement(createStatement))
.bindAll(Map.of(
ID, Values.value(identifier.toString()),
EVENT_SERIALIZED, eventSerialized,
EVENT_HASH, eventHash,
EVENT_TYPE, eventType,
LISTENER_ID, listenerId,
PUBLICATION_DATE, Values.value(publicationDate.atOffset(ZoneOffset.UTC))
))
.run();
return publication; return publication;
} }
@@ -99,19 +167,14 @@ class Neo4jEventPublicationRepository implements EventPublicationRepository {
@Override @Override
@Transactional @Transactional
public void markCompleted(Object event, PublicationTargetIdentifier identifier, Instant completionDate) { public void markCompleted(Object event, PublicationTargetIdentifier identifier, Instant completionDate) {
var eventHash = DigestUtils.md5DigestAsHex(((String) eventSerializer.serialize(event)).getBytes());
var completeStatement = Cypher.match(eventPublicationNode) var eventHash = DigestUtils.md5DigestAsHex(eventSerializer.serialize(event).toString().getBytes());
.where(eventPublicationNode.property(EVENT_HASH).eq(Cypher.parameter(EVENT_HASH)))
.and(eventPublicationNode.property(LISTENER_ID).eq(Cypher.parameter(LISTENER_ID)))
.set(eventPublicationNode.property(COMPLETION_DATE).to(Cypher.parameter(COMPLETION_DATE)))
.build();
neo4jClient.query(renderStatement(completeStatement)) neo4jClient.query(renderer.render(COMPLETE_STATEMENT))
.bind(eventHash).to(EVENT_HASH) .bind(eventHash).to(EVENT_HASH)
.bind(identifier.getValue()).to(LISTENER_ID) .bind(identifier.getValue()).to(LISTENER_ID)
.bind(Values.value(completionDate.atOffset(ZoneOffset.UTC))).to(COMPLETION_DATE) .bind(Values.value(completionDate.atOffset(ZoneOffset.UTC))).to(COMPLETION_DATE)
.run(); .run();
} }
/* /*
@@ -122,99 +185,85 @@ class Neo4jEventPublicationRepository implements EventPublicationRepository {
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<TargetEventPublication> findIncompletePublications() { public List<TargetEventPublication> findIncompletePublications() {
var findIncompleteStatement = Cypher.match(eventPublicationNode) return List.copyOf(neo4jClient.query(renderer.render(INCOMPLETE_STATEMENT))
.where(eventPublicationNode.property(COMPLETION_DATE).isNull()) .fetchAs(TargetEventPublication.class)
.returning(eventPublicationNode) .mappedBy(this::mapRecordToPublication)
.orderBy(eventPublicationNode.property(PUBLICATION_DATE)) .all());
.build();
return List.copyOf(neo4jClient.query(renderStatement(findIncompleteStatement))
.fetchAs(TargetEventPublication.class)
.mappedBy(this::mapRecordToPublication)
.all());
} }
/*
* (non-Javadoc)
* @see org.springframework.modulith.events.core.EventPublicationRepository#findIncompletePublicationsPublishedBefore(java.time.Instant)
*/
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<TargetEventPublication> findIncompletePublicationsPublishedBefore(Instant instant) { public List<TargetEventPublication> findIncompletePublicationsPublishedBefore(Instant instant) {
var findIncompleteStatement = Cypher.match(eventPublicationNode)
.where(eventPublicationNode.property(PUBLICATION_DATE).lt(Cypher.parameter(PUBLICATION_DATE)))
.and(eventPublicationNode.property(COMPLETION_DATE).isNull())
.returning(eventPublicationNode)
.orderBy(eventPublicationNode.property(PUBLICATION_DATE))
.build();
return List.copyOf(neo4jClient.query(renderStatement(findIncompleteStatement))
.bind(Values.value(instant.atOffset(ZoneOffset.UTC))).to(PUBLICATION_DATE)
.fetchAs(TargetEventPublication.class)
.mappedBy(this::mapRecordToPublication)
.all());
return List.copyOf(neo4jClient.query(renderer.render(INCOMPLETE_PUBLISHED_BEFORE_STATEMENT))
.bind(Values.value(instant.atOffset(ZoneOffset.UTC))).to(PUBLICATION_DATE)
.fetchAs(TargetEventPublication.class)
.mappedBy(this::mapRecordToPublication)
.all());
} }
/*
* (non-Javadoc)
* @see org.springframework.modulith.events.core.EventPublicationRepository#findIncompletePublicationsByEventAndTargetIdentifier(java.lang.Object, org.springframework.modulith.events.core.PublicationTargetIdentifier)
*/
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Optional<TargetEventPublication> findIncompletePublicationsByEventAndTargetIdentifier(Object event, PublicationTargetIdentifier targetIdentifier) { public Optional<TargetEventPublication> findIncompletePublicationsByEventAndTargetIdentifier(Object event,
PublicationTargetIdentifier targetIdentifier) {
var eventHash = DigestUtils.md5DigestAsHex(((String) eventSerializer.serialize(event)).getBytes()); var eventHash = DigestUtils.md5DigestAsHex(((String) eventSerializer.serialize(event)).getBytes());
var listenerId = targetIdentifier.getValue(); var listenerId = targetIdentifier.getValue();
var statement = Cypher.match(eventPublicationNode) return neo4jClient.query(renderer.render(INCOMPLETE_BY_EVENT_AND_TARGET_IDENTIFIER_STATEMENT))
.where(eventPublicationNode.property(EVENT_HASH).eq(Cypher.parameter(EVENT_HASH))) .bindAll(Map.of(EVENT_HASH, eventHash, LISTENER_ID, listenerId))
.and(eventPublicationNode.property(LISTENER_ID).eq(Cypher.parameter(LISTENER_ID))) .fetchAs(TargetEventPublication.class)
.and(eventPublicationNode.property(COMPLETION_DATE).isNull()) .mappedBy(this::mapRecordToPublication)
.returning(eventPublicationNode) .one();
.build();
return neo4jClient.query(renderStatement(statement))
.bindAll(Map.of(EVENT_HASH, eventHash, LISTENER_ID, listenerId))
.fetchAs(TargetEventPublication.class)
.mappedBy(this::mapRecordToPublication)
.one();
} }
/*
* (non-Javadoc)
* @see org.springframework.modulith.events.core.EventPublicationRepository#deletePublications(java.util.List)
*/
@Override @Override
@Transactional @Transactional
public void deletePublications(List<UUID> identifiers) { public void deletePublications(List<UUID> identifiers) {
var deleteStatement = Cypher.match(eventPublicationNode)
.where(eventPublicationNode.property(ID).in(Cypher.parameter(ID)))
.delete(eventPublicationNode)
.build();
neo4jClient.query(renderStatement(deleteStatement)) neo4jClient.query(renderer.render(DELETE_BY_ID_STATEMENT))
.bind(identifiers.stream().map(UUID::toString).toList()).to(ID) .bind(identifiers.stream().map(UUID::toString).toList()).to(ID)
.run(); .run();
} }
/*
* (non-Javadoc)
* @see org.springframework.modulith.events.core.EventPublicationRepository#deleteCompletedPublications()
*/
@Override @Override
@Transactional @Transactional
public void deleteCompletedPublications() { public void deleteCompletedPublications() {
var deleteStatement = Cypher.match(eventPublicationNode) neo4jClient.query(renderer.render(DELETE_COMPLETED_STATEMENT)).run();
.where(eventPublicationNode.property(COMPLETION_DATE).isNotNull())
.delete(eventPublicationNode)
.build();
neo4jClient.query(renderStatement(deleteStatement))
.run();
} }
/*
* (non-Javadoc)
* @see org.springframework.modulith.events.core.EventPublicationRepository#deleteCompletedPublicationsBefore(java.time.Instant)
*/
@Override @Override
@Transactional @Transactional
public void deleteCompletedPublicationsBefore(Instant instant) { public void deleteCompletedPublicationsBefore(Instant instant) {
var deleteStatement = Cypher.match(eventPublicationNode)
.where(eventPublicationNode.property(PUBLICATION_DATE).lt(Cypher.parameter(PUBLICATION_DATE)))
.and(eventPublicationNode.property(COMPLETION_DATE).isNotNull())
.delete(eventPublicationNode)
.build();
neo4jClient.query(renderStatement(deleteStatement)) neo4jClient.query(renderer.render(DELETE_COMPLETED_BEFORE_STATEMENT))
.bind(Values.value(instant.atOffset(ZoneOffset.UTC))).to(PUBLICATION_DATE) .bind(Values.value(instant.atOffset(ZoneOffset.UTC))).to(PUBLICATION_DATE)
.run(); .run();
} }
private Neo4jEventPublicationAdapter mapRecordToPublication(TypeSystem typeSystem, org.neo4j.driver.Record record) { private Neo4jEventPublicationAdapter mapRecordToPublication(TypeSystem typeSystem, org.neo4j.driver.Record record) {
var publicationNode = record.get(eventPublicationNode.getRequiredSymbolicName().getValue()).asNode();
var publicationNode = record.get(EVENT_PUBLICATION_NODE.getRequiredSymbolicName().getValue()).asNode();
var identifier = UUID.fromString(publicationNode.get(ID).asString()); var identifier = UUID.fromString(publicationNode.get(ID).asString());
var publicationDate = publicationNode.get(PUBLICATION_DATE).asZonedDateTime().toInstant(); var publicationDate = publicationNode.get(PUBLICATION_DATE).asZonedDateTime().toInstant();
var listenerId = publicationNode.get(LISTENER_ID).asString(); var listenerId = publicationNode.get(LISTENER_ID).asString();
@@ -223,21 +272,19 @@ class Neo4jEventPublicationRepository implements EventPublicationRepository {
var eventType = publicationNode.get(EVENT_TYPE).asString(); var eventType = publicationNode.get(EVENT_TYPE).asString();
try { try {
Object event = eventSerializer.deserialize(eventSerialized, Class.forName(eventType));
Neo4jEventPublication publication = new Neo4jEventPublication(identifier, publicationDate, listenerId, event, eventHash); var event = eventSerializer.deserialize(eventSerialized, Class.forName(eventType));
var publication = new Neo4jEventPublication(identifier, publicationDate, listenerId, event,
eventHash);
return new Neo4jEventPublicationAdapter(publication); return new Neo4jEventPublicationAdapter(publication);
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
private String renderStatement(Statement statement) { private static class Neo4jEventPublicationAdapter implements TargetEventPublication {
return Renderer.getRenderer(cypherDslConfiguration).render(statement);
}
public static class Neo4jEventPublicationAdapter implements TargetEventPublication {
private final Neo4jEventPublication delegate; private final Neo4jEventPublication delegate;
@@ -285,7 +332,7 @@ class Neo4jEventPublicationRepository implements EventPublicationRepository {
* @see java.lang.Object#equals(java.lang.Object) * @see java.lang.Object#equals(java.lang.Object)
*/ */
@Override @Override
public boolean equals(Object obj) { public boolean equals(@Nullable Object obj) {
if (this == obj) { if (this == obj) {
return true; return true;
@@ -307,4 +354,4 @@ class Neo4jEventPublicationRepository implements EventPublicationRepository {
return Objects.hash(delegate); return Objects.hash(delegate);
} }
} }
} }

View File

@@ -1,23 +1,46 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.events.neo4j; package org.springframework.modulith.events.neo4j;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.Neo4jClient;
/** /**
* Automatically creates an index on the {@link Neo4jEventPublication#eventHash} field.
*
* @author Gerrit Meier * @author Gerrit Meier
* @since 1.1
*/ */
public class Neo4jIndexInitializer implements InitializingBean { class Neo4jIndexInitializer implements InitializingBean {
private final Neo4jClient neo4jClient; private final Neo4jClient neo4jClient;
public Neo4jIndexInitializer(Neo4jClient neo4jClient) { Neo4jIndexInitializer(Neo4jClient neo4jClient) {
this.neo4jClient = neo4jClient; this.neo4jClient = neo4jClient;
} }
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
neo4jClient neo4jClient
.query("CREATE INDEX eventHashIndex IF NOT EXISTS FOR (n:`Neo4jEventPublication`) ON (n.eventHash)") .query("CREATE INDEX eventHashIndex IF NOT EXISTS FOR (n:`Neo4jEventPublication`) ON (n.eventHash)")
.run(); .run();
} }
} }

View File

@@ -0,0 +1,5 @@
/**
* Neo4j integration for {@link org.springframework.modulith.events.core.EventPublicationRepository}.
*/
@org.springframework.lang.NonNullApi
package org.springframework.modulith.events.neo4j;

View File

@@ -1,13 +1,21 @@
package org.springframework.modulith.events.neo4j; package org.springframework.modulith.events.neo4j;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import lombok.Value; import lombok.Value;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Dialect;
import org.neo4j.driver.AuthTokens; import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver; import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase; import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.types.Node;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -24,14 +32,6 @@ import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.DockerImageName;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
/** /**
* @author Gerrit Meier * @author Gerrit Meier
*/ */
@@ -39,23 +39,19 @@ import static org.mockito.Mockito.when;
@Testcontainers(disabledWithoutDocker = true) @Testcontainers(disabledWithoutDocker = true)
class Neo4jEventPublicationRepositoryTest { class Neo4jEventPublicationRepositoryTest {
@Container @Container //
private static Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>(DockerImageName.parse("neo4j:5")) static final Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>(DockerImageName.parse("neo4j:5"))
.withRandomPassword(); .withRandomPassword();
static final PublicationTargetIdentifier TARGET_IDENTIFIER = PublicationTargetIdentifier.of("listener"); static final PublicationTargetIdentifier TARGET_IDENTIFIER = PublicationTargetIdentifier.of("listener");
@Autowired @Autowired Neo4jEventPublicationRepository repository;
private Neo4jEventPublicationRepository repository; @Autowired Driver driver;
@MockBean EventSerializer eventSerializer;
@Autowired
private Driver driver;
@MockBean
private EventSerializer eventSerializer;
@BeforeEach @BeforeEach
void clearDb() { void clearDb() {
try (var session = driver.session()) { try (var session = driver.session()) {
session.run("MATCH (n) detach delete n").consume(); session.run("MATCH (n) detach delete n").consume();
} }
@@ -63,6 +59,7 @@ class Neo4jEventPublicationRepositoryTest {
@Test @Test
void createEventPublication() { void createEventPublication() {
var testEvent = new TestEvent("id"); var testEvent = new TestEvent("id");
var eventSerialized = "{\"eventId\":\"id\"}"; var eventSerialized = "{\"eventId\":\"id\"}";
var eventHash = DigestUtils.md5DigestAsHex(eventSerialized.getBytes()); var eventHash = DigestUtils.md5DigestAsHex(eventSerialized.getBytes());
@@ -71,22 +68,27 @@ class Neo4jEventPublicationRepositoryTest {
var publication = repository.create(TargetEventPublication.of(testEvent, TARGET_IDENTIFIER)); var publication = repository.create(TargetEventPublication.of(testEvent, TARGET_IDENTIFIER));
try (var session = driver.session()) { try (var session = driver.session()) {
var result = session.run("MATCH (p:Neo4jEventPublication) return p")
.single();
Node neo4jEventPublicationNode = result.get("p").asNode(); var result = session.run("MATCH (p:Neo4jEventPublication) return p")
assertThat(UUID.fromString(neo4jEventPublicationNode.get("identifier").asString())).isEqualTo(publication.getIdentifier()); .single();
assertThat(neo4jEventPublicationNode.get("publicationDate").asZonedDateTime().toInstant()).isEqualTo(publication.getPublicationDate());
assertThat(neo4jEventPublicationNode.get("listenerId").asString()).isEqualTo(publication.getTargetIdentifier().getValue()); var neo4jEventPublicationNode = result.get("p").asNode();
assertThat(UUID.fromString(neo4jEventPublicationNode.get("identifier").asString()))
.isEqualTo(publication.getIdentifier());
assertThat(neo4jEventPublicationNode.get("publicationDate").asZonedDateTime().toInstant())
.isEqualTo(publication.getPublicationDate());
assertThat(neo4jEventPublicationNode.get("listenerId").asString())
.isEqualTo(publication.getTargetIdentifier().getValue());
assertThat(neo4jEventPublicationNode.get("completionDate").isNull()).isTrue(); assertThat(neo4jEventPublicationNode.get("completionDate").isNull()).isTrue();
assertThat(neo4jEventPublicationNode.get("eventSerialized").asString()).isEqualTo(eventSerialized); assertThat(neo4jEventPublicationNode.get("eventSerialized").asString()).isEqualTo(eventSerialized);
assertThat(neo4jEventPublicationNode.get("eventHash").asString()).isEqualTo(eventHash); assertThat(neo4jEventPublicationNode.get("eventHash").asString()).isEqualTo(eventHash);
} }
} }
@Test @Test
void updateEventPublication() { void updateEventPublication() {
var testEvent1 = new TestEvent("id1"); var testEvent1 = new TestEvent("id1");
var event1Serialized = "{\"eventId\":\"id1\"}"; var event1Serialized = "{\"eventId\":\"id1\"}";
var testEvent2 = new TestEvent("id2"); var testEvent2 = new TestEvent("id2");
@@ -103,12 +105,13 @@ class Neo4jEventPublicationRepositoryTest {
repository.markCompleted(event1, now); repository.markCompleted(event1, now);
assertThat(repository.findIncompletePublications()).hasSize(1) assertThat(repository.findIncompletePublications()).hasSize(1)
.element(0) .element(0)
.extracting(TargetEventPublication::getEvent).isEqualTo(event2.getEvent()); .extracting(TargetEventPublication::getEvent).isEqualTo(event2.getEvent());
} }
@Test @Test
void findInCompletePastPublications() { void findInCompletePastPublications() {
var testEvent = new TestEvent("id"); var testEvent = new TestEvent("id");
var eventSerialized = "{\"eventId\":\"id\"}"; var eventSerialized = "{\"eventId\":\"id\"}";
@@ -121,14 +124,15 @@ class Neo4jEventPublicationRepositoryTest {
var older = Instant.now().minus(1L, ChronoUnit.MINUTES); var older = Instant.now().minus(1L, ChronoUnit.MINUTES);
assertThat(repository.findIncompletePublicationsPublishedBefore(newer)).hasSize(1) assertThat(repository.findIncompletePublicationsPublishedBefore(newer)).hasSize(1)
.element(0) .element(0)
.extracting(TargetEventPublication::getEvent).isEqualTo(event.getEvent()); .extracting(TargetEventPublication::getEvent).isEqualTo(event.getEvent());
assertThat(repository.findIncompletePublicationsPublishedBefore(older)).hasSize(0); assertThat(repository.findIncompletePublicationsPublishedBefore(older)).hasSize(0);
} }
@Test @Test
void findIncompleteByEventAndTargetIdentifier() { void findIncompleteByEventAndTargetIdentifier() {
var testEvent = new TestEvent("id"); var testEvent = new TestEvent("id");
var eventSerialized = "{\"eventId\":\"id\"}"; var eventSerialized = "{\"eventId\":\"id\"}";
@@ -138,13 +142,15 @@ class Neo4jEventPublicationRepositoryTest {
var event = repository.create(TargetEventPublication.of(testEvent, TARGET_IDENTIFIER)); var event = repository.create(TargetEventPublication.of(testEvent, TARGET_IDENTIFIER));
assertThat(repository.findIncompletePublicationsByEventAndTargetIdentifier(testEvent, event.getTargetIdentifier())) assertThat(repository.findIncompletePublicationsByEventAndTargetIdentifier(testEvent, event.getTargetIdentifier()))
.isPresent(); .isPresent();
} }
@Test @Test
void deletePublicationById() { void deletePublicationById() {
TestEvent testEvent = new TestEvent("id");
var testEvent = new TestEvent("id");
var eventSerialized = "{\"eventId\":\"id\"}"; var eventSerialized = "{\"eventId\":\"id\"}";
when(eventSerializer.serialize(testEvent)).thenReturn(eventSerialized); when(eventSerializer.serialize(testEvent)).thenReturn(eventSerialized);
var event = repository.create(TargetEventPublication.of(testEvent, TARGET_IDENTIFIER)); var event = repository.create(TargetEventPublication.of(testEvent, TARGET_IDENTIFIER));
@@ -156,9 +162,10 @@ class Neo4jEventPublicationRepositoryTest {
@Test @Test
void deleteCompletedPublications() { void deleteCompletedPublications() {
TestEvent testEvent1 = new TestEvent("id1");
var testEvent1 = new TestEvent("id1");
var event1Serialized = "{\"eventId\":\"id1\"}"; var event1Serialized = "{\"eventId\":\"id1\"}";
TestEvent testEvent2 = new TestEvent("id2"); var testEvent2 = new TestEvent("id2");
var event2Serialized = "{\"eventId\":\"id2\"}"; var event2Serialized = "{\"eventId\":\"id2\"}";
when(eventSerializer.serialize(testEvent1)).thenReturn(event1Serialized); when(eventSerializer.serialize(testEvent1)).thenReturn(event1Serialized);
@@ -173,16 +180,18 @@ class Neo4jEventPublicationRepositoryTest {
repository.deleteCompletedPublications(); repository.deleteCompletedPublications();
try (var session = driver.session()) { try (var session = driver.session()) {
var count = session.run("MATCH (n) WHERE n.completionDate is not null return count(n)").single().get("count(n)").asLong(); var count = session.run("MATCH (n) WHERE n.completionDate is not null return count(n)").single().get("count(n)")
.asLong();
assertThat(count).isEqualTo(0); assertThat(count).isEqualTo(0);
} }
} }
@Test @Test
void deleteCompletedPublicationsBefore() throws Exception { void deleteCompletedPublicationsBefore() throws Exception {
TestEvent testEvent1 = new TestEvent("id1");
var testEvent1 = new TestEvent("id1");
var event1Serialized = "{\"eventId\":\"id1\"}"; var event1Serialized = "{\"eventId\":\"id1\"}";
TestEvent testEvent2 = new TestEvent("id2"); var testEvent2 = new TestEvent("id2");
var event2Serialized = "{\"eventId\":\"id2\"}"; var event2Serialized = "{\"eventId\":\"id2\"}";
when(eventSerializer.serialize(testEvent1)).thenReturn(event1Serialized); when(eventSerializer.serialize(testEvent1)).thenReturn(event1Serialized);
@@ -203,27 +212,31 @@ class Neo4jEventPublicationRepositoryTest {
assertThat(repository.findIncompletePublications()).hasSize(0); assertThat(repository.findIncompletePublications()).hasSize(0);
try (var session = driver.session()) { try (var session = driver.session()) {
var records = session.run("MATCH (n) WHERE n.completionDate is not null return n").list(); var records = session.run("MATCH (n) WHERE n.completionDate is not null return n").list();
assertThat(records.size()).isEqualTo(1); assertThat(records.size()).isEqualTo(1);
assertThat(records.get(0).get("n").asNode().get("eventSerialized").asString()).contains("id2"); assertThat(records.get(0).get("n").asNode().get("eventSerialized").asString()).contains("id2");
} }
} }
@Value @Value
private static final class TestEvent { static class TestEvent {
String eventId; String eventId;
} }
@Import({TestApplication.class}) @Import({ TestApplication.class })
@Configuration @Configuration
static class Config { static class Config {
@Bean @Bean
public Driver driver() { Driver driver() {
return GraphDatabase.driver(neo4jContainer.getBoltUrl(), AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword())); return GraphDatabase.driver(neo4jContainer.getBoltUrl(),
AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword()));
} }
@Bean @Bean
public org.neo4j.cypherdsl.core.renderer.Configuration cypherDslConfiguration() { org.neo4j.cypherdsl.core.renderer.Configuration cypherDslConfiguration() {
return org.neo4j.cypherdsl.core.renderer.Configuration.newConfig().withDialect(Dialect.NEO4J_5).build(); return org.neo4j.cypherdsl.core.renderer.Configuration.newConfig().withDialect(Dialect.NEO4J_5).build();
} }
} }

View File

@@ -1,5 +1,24 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.events.neo4j; package org.springframework.modulith.events.neo4j;
import static org.assertj.core.api.Assertions.*;
import java.util.Optional;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.neo4j.driver.AuthTokens; import org.neo4j.driver.AuthTokens;
@@ -20,34 +39,28 @@ import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.DockerImageName;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
/** /**
* @author Gerrit Meier * @author Gerrit Meier
*/ */
public class Neo4jIndexInitializerTest { class Neo4jIndexInitializerTest {
@ImportAutoConfiguration({Neo4jEventPublicationAutoConfiguration.class, TestBase.Config.class}) @ImportAutoConfiguration({ Neo4jEventPublicationAutoConfiguration.class, TestBase.Config.class })
@Testcontainers(disabledWithoutDocker = true) @Testcontainers(disabledWithoutDocker = true)
@ContextConfiguration(classes = TestApplication.class) @ContextConfiguration(classes = TestApplication.class)
static class TestBase { static class TestBase {
@Container @Container private static Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>(DockerImageName.parse("neo4j:5"))
private static Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>(DockerImageName.parse("neo4j:5")) .withRandomPassword();
.withRandomPassword();
@MockBean
EventSerializer eventSerializer;
@MockBean EventSerializer eventSerializer;
@Configuration @Configuration
static class Config { static class Config {
@Bean @Bean
Driver driver() { Driver driver() {
return GraphDatabase.driver(neo4jContainer.getBoltUrl(), AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword())); return GraphDatabase.driver(neo4jContainer.getBoltUrl(),
AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword()));
} }
} }
} }
@@ -55,11 +68,9 @@ public class Neo4jIndexInitializerTest {
@Nested @Nested
@DataNeo4jTest(properties = "spring.modulith.events.neo4j.event-index.enabled=true") @DataNeo4jTest(properties = "spring.modulith.events.neo4j.event-index.enabled=true")
class WithIndexEnabled extends TestBase { class WithIndexEnabled extends TestBase {
@Autowired
Neo4jClient neo4jClient;
@Autowired @Autowired Neo4jClient neo4jClient;
Optional<Neo4jIndexInitializer> neo4jIndexInitializer; @Autowired Optional<Neo4jIndexInitializer> neo4jIndexInitializer;
@Test @Test
void indexInitializerBeanIsPresent() { void indexInitializerBeanIsPresent() {
@@ -69,19 +80,17 @@ public class Neo4jIndexInitializerTest {
@Test @Test
void indexWasCreated() { void indexWasCreated() {
assertThat(neo4jClient.query("SHOW INDEX YIELD name") assertThat(neo4jClient.query("SHOW INDEX YIELD name")
.fetchAs(String.class) .fetchAs(String.class)
.all()).contains("eventHashIndex"); .all()).contains("eventHashIndex");
} }
} }
@Nested @Nested
@DataNeo4jTest(properties = "spring.modulith.events.neo4j.event-index.enabled=false") @DataNeo4jTest(properties = "spring.modulith.events.neo4j.event-index.enabled=false")
class WithoutIndexEnabled extends TestBase { class WithoutIndexEnabled extends TestBase {
@Autowired
Neo4jClient neo4jClient;
@Autowired @Autowired Neo4jClient neo4jClient;
Optional<Neo4jIndexInitializer> neo4jIndexInitializer; @Autowired Optional<Neo4jIndexInitializer> neo4jIndexInitializer;
@Test @Test
void indexInitializerBeanIsNotPresent() { void indexInitializerBeanIsNotPresent() {
@@ -91,9 +100,8 @@ public class Neo4jIndexInitializerTest {
@Test @Test
void indexWasNotCreated() { void indexWasNotCreated() {
assertThat(neo4jClient.query("SHOW INDEX YIELD name") assertThat(neo4jClient.query("SHOW INDEX YIELD name")
.fetchAs(String.class) .fetchAs(String.class)
.all()).doesNotContain("eventHashIndex"); .all()).doesNotContain("eventHashIndex");
} }
} }
} }

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.testapp; package org.springframework.modulith.testapp;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -6,5 +21,4 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* @author Gerrit Meier * @author Gerrit Meier
*/ */
@SpringBootApplication @SpringBootApplication
public class TestApplication { public class TestApplication {}
}

View File

@@ -1,56 +1,58 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.modulith</groupId> <groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-examples</artifactId> <artifactId>spring-modulith-examples</artifactId>
<version>1.1.0-SNAPSHOT</version> <version>1.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<artifactId>spring-modulith-example-epr-neo4j</artifactId> <artifactId>spring-modulith-example-epr-neo4j</artifactId>
<name>Spring Modulith - Examples - EPR Neo4j Example</name> <name>Spring Modulith - Examples - EPR Neo4j Example</name>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.springframework.modulith</groupId> <groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-starter-neo4j</artifactId> <artifactId>spring-modulith-starter-neo4j</artifactId>
</dependency> </dependency>
<!-- jMolecules --> <!-- jMolecules -->
<dependency> <dependency>
<groupId>org.jmolecules</groupId> <groupId>org.jmolecules</groupId>
<artifactId>jmolecules-events</artifactId> <artifactId>jmolecules-events</artifactId>
</dependency> </dependency>
<!-- Persistence --> <!-- Persistence -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId> <artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.testcontainers</groupId> <groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId> <artifactId>junit-jupiter</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.testcontainers</groupId> <groupId>org.testcontainers</groupId>
<artifactId>neo4j</artifactId> <artifactId>neo4j</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId> <artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -17,6 +17,9 @@ package example;
import example.inventory.InventoryUpdated; import example.inventory.InventoryUpdated;
import example.order.OrderManagement; import example.order.OrderManagement;
import java.util.Collection;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.neo4j.cypherdsl.core.renderer.Configuration; import org.neo4j.cypherdsl.core.renderer.Configuration;
import org.neo4j.cypherdsl.core.renderer.Dialect; import org.neo4j.cypherdsl.core.renderer.Dialect;
@@ -34,8 +37,6 @@ import org.springframework.modulith.test.Scenario;
import org.testcontainers.containers.Neo4jContainer; import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.Collection;
/** /**
* @author Oliver Drotbohm * @author Oliver Drotbohm
* @author Gerrit Meier * @author Gerrit Meier
@@ -46,7 +47,7 @@ import java.util.Collection;
class ApplicationIntegrationTests { class ApplicationIntegrationTests {
@TestConfiguration @TestConfiguration
static class MongoDbInfrastructureConfiguration { static class InfrastructureConfiguration {
@Bean @Bean
@ServiceConnection @ServiceConnection
@@ -55,12 +56,12 @@ class ApplicationIntegrationTests {
} }
@Bean @Bean
public Driver driver(Neo4jContainer<?> container) { Driver driver(Neo4jContainer<?> container) {
return GraphDatabase.driver(container.getBoltUrl(), AuthTokens.basic("neo4j", container.getAdminPassword())); return GraphDatabase.driver(container.getBoltUrl(), AuthTokens.basic("neo4j", container.getAdminPassword()));
} }
@Bean @Bean
public Configuration cypherDslConfiguration() { Configuration cypherDslConfiguration() {
return Configuration.newConfig().withDialect(Dialect.NEO4J_5).build(); return Configuration.newConfig().withDialect(Dialect.NEO4J_5).build();
} }
} }

View File

@@ -1,54 +1,55 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.modulith</groupId> <groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-starters</artifactId> <artifactId>spring-modulith-starters</artifactId>
<version>1.1.0-SNAPSHOT</version> <version>1.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<artifactId>spring-modulith-starter-neo4j</artifactId> <artifactId>spring-modulith-starter-neo4j</artifactId>
<name>Spring Modulith - Starters - Starter Neo4j</name> <name>Spring Modulith - Starters - Starter Neo4j</name>
<properties> <properties>
<module.name>org.springframework.modulith.starter.neo4j</module.name> <module.name>org.springframework.modulith.starter.neo4j</module.name>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.springframework.modulith</groupId> <groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-starter-core</artifactId> <artifactId>spring-modulith-starter-core</artifactId>
<version>1.1.0-SNAPSHOT</version> <version>1.1.0-SNAPSHOT</version>
</dependency> </dependency>
<!-- Events --> <!-- Events -->
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-events-api</artifactId>
<version>1.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-events-core</artifactId>
<version>1.1.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-events-jackson</artifactId>
<version>1.1.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-events-neo4j</artifactId>
<version>1.1.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
</dependencies> <dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-events-api</artifactId>
<version>1.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-events-core</artifactId>
<version>1.1.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-events-jackson</artifactId>
<version>1.1.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-events-neo4j</artifactId>
<version>1.1.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project> </project>