Driver context
The context holds the driver’s internal components. It is exposed in the public API as
DriverContext
, accessible via session.getContext()
. Internally, the child interface
InternalDriverContext
adds access to more components; finally, DefaultDriverContext
is the
implementing class.
The dependency graph
Most components initialize lazily (see LazyReference
). They also reference each other, typically
by taking the context as a constructor argument, and extracting the dependencies they need:
public DefaultTopologyMonitor(InternalDriverContext context) {
...
this.controlConnection = context.getControlConnection();
}
This avoids having to handle the initialization order ourselves. It is also convenient for unit tests: you can run a component in isolation by mocking all of its dependencies.
Obviously, things won’t go well if there are cyclic dependencies; if you make changes to the
context, you can set a system property to check the dependency graph, it will throw if a cycle is
detected (see CycleDetector
):
-Dcom.datastax.oss.driver.DETECT_CYCLES=true
This is disabled by default, because we don’t expect it to be very useful outside of testing cycles.
Why not use a DI framework?
As should be clear by now, the context is a poor man’s Dependency Injection framework. We deliberately avoided third-party solutions:
- to keep things as simple as possible,
- to avoid an additional library dependency,
- to allow end users to access components and add their own (which wouldn’t work well with compile-time approaches like Dagger).
Overriding a context component
The basic approach to plug in a custom internal component is to subclass the context.
For example, let’s say you wrote a custom NettyOptions
implementation (maybe you have multiple
sessions, and want to reuse the event loop groups instead of recreating them every time):
public class CustomNettyOptions implements NettyOptions {
...
}
In the default context, here’s how the component is managed:
public class DefaultDriverContext {
// some content omitted for brevity
private final LazyReference<NettyOptions> nettyOptionsRef =
new LazyReference<>("nettyOptions", this::buildNettyOptions, cycleDetector);
protected NettyOptions buildNettyOptions() {
return new DefaultNettyOptions(this);
}
@NonNull
@Override
public NettyOptions getNettyOptions() {
return nettyOptionsRef.get();
}
}
To switch in your implementation, you only need to override the build method:
public class CustomContext extends DefaultDriverContext {
public CustomContext(DriverConfigLoader configLoader, ProgrammaticArguments programmaticArguments) {
super(configLoader, programmaticArguments);
}
@Override
protected NettyOptions buildNettyOptions() {
return new CustomNettyOptions(this);
}
}
Then you need a way to create a session that uses your custom context. The session builder is extensible as well:
public class CustomBuilder extends SessionBuilder<CustomBuilder, CqlSession> {
@Override
protected DriverContext buildContext(
DriverConfigLoader configLoader, ProgrammaticArguments programmaticArguments) {
return new CustomContext(configLoader, programmaticArguments);
}
@Override
protected CqlSession wrap(@NonNull CqlSession defaultSession) {
// Nothing to do here, nothing changes on the session type
return defaultSession;
}
}
Finally, you can use your custom builder like the regular CqlSession.builder()
, it inherits all
the methods:
CqlSession session = new CustomBuilder()
.addContactPoint(new InetSocketAddress("1.2.3.4", 9042))
.withLocalDatacenter("datacenter1")
.build();