Skip to content

Commit 5bb595b

Browse files
Introduce context propagation API (#8161)
* feat(context): Introduce context propagation API * feat(context): Clean context API * feat(context): Add API test coverage * feat(context): Introduce compositor extractor carrier cache * feat(context): Improve propagators collection
1 parent 0df72c4 commit 5bb595b

File tree

14 files changed

+577
-6
lines changed

14 files changed

+577
-6
lines changed

components/context/src/main/java/datadog/context/Context.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import static datadog.context.ContextProviders.manager;
55

66
import javax.annotation.Nullable;
7-
import javax.annotation.ParametersAreNonnullByDefault;
87

98
/**
109
* Immutable context scoped to an execution unit or carrier object.
@@ -36,7 +35,6 @@
3635
*
3736
* @see ContextKey
3837
*/
39-
@ParametersAreNonnullByDefault
4038
public interface Context {
4139
/**
4240
* Returns the root context.

components/context/src/main/java/datadog/context/ContextBinder.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package datadog.context;
22

3-
import javax.annotation.ParametersAreNonnullByDefault;
4-
53
/** Binds context to carrier objects. */
6-
@ParametersAreNonnullByDefault
74
public interface ContextBinder {
85
/**
96
* Returns the context attached to the given carrier object.

components/context/src/main/java/datadog/context/ContextKey.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
*/
1111
public final class ContextKey<T> {
1212
private static final AtomicInteger NEXT_INDEX = new AtomicInteger(0);
13-
/** The key name, for debugging purpose only . */
13+
/** The key name, for debugging purpose only. */
1414
private final String name;
1515
/** The key unique context, related to {@link IndexedContext} implementation. */
1616
final int index;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@ParametersAreNonnullByDefault
2+
package datadog.context;
3+
4+
import javax.annotation.ParametersAreNonnullByDefault;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package datadog.context.propagation;
2+
3+
import javax.annotation.Nullable;
4+
5+
@FunctionalInterface
6+
public interface CarrierSetter<C> {
7+
/**
8+
* Sets a carrier key/value pair.
9+
*
10+
* @param carrier the carrier to store key/value into.
11+
* @param key the key to set.
12+
* @param value the value to set.
13+
*/
14+
void set(@Nullable C carrier, String key, String value);
15+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package datadog.context.propagation;
2+
3+
import java.util.function.BiConsumer;
4+
5+
/**
6+
* This interface represents the capacity of walking through a carrier content, iterating over its
7+
* key/value pairs.
8+
*
9+
* <p>Walking through carrier is preferred to direct access to carrier key/value pairs as some
10+
* carrier implementations do not have built-in direct access and require walking over the full
11+
* carrier structure to find the requested key/value pair, leading to multiple walks when multiple
12+
* keys are requested, whereas the visitor is expected to walk through only once, and the
13+
* propagators to keep the data they need using the visitor callback.
14+
*
15+
* @param <C> the type of carrier.
16+
*/
17+
@FunctionalInterface
18+
public interface CarrierVisitor<C> {
19+
/**
20+
* Iterates over the carrier content and calls the visitor callback for every key/value found.
21+
*
22+
* @param carrier the carrier to iterate over.
23+
* @param visitor the callback to call for each carrier key/value pair found.
24+
*/
25+
void forEachKeyValue(C carrier, BiConsumer<String, String> visitor);
26+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package datadog.context.propagation;
2+
3+
import datadog.context.Context;
4+
import java.util.ArrayList;
5+
import java.util.List;
6+
import java.util.function.BiConsumer;
7+
8+
class CompositePropagator implements Propagator {
9+
private final Propagator[] propagators;
10+
11+
CompositePropagator(Propagator[] propagators) {
12+
this.propagators = propagators;
13+
}
14+
15+
@Override
16+
public <C> void inject(Context context, C carrier, CarrierSetter<C> setter) {
17+
for (Propagator propagator : this.propagators) {
18+
propagator.inject(context, carrier, setter);
19+
}
20+
}
21+
22+
@Override
23+
public <C> Context extract(Context context, C carrier, CarrierVisitor<C> visitor) {
24+
// Extract and cache carrier key/value pairs
25+
CarrierCache carrierCache = new CarrierCache();
26+
visitor.forEachKeyValue(carrier, carrierCache);
27+
// Run the multiple extractions on cache
28+
for (Propagator propagator : this.propagators) {
29+
context = propagator.extract(context, carrierCache, carrierCache);
30+
}
31+
return context;
32+
}
33+
34+
static class CarrierCache implements BiConsumer<String, String>, CarrierVisitor<CarrierCache> {
35+
/** Cached key/values from carrier (even indexes are keys, odd indexes are values). */
36+
private final List<String> keysAndValues;
37+
38+
public CarrierCache() {
39+
this.keysAndValues = new ArrayList<>(32);
40+
}
41+
42+
@Override
43+
public void accept(String key, String value) {
44+
this.keysAndValues.add(key);
45+
this.keysAndValues.add(value);
46+
}
47+
48+
@Override
49+
public void forEachKeyValue(CarrierCache carrier, BiConsumer<String, String> visitor) {
50+
for (int i = 0; i < carrier.keysAndValues.size() - 1; i += 2) {
51+
visitor.accept(carrier.keysAndValues.get(i), carrier.keysAndValues.get(i + 1));
52+
}
53+
}
54+
}
55+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package datadog.context.propagation;
2+
3+
import static java.util.Objects.requireNonNull;
4+
5+
import datadog.context.Context;
6+
7+
/** This class defines a cross-cutting concern to be propagated from a {@link Context}. */
8+
public class Concern {
9+
/** The concern default priority. */
10+
public static final int DEFAULT_PRIORITY = 100;
11+
/** The concern name, for debugging purpose only. */
12+
private final String name;
13+
/** The concern priority, lower value means higher priority. */
14+
private final int priority;
15+
16+
/**
17+
* Creates a concern.
18+
*
19+
* @param name the concern name, for debugging purpose only.
20+
* @return The created concern.
21+
*/
22+
public static Concern named(String name) {
23+
return new Concern(name, DEFAULT_PRIORITY);
24+
}
25+
26+
/**
27+
* Creates a concern with a specific priority.
28+
*
29+
* @param name the concern name, for debugging purpose only.
30+
* @param priority the concern priority (lower value means higher priority, while the default is
31+
* {@link #DEFAULT_PRIORITY}),
32+
* @return The created concern.
33+
*/
34+
public static Concern withPriority(String name, int priority) {
35+
return new Concern(name, priority);
36+
}
37+
38+
private Concern(String name, int priority) {
39+
requireNonNull(name, "Concern name cannot be null");
40+
if (priority < 0) {
41+
throw new IllegalArgumentException("Concern priority cannot be negative");
42+
}
43+
this.name = name;
44+
this.priority = priority;
45+
}
46+
47+
int priority() {
48+
return this.priority;
49+
}
50+
51+
// We want identity equality, so no need to override equals().
52+
53+
@Override
54+
public String toString() {
55+
return this.name;
56+
}
57+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package datadog.context.propagation;
2+
3+
import datadog.context.Context;
4+
5+
final class NoopPropagator implements Propagator {
6+
static final NoopPropagator INSTANCE = new NoopPropagator();
7+
8+
private NoopPropagator() {}
9+
10+
@Override
11+
public <C> void inject(Context context, C carrier, CarrierSetter<C> setter) {
12+
// noop
13+
}
14+
15+
@Override
16+
public <C> Context extract(Context context, C carrier, CarrierVisitor<C> visitor) {
17+
return context;
18+
}
19+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package datadog.context.propagation;
2+
3+
import datadog.context.Context;
4+
5+
/**
6+
* This interface represents a {@link Context} propagator for a given {@link Concern}.
7+
*
8+
* <p>Its goal is to {@link #inject} context values into carriers, or {@link #extract} them from
9+
* carriers to populate context. Carrier could be any kind of object that stores key/value pairs,
10+
* like HTTP or messages headers. {@link CarrierSetter}s and {@link CarrierVisitor}s define how to
11+
* store and retrieve key/value pairs from carriers.
12+
*/
13+
public interface Propagator {
14+
/**
15+
* Injects a context into a downstream service using the given carrier.
16+
*
17+
* @param context the context containing the values to be injected.
18+
* @param carrier the instance that will receive the key/value pairs to propagate.
19+
* @param setter the callback to set key/value pairs into the carrier.
20+
* @param <C> the type of carrier instance.
21+
*/
22+
<C> void inject(Context context, C carrier, CarrierSetter<C> setter);
23+
24+
/**
25+
* Extracts a context from un upstream service.
26+
*
27+
* @param context the base context to store the extracted values on top, use {@link
28+
* Context#root()} for a default base context.
29+
* @param carrier the instance to fetch the propagated key/value pairs from.
30+
* @param visitor the callback to walk over the carrier and extract its key/value pais.
31+
* @param <C> the type of the carrier.
32+
* @return A context with the extracted values on top of the given base context.
33+
*/
34+
<C> Context extract(Context context, C carrier, CarrierVisitor<C> visitor);
35+
}

0 commit comments

Comments
 (0)