Uploaded image for project: 'Java Driver'
  1. Java Driver
  2. JAVA-4242

Introduce Request Context support for API-agnostic context propagation

    • Needed

      A MongoDB driver can be used from various programming models (imperative where calls remain on the same thread, reactive where code is thread-agnostic). At the same time, the underlying application using the MongoDB driver can be interested in end-to-end tracing of outgoing remote calls. To properly construct traces, each participating component must be able to contribute their individual activity in the form of a span (one or more spans represent a full trace) and be able to attach its span to the overall trace.

      In imperative programming arrangements, context propagation is typically implemented as out-of-band storage where the context is attached to the thread that is associated with the call. Context information is stored outside and integration components can associate context data and tracing identifiers through CommandListener.
      Since CommandListener accept CommandEvent that are not associated with a context (or context provider), other types of programming models (asynchronous, event-loop, reactive) cannot leverage the ThreadLocal pattern.

      The following proposal outlines a context capturing and propagation API to enable the driver to capture and propagate request contexts regardless of the underlying programming model. It consists of the following components:

      1. Generic context capturing interfaces to capture the current (inbound) context
      2. Context API
      3. Context consumption API

      Context capturing

      Calls to the driver are subject can happen within a potentially existing context. Depending on the called MongoDB API, context can be either stored ThreadLocal or can be passed as Reactor Context. Since the driver configuration infrastructure (MongoClientSettings) is API-agnostict, context providers should not depend on dependencies used in particular driver implementations. Instead, the driver internally can detect whether a context provider component was registered that can be leveraged to obtain the context:

      // marker interface
      interface ContextProvider {
      interface EnvironmentContextProvider extends ContextProvider {
        RequestContext get(); // typically fetches data from a ThreadLocal
      interface ReactorContextProvider extends ContextProvider {
        RequestContext get(ContextView reactorContext); // extracts a context object from the Reactor ContextView
      class MixedContextProvider implements EnvironmentContextProvider, ReactorContextProvider {
        private final Tracer tracer = …;
        public RequestContext get() {
      		Span span = tracer.getCurrentSpan();
      		RequestContext ctx = …;
      		ctx.put("span", span);
      		return ctx;
        public RequestContext get(ContextView view) {
      		Span span = view.get(Span.class);
      		RequestContext ctx = …;
      		ctx.put("span", span);
      		return ctx;
      MongoClientSettings.builder().contextProvider(new MixedContextProvider(…)).build();

      Context API

      The context API is a mutable, Map-like data structure that allows associating key-value tuples with the current request. In a tracing setup, beginning a request would create a new span, store it in the request context, and upon success, error, retry, the span would be retrieved from the context and completed with the command outcome.

      // marker interface
      interface RequestContext // optional: extends Map<Object, Object> {
      	Object get(Object key);
      	// optional: <T> T get(Object key, Class<T> requiredType);
      	Object put(Object key, Object o);
      	Object remove(Object key);
      	// optional: <K, T> T computeIfAbsent(K key, Function<K, T> factory, Class<T> requiredType);

      Context consumption

      The context would be primarily consumed through the command listener API CommandListener and ideally, RequestContext is provided through CommandEvent. An example implementation would be:

      class TracingCommandListener implements CommandListener {
        private final Tracer tracer = …;
      	public void commandStarted(CommandStartedEvent event) {
                Span parent = event.getContext().get("span", Span.class);
      	  Span span = tracer.createSpan(event.getCommandName(), parent);
                event.getContext().put("span", span);
      	public void commandSucceeded(CommandSucceededEvent event) {
                Span span = event.getContext().get("span", Span.class);

            jeff.yemin@mongodb.com Jeffrey Yemin
            mpaluch@paluch.biz Mark Paluch
            0 Vote for this issue
            2 Start watching this issue