## 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:

```java
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):

```java
public class CustomNettyOptions implements NettyOptions {
  ...
} 
```

In the default context, here's how the component is managed:
 
```java
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:

```java
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:

```java
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:

```java
CqlSession session = new CustomBuilder()
    .addContactPoint(new InetSocketAddress("1.2.3.4", 9042))
    .withLocalDatacenter("datacenter1")
    .build();
```
