Polishing.

Introduce factory beans for easier wrapping of CqlSession.

See #1321
Original pull request: #1322
This commit is contained in:
Mark Paluch
2022-10-25 14:28:46 +02:00
parent 1d3e754e3e
commit fe4ab0cd8e
5 changed files with 203 additions and 35 deletions

View File

@@ -0,0 +1,98 @@
/*
* 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.data.cassandra.observability;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.CqlSessionBuilder;
import io.micrometer.observation.ObservationRegistry;
/**
* Factory bean to construct a {@link CqlSession} integrated with given {@link ObservationRegistry}. This factory bean
* registers also {@link ObservationRequestTracker#INSTANCE ObservationRequestTracker.INSTANCE} with the builder to
* ensure full integration with the required infrastructure.
*
* @author Mark Paluch
* @since 4.0
* @see ObservationRequestTracker
* @see ObservableCqlSessionFactory
*/
public class ObservableCqlSessionFactoryBean extends AbstractFactoryBean<CqlSession> {
private final CqlSessionBuilder cqlSessionBuilder;
private final ObservationRegistry observationRegistry;
private @Nullable String remoteServiceName;
/**
* Construct a new {@link ObservableCqlSessionFactoryBean}.
*
* @param cqlSessionBuilder must not be {@literal null}.
* @param observationRegistry must not be {@literal null}.
*/
public ObservableCqlSessionFactoryBean(CqlSessionBuilder cqlSessionBuilder, ObservationRegistry observationRegistry) {
Assert.notNull(cqlSessionBuilder, "CqlSessionBuilder must not be null");
Assert.notNull(observationRegistry, "ObservationRegistry must not be null");
this.cqlSessionBuilder = cqlSessionBuilder;
this.observationRegistry = observationRegistry;
}
@Override
protected CqlSession createInstance() {
cqlSessionBuilder.addRequestTracker(ObservationRequestTracker.INSTANCE);
if (ObjectUtils.isEmpty(getRemoteServiceName())) {
return ObservableCqlSessionFactory.wrap(cqlSessionBuilder.build(), observationRegistry);
}
return ObservableCqlSessionFactory.wrap(cqlSessionBuilder.build(), getRemoteServiceName(), observationRegistry);
}
@Override
protected void destroyInstance(@Nullable CqlSession instance) {
if (instance != null) {
instance.close();
}
}
@Override
public Class<?> getObjectType() {
return CqlSession.class;
}
@Nullable
public String getRemoteServiceName() {
return remoteServiceName;
}
/**
* Set the remote service name.
*
* @param remoteServiceName
*/
public void setRemoteServiceName(@Nullable String remoteServiceName) {
this.remoteServiceName = remoteServiceName;
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.data.cassandra.observability;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.data.cassandra.ReactiveSession;
import org.springframework.data.cassandra.core.cql.session.DefaultBridgedReactiveSession;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import com.datastax.oss.driver.api.core.CqlSession;
import io.micrometer.observation.ObservationRegistry;
/**
* Factory bean to construct a {@link ReactiveSession} integrated with given {@link ObservationRegistry}. The required
* {@link CqlSession} must be associated with {@link ObservationRequestTracker#INSTANCE
* ObservationRequestTracker.INSTANCE} to ensure proper integration with all observability components. You can use
* {@link ObservableCqlSessionFactoryBean} to obtain a properly configured {@link CqlSession}.
*
* @author Mark Paluch
* @since 4.0
* @see ObservationRequestTracker
* @see ObservableReactiveSessionFactory
*/
public class ObservableReactiveSessionFactoryBean extends AbstractFactoryBean<ReactiveSession> {
private final CqlSession cqlSession;
private final ObservationRegistry observationRegistry;
private @Nullable String remoteServiceName;
/**
* Construct a new {@link ObservableReactiveSessionFactoryBean}.
*
* @param cqlSession must not be {@literal null}.
* @param observationRegistry must not be {@literal null}.
*/
public ObservableReactiveSessionFactoryBean(CqlSession cqlSession, ObservationRegistry observationRegistry) {
Assert.notNull(cqlSession, "CqlSession must not be null");
Assert.notNull(observationRegistry, "ObservationRegistry must not be null");
this.cqlSession = cqlSession;
this.observationRegistry = observationRegistry;
}
@Override
protected ReactiveSession createInstance() {
if (ObjectUtils.isEmpty(getRemoteServiceName())) {
return ObservableReactiveSessionFactory.wrap(new DefaultBridgedReactiveSession(cqlSession), observationRegistry);
}
return ObservableReactiveSessionFactory.wrap(new DefaultBridgedReactiveSession(cqlSession), getRemoteServiceName(),
observationRegistry);
}
@Override
public Class<?> getObjectType() {
return ReactiveSession.class;
}
@Nullable
public String getRemoteServiceName() {
return remoteServiceName;
}
/**
* Set the remote service name.
*
* @param remoteServiceName
*/
public void setRemoteServiceName(@Nullable String remoteServiceName) {
this.remoteServiceName = remoteServiceName;
}
}

View File

@@ -45,7 +45,7 @@ import reactor.util.context.Context;
@ContextConfiguration(classes = TestConfig.class)
public class ReactiveIntegrationTests extends SampleTestRunner {
@Autowired ReactiveSession session;
@Autowired ReactiveSession observableSession;
ReactiveIntegrationTests() {
super(SampleRunnerConfig.builder().build());
@@ -68,7 +68,6 @@ public class ReactiveIntegrationTests extends SampleTestRunner {
Observation intermediate = Observation.start("intermediate", createObservationRegistry());
ReactiveSession observableSession = ObservableReactiveSessionFactory.wrap(session, createObservationRegistry());
Mono<ReactiveResultSet> drop = observableSession.execute("DROP KEYSPACE IF EXISTS ObservationTest");
Mono<ReactiveResultSet> create = observableSession.execute("CREATE KEYSPACE ObservationTest " + "WITH "

View File

@@ -18,9 +18,7 @@ package org.springframework.data.cassandra.observability;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.data.cassandra.ReactiveSession;
import org.springframework.data.cassandra.config.SessionBuilderConfigurer;
import org.springframework.data.cassandra.core.cql.session.DefaultBridgedReactiveSession;
import org.springframework.data.cassandra.support.AbstractTestJavaConfig;
import org.springframework.lang.Nullable;
@@ -71,7 +69,7 @@ class TestConfig extends AbstractTestJavaConfig {
}
@Bean
ReactiveSession reactiveSession(CqlSession session) {
return new DefaultBridgedReactiveSession(session);
ObservableReactiveSessionFactoryBean reactiveSession(CqlSession session, ObservationRegistry observationRegistry) {
return new ObservableReactiveSessionFactoryBean(session, observationRegistry);
}
}

View File

@@ -14,41 +14,22 @@ To enable the instrumentation, apply the following configuration to your applica
class ObservabilityConfiguration {
@Bean
public ObservationBeanPostProcessor observationBeanPostProcessor(ObservationRegistry observationRegistry) {
return new ObservationBeanPostProcessor(observationRegistry); <1>
public ObservableCqlSessionFactoryBean observableCqlSession(CqlSessionBuilder builder,
ObservationRegistry registry) {
return new ObservableCqlSessionFactoryBean(builder, registry); <1>
}
@Bean
public SessionBuilderConfigurer getSessionBuilderConfigurer() {
return sessionBuilder -> sessionBuilder.addRequestTracker(ObservationRequestTracker.INSTANCE); <2>
}
class ObservationBeanPostProcessor implements BeanPostProcessor {
public final ObservationRegistry observationRegistry;
public ObservationBeanPostProcessor(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof CqlSession) {
return ObservableCqlSessionFactory.wrap((CqlSession) bean, observationRegistry);
}
if (bean instanceof ReactiveSession) {
return ObservableReactiveSessionFactory.wrap((ReactiveSession) bean, observationRegistry);
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
public ObservableReactiveSessionFactoryBean observableReactiveSession(CqlSession session,
ObservationRegistry registry) {
return new ObservableReactiveSessionFactoryBean(session, registry); <2>
}
}
----
<1> Wraps all CQL session objects (imperative/reactive Session API) to observe Cassandra statement execution.
<2> Integrate with the Cassandra driver to obtain success/error callbacks.
<1> Wraps the CQL session object to observe Cassandra statement execution.
Also, registers `ObservationRequestTracker.INSTANCE` with the `CqlSessionBuilder`.
<1> Wraps a CQL session object to observe reactive Cassandra statement execution.
====
include::../../../../target/_conventions.adoc[]