Apply addiitional polishing to the Inline Caching with Apache Cassandra Integration Tests.

The refactorings and code changes are based on feedback from Mark Paluch regarding Apache Cassandra and Spring Data for Apache Cassandra.

Note: a developer must quote the CqlIdentifier (e.g. table names) when the case is mixed.
This commit is contained in:
John Blum
2022-07-21 14:50:51 -07:00
parent 1740f16bf6
commit 9cdeaf0325
6 changed files with 83 additions and 70 deletions

View File

@@ -27,6 +27,7 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
@@ -38,6 +39,7 @@ import org.springframework.data.cassandra.core.cql.RowMapper;
import org.springframework.data.cassandra.core.cql.session.init.KeyspacePopulator;
import org.springframework.data.cassandra.core.cql.session.init.ResourceKeyspacePopulator;
import org.springframework.data.cassandra.core.cql.session.init.SessionFactoryInitializer;
import org.springframework.lang.NonNull;
import example.app.crm.model.Customer;
import example.app.crm.repo.CustomerRepository;
@@ -64,6 +66,8 @@ public abstract class TestCassandraConfiguration {
private static final boolean CONTINUE_ON_ERROR = false;
private static final boolean IGNORE_FAILED_DROPS = true;
private static final Customer pieDoe = Customer.newCustomer(16L, "Pie Doe");
private static final String CQL_SCRIPT_ENCODING = null;
protected static final int CASSANDRA_DEFAULT_PORT = CqlSessionFactoryBean.DEFAULT_PORT;
@@ -71,8 +75,21 @@ public abstract class TestCassandraConfiguration {
protected static final String CASSANDRA_DATA_CQL = "cassandra-data.cql";
protected static final String CASSANDRA_INIT_CQL = "cassandra-init.cql";
protected static final String CASSANDRA_SCHEMA_CQL = "cassandra-schema.cql";
protected static final String LOCAL_DATA_CENTER = "datacenter1";
protected static final String DEBUGGING_PROFILE = "debugging";
protected static final String KEYSPACE_NAME = "CustomerService";
protected static final String TABLE_NAME = "Customers";
protected @NonNull Resource newCassandraDataCqlScriptResource() {
return new ClassPathResource(CASSANDRA_DATA_CQL);
}
protected @NonNull Resource newCassandraInitCqlScriptResource() {
return new ClassPathResource(CASSANDRA_INIT_CQL);
}
protected @NonNull Resource newCassandraSchemaCqlScriptResource() {
return new ClassPathResource(CASSANDRA_SCHEMA_CQL);
}
@Bean
SessionFactoryInitializer sessionFactoryInitializer(SessionFactory sessionFactory) {
@@ -87,24 +104,12 @@ public abstract class TestCassandraConfiguration {
return sessionFactoryInitializer;
}
protected Resource newCassandraDataCqlScriptResource() {
return new ClassPathResource(CASSANDRA_DATA_CQL);
}
protected Resource newCassandraInitCqlScriptResource() {
return new ClassPathResource(CASSANDRA_INIT_CQL);
}
protected Resource newCassandraSchemaCqlScriptResource() {
return new ClassPathResource(CASSANDRA_SCHEMA_CQL);
}
protected KeyspacePopulator newKeyspacePopulator(Resource... cqlScripts) {
protected @NonNull KeyspacePopulator newKeyspacePopulator(Resource... cqlScripts) {
return new ResourceKeyspacePopulator(CONTINUE_ON_ERROR, IGNORE_FAILED_DROPS, CQL_SCRIPT_ENCODING, cqlScripts);
//return cqlScript -> {};
}
@Bean
@Profile(DEBUGGING_PROFILE)
BeanPostProcessor cassandraTemplatePostProcessor() {
return new BeanPostProcessor() {
@@ -114,11 +119,12 @@ public abstract class TestCassandraConfiguration {
if (bean instanceof CassandraTemplate cassandraTemplate) {
Consumer<CassandraTemplate> cassandraTemplateConsumer = noopCassandraTemplateConsumer();
//.andThen(entityObjectInsertingCassandraTemplateConsumer())
//.andThen(entityObjectAssertingCassandraTemplateConsumer());
//.andThen(keyspaceNameAssertingCassandraTemplateConsumer())
//.andThen(tableNameAssertingCassandraTemplateConsumer());
Consumer<CassandraTemplate> cassandraTemplateConsumer = noopCassandraTemplateConsumer()
.andThen(insertEntityObjectCassandraTemplateConsumer())
.andThen(assertEntityCountCassandraTemplateConsumer())
.andThen(assertEntityObjectCassandraTemplateConsumer())
.andThen(assertKeyspaceNameCassandraTemplateConsumer())
.andThen(assertTableNameCassandraTemplateConsumer());
cassandraTemplateConsumer.accept(cassandraTemplate);
}
@@ -132,35 +138,32 @@ public abstract class TestCassandraConfiguration {
return cassandraTemplate -> {};
}
private Consumer<CassandraTemplate> entityCountAssertingCassandraTemplateConsumer() {
private Consumer<CassandraTemplate> assertEntityCountCassandraTemplateConsumer() {
return cassandraTemplate -> assertThat(cassandraTemplate.count(Customer.class)).isOne();
}
private Consumer<CassandraTemplate> entityObjectAssertingCassandraTemplateConsumer() {
private Consumer<CassandraTemplate> assertEntityObjectCassandraTemplateConsumer() {
return cassandraTemplate -> {
String cql = "SELECT id, name FROM Customers";
String cql = "SELECT id, name FROM \"Customers\"";
RowMapper<Customer> customerRowMapper = (row, rowNumber) ->
Customer.newCustomer(row.getLong("id"), row.getString("name"));
Customer actualCustomer = cassandraTemplate.getCqlOperations().queryForObject(cql, customerRowMapper);
Customer expectedCustomer = Customer.newCustomer(16L, "Pie Doe");
assertThat(actualCustomer).isEqualTo(expectedCustomer);
//assertThat(cassandraTemplate.selectOneById(16L, Customer.class)).isEqualTo(expectedCustomer);
//assertThat(cassandraTemplate.query(Customer.class).stream().findFirst().orElse(null))
// .isEqualTo(expectedCustomer);
assertThat(actualCustomer).isEqualTo(pieDoe);
assertThat(cassandraTemplate.selectOneById(16L, Customer.class)).isEqualTo(pieDoe);
assertThat(cassandraTemplate.query(Customer.class).stream().findFirst().orElse(null)).isEqualTo(pieDoe);
};
}
// TODO: Why does this work and the CQL data script not work!
private Consumer<CassandraTemplate> entityObjectInsertingCassandraTemplateConsumer() {
return cassandraTemplate -> cassandraTemplate.insert(Customer.newCustomer(16L, "Pie Doe"));
private Consumer<CassandraTemplate> insertEntityObjectCassandraTemplateConsumer() {
return cassandraTemplate -> cassandraTemplate.insert(pieDoe);
}
private Consumer<CassandraTemplate> keyspaceNameAssertingCassandraTemplateConsumer() {
private Consumer<CassandraTemplate> assertKeyspaceNameCassandraTemplateConsumer() {
return cassandraTemplate -> {
@@ -177,7 +180,7 @@ public abstract class TestCassandraConfiguration {
};
}
private Consumer<CassandraTemplate> tableNameAssertingCassandraTemplateConsumer() {
private Consumer<CassandraTemplate> assertTableNameCassandraTemplateConsumer() {
return cassandraTemplate -> {
@@ -198,7 +201,10 @@ public abstract class TestCassandraConfiguration {
}
@Bean
ApplicationListener<ContextRefreshedEvent> populateCassandraDatabaseUsingRepository(CustomerRepository customerRepository) {
return event -> customerRepository.save(Customer.newCustomer(16L, "Pie Doe"));
@Profile(DEBUGGING_PROFILE)
ApplicationListener<ContextRefreshedEvent> populateCassandraDatabaseUsingRepository(
CustomerRepository customerRepository) {
return event -> customerRepository.save(pieDoe);
}
}

View File

@@ -18,15 +18,18 @@ package example.app.crm.config;
import static org.assertj.core.api.Assertions.assertThat;
import java.net.InetSocketAddress;
import java.util.Arrays;
import com.datastax.oss.driver.api.core.CqlSession;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.cassandra.CassandraProperties;
import org.springframework.boot.autoconfigure.cassandra.CqlSessionBuilderCustomizer;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.lang.NonNull;
@@ -57,29 +60,29 @@ import example.app.crm.model.Customer;
public class TestcontainersCassandraConfiguration extends TestCassandraConfiguration {
private static final String CASSANDRA_DOCKER_IMAGE_NAME = "cassandra:latest";
private static final String LOCAL_DATACENTER_NAME = "datacenter1";
@Bean("CassandraContainer")
@SuppressWarnings("rawtypes")
GenericContainer cassandraContainer() {
GenericContainer<?> cassandraContainer(Environment environment) {
GenericContainer cassandraContainer = newEnvironmentTunedCassandraContainer();
GenericContainer<?> cassandraContainer = newEnvironmentOptimizedCassandraContainer();
cassandraContainer.start();
return withCassandraServer(cassandraContainer);
return withCassandraServer(cassandraContainer, environment);
}
@SuppressWarnings("rawtypes")
private @NonNull GenericContainer newCassandraContainer() {
return new CassandraContainer(CASSANDRA_DOCKER_IMAGE_NAME)
private @NonNull GenericContainer<?> newCassandraContainer() {
return new CassandraContainer<>(CASSANDRA_DOCKER_IMAGE_NAME)
.withInitScript(CASSANDRA_SCHEMA_CQL)
//.withInitScript(CASSANDRA_INIT_CQL)
.withExposedPorts(CASSANDRA_DEFAULT_PORT)
.withReuse(true);
}
@SuppressWarnings("rawtypes")
private @NonNull GenericContainer newEnvironmentTunedCassandraContainer() {
// Information (feedback) received from Sergei Egorov.
private @NonNull GenericContainer<?> newEnvironmentOptimizedCassandraContainer() {
return newCassandraContainer()
.withEnv("CASSANDRA_SNITCH", "GossipingPropertyFileSnitch")
@@ -96,19 +99,22 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura
return CqlSession.builder()
.addContactPoint(resolveContactPoint(cassandraContainer))
.withLocalDatacenter(LOCAL_DATA_CENTER)
.withLocalDatacenter(LOCAL_DATACENTER_NAME)
.build();
}
private @NonNull GenericContainer<?> withCassandraServer(@NonNull GenericContainer<?> cassandraContainer) {
private @NonNull GenericContainer<?> withCassandraServer(@NonNull GenericContainer<?> cassandraContainer,
@NonNull Environment environment) {
//cassandraContainer = initializeCassandraServer(cassandraContainer);
cassandraContainer = assertCassandraServerSetup(cassandraContainer);
if (Arrays.asList(environment.getActiveProfiles()).contains(DEBUGGING_PROFILE)) {
cassandraContainer = initializeCassandraServer(cassandraContainer);
cassandraContainer = assertCassandraServerSetup(cassandraContainer);
}
return cassandraContainer;
}
private GenericContainer<?> initializeCassandraServer(GenericContainer<?> cassandraContainer) {
private @NonNull GenericContainer<?> initializeCassandraServer(@NonNull GenericContainer<?> cassandraContainer) {
try (CqlSession session = newCqlSession(cassandraContainer)) {
newKeyspacePopulator(newCassandraSchemaCqlScriptResource()).populate(session);
@@ -117,7 +123,7 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura
return cassandraContainer;
}
private GenericContainer<?> assertCassandraServerSetup(GenericContainer<?> cassandraContainer) {
private @NonNull GenericContainer<?> assertCassandraServerSetup(@NonNull GenericContainer<?> cassandraContainer) {
try (CqlSession session = newCqlSession(cassandraContainer)) {
@@ -126,16 +132,16 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura
assertThat(keyspaceMetadata.getName().toString()).isEqualToIgnoringCase(KEYSPACE_NAME);
keyspaceMetadata.getTable("Customers")
keyspaceMetadata.getTable(TABLE_NAME)
.map(tableMetadata -> {
assertThat(tableMetadata.getName().toString()).isEqualToIgnoringCase("Customers");
assertThat(tableMetadata.getName().toString()).isEqualTo(TABLE_NAME);
assertThat(tableMetadata.getKeyspace().toString()).isEqualToIgnoringCase(KEYSPACE_NAME);
//assertCustomersTableHasSizeOne(session);
return tableMetadata;
})
.orElseThrow(() -> new IllegalStateException("Table [Customers] not found"));
.orElseThrow(() -> new IllegalStateException(String.format("Table [%s] not found", TABLE_NAME)));
return keyspaceMetadata;
})
@@ -145,25 +151,26 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura
return cassandraContainer;
}
private void assertCustomersTableHasSizeOne(CqlSession session) {
private void assertCustomersTableHasSizeOne(@NonNull CqlSession session) {
CassandraTemplate template = newCassandraTemplate(session);
assertThat(template.getCqlOperations().execute(String.format("USE %s;", KEYSPACE_NAME))).isTrue();
assertThat(template.getCqlOperations().queryForObject("SELECT count(*) FROM Customers", Long.class)).isOne();
assertThat(template.getCqlOperations().queryForObject("SELECT count(*) FROM \"Customers\"", Long.class)).isOne();
//assertThat(template.count(Customer.class)).isOne(); // Table Customers not found; needs to use the Keyspace
}
@Bean
CqlSessionBuilderCustomizer cqlSessionBuilderCustomizer(
CqlSessionBuilderCustomizer cqlSessionBuilderCustomizer(CassandraProperties properties,
@Qualifier("CassandraContainer") GenericContainer<?> cassandraContainer) {
return cqlSessionBuilder -> cqlSessionBuilder.addContactPoint(resolveContactPoint(cassandraContainer))
.withLocalDatacenter(LOCAL_DATA_CENTER)
.withKeyspace(KEYSPACE_NAME);
return cqlSessionBuilder -> cqlSessionBuilder
.addContactPoint(resolveContactPoint(cassandraContainer))
.withLocalDatacenter(properties.getLocalDatacenter())
.withKeyspace(properties.getKeyspaceName());
}
private InetSocketAddress resolveContactPoint(GenericContainer<?> cassandraContainer) {
private @NonNull InetSocketAddress resolveContactPoint(@NonNull GenericContainer<?> cassandraContainer) {
return new InetSocketAddress(cassandraContainer.getHost(),
cassandraContainer.getMappedPort(CASSANDRA_DEFAULT_PORT));
}

View File

@@ -18,7 +18,6 @@ package example.app.crm.model;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.core.mapping.Indexed;
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.gemfire.mapping.annotation.Region;
@@ -34,7 +33,8 @@ import lombok.NoArgsConstructor;
* @author John Blum
* @see jakarta.persistence.Entity
* @see jakarta.persistence.Table
* @see org.springframework.data.annotation.Id
* @see org.springframework.data.cassandra.core.mapping.Indexed
* @see org.springframework.data.cassandra.core.mapping.PrimaryKey
* @see org.springframework.data.cassandra.core.mapping.Table
* @see org.springframework.data.gemfire.mapping.annotation.Region
* @since 1.1.0
@@ -48,9 +48,8 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor(staticName = "newCustomer")
public class Customer {
@Id
@jakarta.persistence.Id
@PrimaryKey
@jakarta.persistence.Id
private Long id;
@Indexed

View File

@@ -1 +1 @@
INSERT INTO Customers (id, name) VALUES (16, 'Pie Doe');
INSERT INTO "Customers" (id, name) VALUES (16, 'Pie Doe');

View File

@@ -1,4 +1,5 @@
CREATE KEYSPACE IF NOT EXISTS CustomerService WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1 };
CREATE TABLE IF NOT EXISTS CustomerService.Customers (id BIGINT PRIMARY KEY, name TEXT);
CREATE INDEX IF NOT EXISTS CustomerNameIdx ON CustomerService.Customers(name);
INSERT INTO CustomerService.Customers (id, name) VALUES (16, 'Pie Doe');
USE CustomerService;
CREATE TABLE IF NOT EXISTS "Customers" (id BIGINT PRIMARY KEY, name TEXT);
CREATE INDEX IF NOT EXISTS CustomerNameIdx ON "Customers"(name);
INSERT INTO "Customers" (id, name) VALUES (16, 'Pie Doe');

View File

@@ -1,4 +1,4 @@
CREATE KEYSPACE IF NOT EXISTS CustomerService WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1 };
USE CustomerService;
CREATE TABLE IF NOT EXISTS Customers (id BIGINT PRIMARY KEY, name TEXT);
CREATE INDEX IF NOT EXISTS CustomerNameIdx ON Customers(name);
CREATE TABLE IF NOT EXISTS "Customers" (id BIGINT PRIMARY KEY, name TEXT);
CREATE INDEX IF NOT EXISTS CustomerNameIdx ON "Customers"(name);