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();