GH-3 - Implement JDBC based repository
Signed-off-by: Björn Kieling <bkieling@vmware.com>
This commit is contained in:
committed by
Bjoern Kieling
parent
913ec7bb4e
commit
6a4260471e
@@ -16,6 +16,7 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<module>spring-modulith-events-core</module>
|
<module>spring-modulith-events-core</module>
|
||||||
<module>spring-modulith-events-jpa</module>
|
<module>spring-modulith-events-jpa</module>
|
||||||
|
<module>spring-modulith-events-jdbc</module>
|
||||||
<module>spring-modulith-events-jackson</module>
|
<module>spring-modulith-events-jackson</module>
|
||||||
<module>spring-modulith-events-tests</module>
|
<module>spring-modulith-events-tests</module>
|
||||||
<module>spring-modulith-events-starter</module>
|
<module>spring-modulith-events-starter</module>
|
||||||
|
|||||||
93
spring-modulith-events/spring-modulith-events-jdbc/pom.xml
Normal file
93
spring-modulith-events/spring-modulith-events-jdbc/pom.xml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<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">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.experimental</groupId>
|
||||||
|
<artifactId>spring-modulith-events</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<name>Spring Modulith - Events - JDBC-based registry</name>
|
||||||
|
<artifactId>spring-modulith-events-jdbc</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<module.name>org.springframework.modulith.events.jdbc</module.name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>${project.groupId}</groupId>
|
||||||
|
<artifactId>spring-modulith-events-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.data</groupId>
|
||||||
|
<artifactId>spring-data-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!--
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Testing -->
|
||||||
|
|
||||||
|
<!-- <dependency>
|
||||||
|
<groupId>jakarta.xml.bind</groupId>
|
||||||
|
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>-->
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.transaction</groupId>
|
||||||
|
<artifactId>jakarta.transaction-api</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hsqldb</groupId>
|
||||||
|
<artifactId>hsqldb</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>spring-milestone</id>
|
||||||
|
<url>https://repo.spring.io/milestone</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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.jdbc;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.ResourceLoaderAware;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.support.MetaDataAccessException;
|
||||||
|
import org.springframework.util.FileCopyUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the DB schema used to store events
|
||||||
|
*
|
||||||
|
* @author Dmitry Belyaev, Björn Kieling
|
||||||
|
*/
|
||||||
|
public class DatabaseSchemaInitializer implements ResourceLoaderAware, InitializingBean {
|
||||||
|
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
private ResourceLoader resourceLoader;
|
||||||
|
|
||||||
|
@Value("${spring.modulith.events.schema-initialization.enabled:false}")
|
||||||
|
private boolean initEnabled;
|
||||||
|
|
||||||
|
public DatabaseSchemaInitializer(JdbcTemplate jdbcTemplate) {
|
||||||
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||||
|
this.resourceLoader = resourceLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() throws MetaDataAccessException {
|
||||||
|
if (!initEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseType databaseType = DatabaseType.fromMetaData(jdbcTemplate.getDataSource());
|
||||||
|
String databaseName = databaseType.name().toLowerCase();
|
||||||
|
var schemaDdlResource = resourceLoader.getResource("/schema-" + databaseName + ".sql");
|
||||||
|
var schemaDdl = asString(schemaDdlResource);
|
||||||
|
jdbcTemplate.execute(schemaDdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String asString(Resource resource) {
|
||||||
|
try (Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) {
|
||||||
|
return FileCopyUtils.copyToString(reader);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2006-2022 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.jdbc;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.support.JdbcUtils;
|
||||||
|
import org.springframework.jdbc.support.MetaDataAccessException;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing a database type, such as DB2 or oracle. The type also contains a
|
||||||
|
* product name, which is expected to be the same as the product name provided by the
|
||||||
|
* database driver's metadata.
|
||||||
|
*
|
||||||
|
* @author Lucas Ward
|
||||||
|
*/
|
||||||
|
public enum DatabaseType {
|
||||||
|
|
||||||
|
DERBY("Apache Derby"), DB2("DB2"), DB2VSE("DB2VSE"), DB2ZOS("DB2ZOS"), DB2AS400("DB2AS400"),
|
||||||
|
HSQL("HSQL Database Engine"), SQLSERVER("Microsoft SQL Server"), MYSQL("MySQL"), ORACLE("Oracle"),
|
||||||
|
POSTGRES("PostgreSQL"), SYBASE("Sybase"), H2("H2"), SQLITE("SQLite"), HANA("HDB");
|
||||||
|
|
||||||
|
private static final Map<String, DatabaseType> nameMap;
|
||||||
|
|
||||||
|
static {
|
||||||
|
nameMap = new HashMap<>();
|
||||||
|
for (DatabaseType type : values()) {
|
||||||
|
nameMap.put(type.getProductName(), type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A description is necessary due to the nature of database descriptions
|
||||||
|
// in metadata.
|
||||||
|
private final String productName;
|
||||||
|
|
||||||
|
private DatabaseType(String productName) {
|
||||||
|
this.productName = productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductName() {
|
||||||
|
return productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static method to obtain a DatabaseType from the provided product name.
|
||||||
|
* @param productName {@link String} containing the product name.
|
||||||
|
* @return the {@link DatabaseType} for given product name.
|
||||||
|
* @throws IllegalArgumentException if none is found.
|
||||||
|
*/
|
||||||
|
public static DatabaseType fromProductName(String productName) {
|
||||||
|
if (productName.equals("MariaDB"))
|
||||||
|
productName = "MySQL";
|
||||||
|
if (!nameMap.containsKey(productName)) {
|
||||||
|
throw new IllegalArgumentException("DatabaseType not found for product name: [" + productName + "]");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nameMap.get(productName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method that pulls a database product name from the DataSource's
|
||||||
|
* metadata.
|
||||||
|
* @param dataSource {@link DataSource} to the database to be used.
|
||||||
|
* @return {@link DatabaseType} for the {@link DataSource} specified.
|
||||||
|
* @throws MetaDataAccessException thrown if error occured during Metadata lookup.
|
||||||
|
*/
|
||||||
|
public static DatabaseType fromMetaData(DataSource dataSource) throws MetaDataAccessException {
|
||||||
|
String databaseProductName = JdbcUtils.extractDatabaseMetaData(dataSource,
|
||||||
|
DatabaseMetaData::getDatabaseProductName);
|
||||||
|
if (StringUtils.hasText(databaseProductName) && databaseProductName.startsWith("DB2")) {
|
||||||
|
String databaseProductVersion = JdbcUtils.extractDatabaseMetaData(dataSource,
|
||||||
|
DatabaseMetaData::getDatabaseProductVersion);
|
||||||
|
if (databaseProductVersion.startsWith("ARI")) {
|
||||||
|
databaseProductName = "DB2VSE";
|
||||||
|
}
|
||||||
|
else if (databaseProductVersion.startsWith("DSN")) {
|
||||||
|
databaseProductName = "DB2ZOS";
|
||||||
|
}
|
||||||
|
else if (databaseProductName.contains("AS")
|
||||||
|
&& (databaseProductVersion.startsWith("QSQ") || databaseProductVersion
|
||||||
|
.substring(databaseProductVersion.indexOf('V')).matches("V\\dR\\d[mM]\\d"))) {
|
||||||
|
databaseProductName = "DB2AS400";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
databaseProductName = JdbcUtils.commonDatabaseName(databaseProductName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
databaseProductName = JdbcUtils.commonDatabaseName(databaseProductName);
|
||||||
|
}
|
||||||
|
return fromProductName(databaseProductName);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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.jdbc;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.modulith.events.EventSerializer;
|
||||||
|
import org.springframework.modulith.events.config.EventPublicationConfigurationExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Belyaev, Björn Kieling
|
||||||
|
*/
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
class JdbcEventPublicationAutoConfiguration implements EventPublicationConfigurationExtension {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JdbcEventPublicationRepository jpaEventPublicationRepository(
|
||||||
|
JdbcTemplate jdbcTemplate, EventSerializer serializer) {
|
||||||
|
// TODO Why do we want to instantiate the serializer here and what
|
||||||
|
// happens if no serializer is available or is not compatible to
|
||||||
|
// JDBC serialization?
|
||||||
|
return new JdbcEventPublicationRepository(jdbcTemplate, serializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DatabaseSchemaInitializer databaseSchemaInitializer(JdbcTemplate jdbcTemplate) {
|
||||||
|
return new DatabaseSchemaInitializer(jdbcTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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.jdbc;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.modulith.events.CompletableEventPublication;
|
||||||
|
import org.springframework.modulith.events.EventPublication;
|
||||||
|
import org.springframework.modulith.events.EventPublicationRepository;
|
||||||
|
import org.springframework.modulith.events.EventSerializer;
|
||||||
|
import org.springframework.modulith.events.PublicationTargetIdentifier;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository to store {@link EventPublication}s.
|
||||||
|
*
|
||||||
|
* @author Dmitry Belyaev, Björn Kieling
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JdbcEventPublicationRepository implements EventPublicationRepository {
|
||||||
|
|
||||||
|
private static final String SQL_STATEMENT_INSERT = """
|
||||||
|
INSERT INTO EVENT_PUBLICATION (ID, EVENT_TYPE, LISTENER_ID, PUBLICATION_DATE, SERIALIZED_EVENT)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
""";
|
||||||
|
private static final String SQL_STATEMENT_FIND_UNCOMPLETED = """
|
||||||
|
SELECT ID, COMPLETION_DATE, EVENT_TYPE, LISTENER_ID, PUBLICATION_DATE, SERIALIZED_EVENT
|
||||||
|
FROM EVENT_PUBLICATION
|
||||||
|
WHERE COMPLETION_DATE IS NULL
|
||||||
|
""";
|
||||||
|
private static final String SQL_STATEMENT_UPDATE = """
|
||||||
|
UPDATE EVENT_PUBLICATION
|
||||||
|
SET COMPLETION_DATE = ?
|
||||||
|
WHERE ID = ?
|
||||||
|
""";
|
||||||
|
private static final String SQL_STATEMENT_FIND_BY_EVENT_AND_LISTENER_ID = """
|
||||||
|
SELECT * FROM EVENT_PUBLICATION
|
||||||
|
WHERE SERIALIZED_EVENT = ? AND LISTENER_ID = ?
|
||||||
|
ORDER BY PUBLICATION_DATE
|
||||||
|
""";
|
||||||
|
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
private final EventSerializer serializer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public EventPublication create(EventPublication publication) {
|
||||||
|
String serializedEvent = serializeEvent(publication.getEvent());
|
||||||
|
jdbcTemplate.update(SQL_STATEMENT_INSERT, //
|
||||||
|
UUID.randomUUID(), //
|
||||||
|
publication.getEvent().getClass().getName(), //
|
||||||
|
publication.getTargetIdentifier().getValue(), //
|
||||||
|
publication.getPublicationDate(), //
|
||||||
|
serializedEvent);
|
||||||
|
|
||||||
|
return publication;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public EventPublication updateCompletionDate(CompletableEventPublication publication) {
|
||||||
|
String serializedEvent = serializeEvent(publication.getEvent());
|
||||||
|
List<UUID> results = jdbcTemplate.query(SQL_STATEMENT_FIND_BY_EVENT_AND_LISTENER_ID,
|
||||||
|
(rs, rowNum) -> rs.getObject("ID", UUID.class), serializedEvent, publication.getTargetIdentifier().getValue());
|
||||||
|
if (!results.isEmpty()) {
|
||||||
|
jdbcTemplate.update(SQL_STATEMENT_UPDATE, publication.getCompletionDate().orElse(null), results.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return publication;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<EventPublication> findByCompletionDateIsNull() {
|
||||||
|
return jdbcTemplate.query(SQL_STATEMENT_FIND_UNCOMPLETED, this::mapResultSetToEventPublications);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<EventPublication> findByEventAndTargetIdentifier(Object event,
|
||||||
|
PublicationTargetIdentifier targetIdentifier) {
|
||||||
|
|
||||||
|
String serializedEvent = serializeEvent(event);
|
||||||
|
List<EventPublication> results = jdbcTemplate.query(SQL_STATEMENT_FIND_BY_EVENT_AND_LISTENER_ID,
|
||||||
|
this::mapResultSetToEventPublications, serializedEvent, targetIdentifier.getValue());
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
|
} else {
|
||||||
|
// if there are several events with exactly the same payload we return the oldest one first
|
||||||
|
return Optional.of(results.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String serializeEvent(Object event) {
|
||||||
|
return serializer.serialize(event).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EventPublication> mapResultSetToEventPublications(ResultSet rs) throws SQLException {
|
||||||
|
var result = new ArrayList<EventPublication>();
|
||||||
|
while (rs.next()) {
|
||||||
|
entityToDomain(rs).ifPresent(result::add);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<EventPublication> entityToDomain(ResultSet rs) throws SQLException {
|
||||||
|
var id = rs.getObject("ID", UUID.class);
|
||||||
|
var eventClassName = rs.getString("EVENT_TYPE");
|
||||||
|
Class<?> eventClass;
|
||||||
|
try {
|
||||||
|
eventClass = Class.forName(eventClassName);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
LOG.warn("Event '{}' of unknown type '{}' found", id, eventClassName);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(JdbcEventPublication.builder()
|
||||||
|
.completionDate(Optional.ofNullable(rs.getTimestamp("COMPLETION_DATE")).map(Timestamp::toInstant).orElse(null))
|
||||||
|
.eventType(eventClass) //
|
||||||
|
.listenerId(rs.getString("LISTENER_ID")) //
|
||||||
|
.publicationDate(rs.getTimestamp("PUBLICATION_DATE").toInstant()) //
|
||||||
|
.serializedEvent(rs.getString("SERIALIZED_EVENT")) //
|
||||||
|
.serializer(serializer) //
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@Builder
|
||||||
|
private static class JdbcEventPublication implements CompletableEventPublication {
|
||||||
|
|
||||||
|
private final UUID id;
|
||||||
|
private final Instant publicationDate;
|
||||||
|
private final String listenerId;
|
||||||
|
private final String serializedEvent;
|
||||||
|
private final Class<?> eventType;
|
||||||
|
|
||||||
|
private final EventSerializer serializer;
|
||||||
|
private Instant completionDate;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getEvent() {
|
||||||
|
return serializer.deserialize(serializedEvent, eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PublicationTargetIdentifier getTargetIdentifier() {
|
||||||
|
return PublicationTargetIdentifier.of(listenerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant getPublicationDate() {
|
||||||
|
return publicationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Instant> getCompletionDate() {
|
||||||
|
return Optional.ofNullable(completionDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPublicationCompleted() {
|
||||||
|
return completionDate != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableEventPublication markCompleted() {
|
||||||
|
this.completionDate = Instant.now();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.springframework.modulith.events.jdbc.JdbcEventPublicationAutoConfiguration
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS EVENT_PUBLICATION
|
||||||
|
(
|
||||||
|
ID UUID NOT NULL,
|
||||||
|
COMPLETION_DATE TIMESTAMP(9) WITH TIME ZONE,
|
||||||
|
EVENT_TYPE VARCHAR(512) NOT NULL,
|
||||||
|
LISTENER_ID VARCHAR(512) NOT NULL,
|
||||||
|
PUBLICATION_DATE TIMESTAMP(9) WITH TIME ZONE NOT NULL,
|
||||||
|
SERIALIZED_EVENT VARCHAR(4000) NOT NULL,
|
||||||
|
PRIMARY KEY (ID)
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS EVENT_PUBLICATION
|
||||||
|
(
|
||||||
|
ID UUID NOT NULL,
|
||||||
|
COMPLETION_DATE TIMESTAMP(9),
|
||||||
|
EVENT_TYPE VARCHAR(512) NOT NULL,
|
||||||
|
LISTENER_ID VARCHAR(512) NOT NULL,
|
||||||
|
PUBLICATION_DATE TIMESTAMP(9) NOT NULL,
|
||||||
|
SERIALIZED_EVENT VARCHAR(4000) NOT NULL,
|
||||||
|
PRIMARY KEY (ID)
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS EVENT_PUBLICATION
|
||||||
|
(
|
||||||
|
ID UUID NOT NULL,
|
||||||
|
COMPLETION_DATE TIMESTAMP(9) WITH TIME ZONE,
|
||||||
|
EVENT_TYPE VARCHAR(512) NOT NULL,
|
||||||
|
LISTENER_ID VARCHAR(512) NOT NULL,
|
||||||
|
PUBLICATION_DATE TIMESTAMP(9) WITH TIME ZONE NOT NULL,
|
||||||
|
SERIALIZED_EVENT VARCHAR(4000) NOT NULL,
|
||||||
|
PRIMARY KEY (ID)
|
||||||
|
)
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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.jdbc;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.modulith.events.EventPublicationRegistry;
|
||||||
|
import org.springframework.modulith.events.EventSerializer;
|
||||||
|
import org.springframework.modulith.testapp.TestApplication;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Belyaev, Björn Kieling
|
||||||
|
*/
|
||||||
|
@SpringBootTest(
|
||||||
|
classes = TestApplication.class,
|
||||||
|
properties = "spring.modulith.events.schema-initialization.enabled=true"
|
||||||
|
)
|
||||||
|
public class JdbcEventPublicationAutoConfigurationIntegrationTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ApplicationContext context;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private EventSerializer serializer;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bootstrapsApplicationComponents() {
|
||||||
|
|
||||||
|
assertThat(context.getBean(EventPublicationRegistry.class)).isNotNull();
|
||||||
|
assertThat(context.getBean(JdbcEventPublicationRepository.class)).isNotNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.modulith.events.jdbc.DatabaseSchemaInitializer;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Belyaev, Björn Kieling
|
||||||
|
*/
|
||||||
|
public class DatabaseSchemaInitializerTest {
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DataJdbcTest(properties = {
|
||||||
|
"spring.modulith.events.schema-initialization.enabled=true"
|
||||||
|
})
|
||||||
|
@Import(DatabaseSchemaInitializer.class)
|
||||||
|
class InitializationEnabled {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateDatabaseSchemaOnStartUp() {
|
||||||
|
Long count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM EVENT_PUBLICATION", Long.class);
|
||||||
|
|
||||||
|
assertThat(count).isEqualTo(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DataJdbcTest(properties = {
|
||||||
|
"spring.modulith.events.schema-initialization.enabled=false"
|
||||||
|
})
|
||||||
|
@Import(DatabaseSchemaInitializer.class)
|
||||||
|
class InitializationDisabled {
|
||||||
|
|
||||||
|
@SpyBean
|
||||||
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotCreateDatabaseSchemaOnStartUp() {
|
||||||
|
Mockito.verify(jdbcTemplate, Mockito.never()).execute(anyString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DataJdbcTest
|
||||||
|
@Import(DatabaseSchemaInitializer.class)
|
||||||
|
class InitializationDisabledByDefault {
|
||||||
|
|
||||||
|
@SpyBean
|
||||||
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotCreateDatabaseSchemaOnStartUp() {
|
||||||
|
Mockito.verify(jdbcTemplate, Mockito.never()).execute(anyString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DataJdbcTest(properties = {
|
||||||
|
"spring.modulith.events.schema-initialization.enabled=true"
|
||||||
|
})
|
||||||
|
@ActiveProfiles("hsql")
|
||||||
|
@Import(DatabaseSchemaInitializer.class)
|
||||||
|
class InitializationUseHsql {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateDatabaseSchemaOnStartUp() {
|
||||||
|
Long count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM EVENT_PUBLICATION", Long.class);
|
||||||
|
|
||||||
|
assertThat(count).isEqualTo(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DataJdbcTest(properties = {
|
||||||
|
"spring.modulith.events.schema-initialization.enabled=true"
|
||||||
|
})
|
||||||
|
@ActiveProfiles("h2")
|
||||||
|
@Import(DatabaseSchemaInitializer.class)
|
||||||
|
class InitializationUseH2 {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateDatabaseSchemaOnStartUp() {
|
||||||
|
Long count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM EVENT_PUBLICATION", Long.class);
|
||||||
|
|
||||||
|
assertThat(count).isEqualTo(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@Disabled
|
||||||
|
@DataJdbcTest(properties = {
|
||||||
|
"spring.modulith.events.schema-initialization.enabled=true"
|
||||||
|
})
|
||||||
|
@ActiveProfiles("postgres")
|
||||||
|
@Import(DatabaseSchemaInitializer.class)
|
||||||
|
class InitializationUsePostgres {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateDatabaseSchemaOnStartUp() {
|
||||||
|
Long count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM EVENT_PUBLICATION", Long.class);
|
||||||
|
|
||||||
|
assertThat(count).isEqualTo(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.modulith.events.CompletableEventPublication;
|
||||||
|
import org.springframework.modulith.events.EventPublication;
|
||||||
|
import org.springframework.modulith.events.EventSerializer;
|
||||||
|
import org.springframework.modulith.events.PublicationTargetIdentifier;
|
||||||
|
import org.springframework.modulith.events.jdbc.DatabaseSchemaInitializer;
|
||||||
|
import org.springframework.modulith.events.jdbc.JdbcEventPublicationRepository;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Belyaev, Björn Kieling
|
||||||
|
*/
|
||||||
|
class JdbcEventPublicationRepositoryIntegrationTests {
|
||||||
|
|
||||||
|
private static final PublicationTargetIdentifier TARGET_IDENTIFIER = PublicationTargetIdentifier.of("listener");
|
||||||
|
|
||||||
|
private JdbcEventPublicationRepository repository;
|
||||||
|
|
||||||
|
private final EventSerializer eventSerializer = mock(EventSerializer.class);
|
||||||
|
|
||||||
|
private abstract class TestBase {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
repository = new JdbcEventPublicationRepository(jdbcTemplate, eventSerializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DataJdbcTest
|
||||||
|
@ActiveProfiles("hsql")
|
||||||
|
@Import(DatabaseSchemaInitializer.class)
|
||||||
|
class HSQL extends TestBase {
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CreateAndUpdate {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPersistAndUpdateEventPublication() {
|
||||||
|
shouldPersistAndUpdateEventPublicationTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUpdateSingleEventPublication() {
|
||||||
|
shouldUpdateSingleEventPublicationTest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FindByCompletionDateIsNull {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSilentlyIgnoreNotSerializableEvents() {
|
||||||
|
shouldSilentlyIgnoreNotSerializableEventsTest(jdbcTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FindBySerializedEventAndListenerId {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTolerateEmptyResult() {
|
||||||
|
shouldTolerateEmptyResultTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnTheOldestEvent() throws InterruptedException {
|
||||||
|
shouldReturnTheOldestEventTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSilentlyIgnoreNotSerializableEvents() {
|
||||||
|
shouldSilentlyIgnoreNotSerializableEventsTest(jdbcTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DataJdbcTest
|
||||||
|
@ActiveProfiles("h2")
|
||||||
|
@Import(DatabaseSchemaInitializer.class)
|
||||||
|
class H2 extends TestBase {
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CreateAndUpdate {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPersistAndUpdateEventPublication() {
|
||||||
|
shouldPersistAndUpdateEventPublicationTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUpdateSingleEventPublication() {
|
||||||
|
shouldUpdateSingleEventPublicationTest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FindByCompletionDateIsNull {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSilentlyIgnoreNotSerializableEvents() {
|
||||||
|
shouldSilentlyIgnoreNotSerializableEventsTest(jdbcTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FindBySerializedEventAndListenerId {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTolerateEmptyResult() {
|
||||||
|
shouldTolerateEmptyResultTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnTheOldestEvent() throws InterruptedException {
|
||||||
|
shouldReturnTheOldestEventTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSilentlyIgnoreNotSerializableEvents() {
|
||||||
|
shouldSilentlyIgnoreNotSerializableEventsTest(jdbcTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@Disabled
|
||||||
|
@DataJdbcTest
|
||||||
|
@ActiveProfiles("postgres")
|
||||||
|
@Import(DatabaseSchemaInitializer.class)
|
||||||
|
class Postgres extends TestBase {
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CreateAndUpdate {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPersistAndUpdateEventPublication() {
|
||||||
|
shouldPersistAndUpdateEventPublicationTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUpdateSingleEventPublication() {
|
||||||
|
shouldUpdateSingleEventPublicationTest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FindByCompletionDateIsNull {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSilentlyIgnoreNotSerializableEvents() {
|
||||||
|
shouldSilentlyIgnoreNotSerializableEventsTest(jdbcTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FindBySerializedEventAndListenerId {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTolerateEmptyResult() {
|
||||||
|
shouldTolerateEmptyResultTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnTheOldestEvent() throws InterruptedException {
|
||||||
|
shouldReturnTheOldestEventTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSilentlyIgnoreNotSerializableEvents() {
|
||||||
|
shouldSilentlyIgnoreNotSerializableEventsTest(jdbcTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shouldPersistAndUpdateEventPublicationTest() {
|
||||||
|
TestEvent testEvent = new TestEvent("id");
|
||||||
|
String serializedEvent = "{\"eventId\":\"id\"}";
|
||||||
|
|
||||||
|
when(eventSerializer.serialize(testEvent)).thenReturn(serializedEvent);
|
||||||
|
when(eventSerializer.deserialize(serializedEvent, TestEvent.class)).thenReturn(testEvent);
|
||||||
|
|
||||||
|
CompletableEventPublication publication = CompletableEventPublication.of(testEvent, TARGET_IDENTIFIER);
|
||||||
|
|
||||||
|
// Store publication
|
||||||
|
repository.create(publication);
|
||||||
|
|
||||||
|
List<EventPublication> eventPublications = repository.findByCompletionDateIsNull();
|
||||||
|
assertThat(eventPublications).hasSize(1);
|
||||||
|
assertThat(eventPublications.get(0).getEvent()).isEqualTo(publication.getEvent());
|
||||||
|
assertThat(eventPublications.get(0).getTargetIdentifier()).isEqualTo(publication.getTargetIdentifier());
|
||||||
|
assertThat(repository.findByEventAndTargetIdentifier(testEvent, TARGET_IDENTIFIER))
|
||||||
|
.isPresent();
|
||||||
|
|
||||||
|
// Complete publication
|
||||||
|
repository.updateCompletionDate(publication.markCompleted());
|
||||||
|
|
||||||
|
assertThat(repository.findByCompletionDateIsNull()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shouldUpdateSingleEventPublicationTest() {
|
||||||
|
TestEvent testEvent1 = new TestEvent("id1");
|
||||||
|
TestEvent testEvent2 = new TestEvent("id2");
|
||||||
|
String serializedEvent1 = "{\"eventId\":\"id1\"}";
|
||||||
|
String serializedEvent2 = "{\"eventId\":\"id2\"}";
|
||||||
|
|
||||||
|
when(eventSerializer.serialize(testEvent1)).thenReturn(serializedEvent1);
|
||||||
|
when(eventSerializer.deserialize(serializedEvent1, TestEvent.class)).thenReturn(testEvent1);
|
||||||
|
when(eventSerializer.serialize(testEvent2)).thenReturn(serializedEvent2);
|
||||||
|
when(eventSerializer.deserialize(serializedEvent2, TestEvent.class)).thenReturn(testEvent2);
|
||||||
|
|
||||||
|
CompletableEventPublication publication1 = CompletableEventPublication.of(testEvent1, TARGET_IDENTIFIER);
|
||||||
|
CompletableEventPublication publication2 = CompletableEventPublication.of(testEvent2, TARGET_IDENTIFIER);
|
||||||
|
|
||||||
|
// Store publication
|
||||||
|
repository.create(publication1);
|
||||||
|
repository.create(publication2);
|
||||||
|
|
||||||
|
// Complete publication
|
||||||
|
repository.updateCompletionDate(publication2.markCompleted());
|
||||||
|
|
||||||
|
List<EventPublication> withCompletionDateNull = repository.findByCompletionDateIsNull();
|
||||||
|
assertThat(withCompletionDateNull).hasSize(1);
|
||||||
|
assertThat(withCompletionDateNull.get(0).getEvent()).isEqualTo(testEvent1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shouldTolerateEmptyResultTest() {
|
||||||
|
TestEvent testEvent = new TestEvent("id");
|
||||||
|
String serializedEvent = "{\"eventId\":\"id\"}";
|
||||||
|
when(eventSerializer.serialize(testEvent)).thenReturn(serializedEvent);
|
||||||
|
|
||||||
|
Optional<EventPublication> actual =
|
||||||
|
repository.findByEventAndTargetIdentifier(testEvent, TARGET_IDENTIFIER);
|
||||||
|
|
||||||
|
assertThat(actual).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shouldReturnTheOldestEventTest() throws InterruptedException {
|
||||||
|
TestEvent testEvent = new TestEvent("id");
|
||||||
|
String serializedEvent = "{\"eventId\":\"id\"}";
|
||||||
|
when(eventSerializer.serialize(testEvent)).thenReturn(serializedEvent);
|
||||||
|
when(eventSerializer.deserialize(serializedEvent, TestEvent.class)).thenReturn(testEvent);
|
||||||
|
|
||||||
|
CompletableEventPublication publicationOld = CompletableEventPublication.of(testEvent, TARGET_IDENTIFIER);
|
||||||
|
Thread.sleep(10);
|
||||||
|
CompletableEventPublication publicationNew = CompletableEventPublication.of(testEvent, TARGET_IDENTIFIER);
|
||||||
|
|
||||||
|
repository.create(publicationNew);
|
||||||
|
repository.create(publicationOld);
|
||||||
|
|
||||||
|
|
||||||
|
Optional<EventPublication> actual =
|
||||||
|
repository.findByEventAndTargetIdentifier(testEvent, TARGET_IDENTIFIER);
|
||||||
|
|
||||||
|
assertThat(actual).isNotEmpty();
|
||||||
|
assertThat(actual.get().getPublicationDate()).isEqualTo(publicationOld.getPublicationDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shouldSilentlyIgnoreNotSerializableEventsTest(JdbcTemplate jdbcTemplate) {
|
||||||
|
TestEvent testEvent = new TestEvent("id");
|
||||||
|
String serializedEvent = "{\"eventId\":\"id\"}";
|
||||||
|
when(eventSerializer.serialize(testEvent)).thenReturn(serializedEvent);
|
||||||
|
when(eventSerializer.deserialize(serializedEvent, TestEvent.class)).thenReturn(testEvent);
|
||||||
|
|
||||||
|
CompletableEventPublication publication = CompletableEventPublication.of(testEvent, TARGET_IDENTIFIER);
|
||||||
|
|
||||||
|
// Store publication
|
||||||
|
repository.create(publication);
|
||||||
|
jdbcTemplate.update("UPDATE EVENT_PUBLICATION SET EVENT_TYPE='abc'");
|
||||||
|
|
||||||
|
Optional<EventPublication> actual =
|
||||||
|
repository.findByEventAndTargetIdentifier(testEvent, TARGET_IDENTIFIER);
|
||||||
|
|
||||||
|
assertThat(actual).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class TestEvent {
|
||||||
|
private final String eventId;
|
||||||
|
|
||||||
|
private TestEvent(String eventId) {
|
||||||
|
this.eventId = eventId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Belyaev, Björn Kieling
|
||||||
|
*/
|
||||||
|
@SpringBootApplication
|
||||||
|
public class TestApplication {
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
spring.datasource.driverClassName=org.h2.Driver
|
||||||
|
spring.test.database.replace=NONE
|
||||||
|
|
||||||
|
spring.modulith.events.schema-initialization.enabled=true
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
spring.datasource.driverClassName=org.hsqldb.jdbc.JDBCDriver
|
||||||
|
spring.datasource.url=jdbc:hsqldb:mem:testdb;DB_CLOSE_DELAY=-1
|
||||||
|
spring.test.database.replace=NONE
|
||||||
|
|
||||||
|
spring.modulith.events.schema-initialization.enabled=true
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
spring.datasource.driverClassName=org.postgresql.Driver
|
||||||
|
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
|
||||||
|
spring.datasource.username=username
|
||||||
|
spring.datasource.password=password
|
||||||
|
spring.test.database.replace=NONE
|
||||||
|
|
||||||
|
spring.modulith.events.schema-initialization.enabled=true
|
||||||
Reference in New Issue
Block a user