Reputation: 15
I want to make sure that my App gets started even if Cassandra is down. What is the best way to handle that?
I am using AbstractReactiveCassandraConfiguration
and multiple keyspace implementation.
@Bean
@NonNull
@Override
public CqlSessionFactoryBean cassandraSession() {
final CqlSessionFactoryBean cqlSessionFactoryBean = new CqlSessionFactoryBean();
cqlSessionFactoryBean.setContactPoints(contactPoints);
cqlSessionFactoryBean.setLocalDatacenter(localDataCenter);
cqlSessionFactoryBean.setUsername(username);
cqlSessionFactoryBean.setPassword(password);
return cqlSessionFactoryBean;
}
Keyspace is set at the individual keyspace config file.
Keyspace 1 and keyspace 2 with similar kinds of configurations.
@Configuration
@EnableReactiveCassandraRepositories(basePackages = "com.abc.repository.cassandra.xyz", reactiveCassandraTemplateRef = "keyspaceXYZTemplate")
public class XYZCassandraConfiguration extends CassandraConfig {
@Override
protected String getKeyspaceName() {
return "KEYSPACE_NAME";
}
@Bean("keyspaceXYZTemplate")
public ReactiveCassandraTemplate reactiveCassandraTemplate(@Qualifier("xyzBean") CqlSessionFactoryBean cqlSessionFactoryBean) {
final ReactiveSession reactiveSession = new DefaultBridgedReactiveSession(cqlSessionFactoryBean.getObject());
return new ReactiveCassandraTemplate(reactiveSession, cassandraConverter());
}
@NonNull
@Override
@Primary
@Bean(name = "xyzBean")
public CqlSessionFactoryBean cassandraSession() {
final CqlSessionFactoryBean cqlSessionFactoryBean = super.cassandraSession();
cqlSessionFactoryBean.setKeyspaceName(getKeyspaceName());
return cqlSessionFactoryBean;
}
}
Stack Trace: When the app starts and Cassandra is down.
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean
Invocation of init method failed; nested exception is com.datastax.oss.driver.api.core.AllNodesFailedException: Could not reach any contact point, make sure you've provided valid addresses (showing first 1 nodes, use getAllErrors() for more): Node(endPoint=localhost:9042, hostId=null, hashCode=70c95ec9): [com.datastax.oss.driver.api.core.connection.ConnectionInitException: [s0|control|connecting...] Protocol initialization request, step 1 (OPTIONS): failed to send request (io.netty.channel.StacklessClosedChannelException)]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1354) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1204) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.8.jar:5.3.8]
Upvotes: 0
Views: 662
Reputation: 21
In case anyone finds their way to this question, like I did, with the same need - ie start Spring app if Cassandra DB is down - it is possible!
If your set up utilises Cassandra Auto Configuration, the solution is in this question: Spring Boot and Spring Data with Cassandra: Continue on failed database connection
This very clever approach by spx01 fakes the CqlSession and it works.
If your set up utilises AbstractReactiveCassandraConfiguration, like mine, then you need to:
@Override
@Bean
public CqlSessionFactoryBean cassandraSession() {
CqlSessionFactoryBean cassandraSession = super.cassandraSession();
cassandraSession.setUsername(env.getProperty("spring.data.cassandra.username", "monkey"));
cassandraSession.setPassword(env.getProperty("spring.data.cassandra.password", "likesBananas"));
try {
cassandraSession.afterPropertiesSet(); //this is needed so that we can trigger the exception before Spring tries to wire in the bean
cassandraSession.destroy() //to close tcp connections/sessions opened by previous line
} catch(AllNodesFailedException e) {
return new NoOpCqlSessionFactoryBean();
}
return cassandraSession;
}
Here, we are utilising DatabaseNotConnectedFakeCqlSession from spx01's solution mentioned above but in amended form (see point 3).
public class NoOpCqlSessionFactoryBean extends CqlSessionFactoryBean {
@Override
public CqlSession getObject() {
return new DatabaseNotConnectedFakeCqlSession();
}
@Override
public void afterPropertiesSet() {
}
}
public class DatabaseNotConnectedFakeCqlSession implements CqlSession {
@NonNull
@Override
public String getName() {
return null;
}
@NonNull
@Override
public Metadata getMetadata() {
return null;
}
@Override
public boolean isSchemaMetadataEnabled() {
return false;
}
@NonNull
@Override
public CompletionStage<Metadata> setSchemaMetadataEnabled(@Nullable Boolean newValue) {
return CompletableFuture.completedStage(new Metadata() {
@NonNull
@Override
public Map<UUID, Node> getNodes() {
return null;
}
@NonNull
@Override
public Map<CqlIdentifier, KeyspaceMetadata> getKeyspaces() {
return null;
}
@NonNull
@Override
public Optional<TokenMap> getTokenMap() {
return Optional.empty();
}
});
}
@NonNull
@Override
public CompletionStage<Metadata> refreshSchemaAsync() {
return CompletableFuture.completedStage(new Metadata() {
@NonNull
@Override
public Map<UUID, Node> getNodes() {
return null;
}
@NonNull
@Override
public Map<CqlIdentifier, KeyspaceMetadata> getKeyspaces() {
return null;
}
@NonNull
@Override
public Optional<TokenMap> getTokenMap() {
return Optional.empty();
}
});
}
@NonNull
@Override
public CompletionStage<Boolean> checkSchemaAgreementAsync() {
return null;
}
@NonNull
@Override
public DriverContext getContext() {
return new NoOpDriverContext();
}
@NonNull
@Override
public Optional<CqlIdentifier> getKeyspace() {
return Optional.empty();
}
@NonNull
@Override
public Optional<Metrics> getMetrics() {
return Optional.empty();
}
@Nullable
@Override
public <RequestT extends Request, ResultT> ResultT execute(
@NonNull RequestT request,
@NonNull GenericType<ResultT> resultType
) {
return null;
}
@NonNull
@Override
public CompletionStage<Void> closeFuture() {
return null;
}
@NonNull
@Override
public CompletionStage<Void> closeAsync() {
return null;
}
@NonNull
@Override
public CompletionStage<Void> forceCloseAsync() {
return null;
}
}
public class NoOpDriverContext implements DriverContext {
@NonNull
@Override
public String getSessionName() {
return null;
}
@NonNull
@Override
public DriverConfig getConfig() {
return null;
}
@NonNull
@Override
public DriverConfigLoader getConfigLoader() {
return null;
}
@NonNull
@Override
public Map<String, LoadBalancingPolicy> getLoadBalancingPolicies() {
return null;
}
@NonNull
@Override
public Map<String, RetryPolicy> getRetryPolicies() {
return null;
}
@NonNull
@Override
public Map<String, SpeculativeExecutionPolicy> getSpeculativeExecutionPolicies() {
return null;
}
@NonNull
@Override
public TimestampGenerator getTimestampGenerator() {
return null;
}
@NonNull
@Override
public ReconnectionPolicy getReconnectionPolicy() {
return null;
}
@NonNull
@Override
public AddressTranslator getAddressTranslator() {
return null;
}
@NonNull
@Override
public Optional<AuthProvider> getAuthProvider() {
return Optional.empty();
}
@NonNull
@Override
public Optional<SslEngineFactory> getSslEngineFactory() {
return Optional.empty();
}
@NonNull
@Override
public RequestTracker getRequestTracker() {
return null;
}
@NonNull
@Override
public RequestThrottler getRequestThrottler() {
return null;
}
@NonNull
@Override
public NodeStateListener getNodeStateListener() {
return null;
}
@NonNull
@Override
public SchemaChangeListener getSchemaChangeListener() {
return null;
}
@NonNull
@Override
public ProtocolVersion getProtocolVersion() {
return null;
}
@NonNull
@Override
public CodecRegistry getCodecRegistry() {
return new CodecRegistry() {
@NonNull
@Override
public <JavaTypeT> TypeCodec<JavaTypeT> codecFor(
@NonNull DataType cqlType,
@NonNull GenericType<JavaTypeT> javaType
) {
return null;
}
@NonNull
@Override
public <JavaTypeT> TypeCodec<JavaTypeT> codecFor(@NonNull DataType cqlType) {
return null;
}
@NonNull
@Override
public <JavaTypeT> TypeCodec<JavaTypeT> codecFor(@NonNull GenericType<JavaTypeT> javaType) {
return null;
}
@NonNull
@Override
public <JavaTypeT> TypeCodec<JavaTypeT> codecFor(@NonNull DataType cqlType, @NonNull JavaTypeT value) {
return null;
}
@NonNull
@Override
public <JavaTypeT> TypeCodec<JavaTypeT> codecFor(@NonNull JavaTypeT value) {
return null;
}
};
}
}
And that should allow you to start your app even when Cassandra is down.
Upvotes: 1
Reputation: 16323
This isn't possible since your app has a dependency on being connected to the database before it can start.
The beans can't be instantiated unless the driver is connected to Cassandra. Cheers!
Upvotes: 2