Skip to content

Commit da8e7d7

Browse files
ssharaevKavindu-Dodan
authored andcommitted
feat: context propagation
Signed-off-by: Sviatoslav Sharaev <[email protected]>
1 parent e1e15f4 commit da8e7d7

11 files changed

+342
-42
lines changed

README.md

+11-10
Original file line numberDiff line numberDiff line change
@@ -120,16 +120,17 @@ See [here](https://javadoc.io/doc/dev.openfeature/sdk/latest/) for the Javadocs.
120120

121121
## 🌟 Features
122122

123-
| Status | Features | Description |
124-
| ------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
125-
|| [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
126-
|| [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
127-
|| [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
128-
|| [Logging](#logging) | Integrate with popular logging packages. |
129-
|| [Named clients](#named-clients) | Utilize multiple providers in a single application. |
130-
|| [Eventing](#eventing) | React to state changes in the provider or flag management system. |
131-
|| [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
132-
|| [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
123+
| Status | Features | Description |
124+
| ------ |-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
125+
|| [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
126+
|| [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
127+
|| [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
128+
|| [Logging](#logging) | Integrate with popular logging packages. |
129+
|| [Named clients](#named-clients) | Utilize multiple providers in a single application. |
130+
|| [Eventing](#eventing) | React to state changes in the provider or flag management system. |
131+
|| [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
132+
|| [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context) for a transaction (e.g. an HTTP request or a thread). |
133+
|| [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
133134

134135
<sub>Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌</sub>
135136

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dev.openfeature.sdk;
2+
3+
/**
4+
* A {@link TransactionContextPropagator} that simply returns null.
5+
*/
6+
public class NoOpTransactionContextPropagator implements TransactionContextPropagator {
7+
8+
/**
9+
* {@inheritDoc}
10+
* @return null
11+
*/
12+
@Override
13+
public EvaluationContext getTransactionContext() {
14+
return null;
15+
}
16+
17+
/**
18+
* {@inheritDoc}
19+
*/
20+
@Override
21+
public void setTransactionContext(EvaluationContext evaluationContext) {
22+
23+
}
24+
}

src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java

+42
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
2727
private ProviderRepository providerRepository;
2828
private EventSupport eventSupport;
2929
private EvaluationContext evaluationContext;
30+
private TransactionContextPropagator transactionContextPropagator;
3031

3132
protected OpenFeatureAPI() {
3233
apiHooks = new ArrayList<>();
3334
providerRepository = new ProviderRepository();
3435
eventSupport = new EventSupport();
36+
transactionContextPropagator = new NoOpTransactionContextPropagator();
3537
}
3638

3739
private static class SingletonHolder {
@@ -96,6 +98,46 @@ public EvaluationContext getEvaluationContext() {
9698
}
9799
}
98100

101+
/**
102+
* Return the transaction context propagator.
103+
*/
104+
public TransactionContextPropagator getTransactionContextPropagator() {
105+
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
106+
return this.transactionContextPropagator;
107+
}
108+
}
109+
110+
/**
111+
* Sets the transaction context propagator.
112+
*
113+
* @throws IllegalArgumentException if {@code transactionContextPropagator} is null
114+
*/
115+
public void setTransactionContextPropagator(TransactionContextPropagator transactionContextPropagator) {
116+
if (transactionContextPropagator == null) {
117+
throw new IllegalArgumentException("Transaction context propagator cannot be null");
118+
}
119+
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
120+
this.transactionContextPropagator = transactionContextPropagator;
121+
}
122+
}
123+
124+
/**
125+
* Returns the currently defined transaction context using the registered transaction
126+
* context propagator.
127+
*
128+
* @return {@link EvaluationContext} The current transaction context
129+
*/
130+
public EvaluationContext getTransactionContext() {
131+
return this.transactionContextPropagator.getTransactionContext();
132+
}
133+
134+
/**
135+
* Sets the transaction context using the registered transaction context propagator.
136+
*/
137+
void setTransactionContext(EvaluationContext evaluationContext) {
138+
this.transactionContextPropagator.setTransactionContext(evaluationContext);
139+
}
140+
99141
/**
100142
* Set the default provider.
101143
*/

src/main/java/dev/openfeature/sdk/OpenFeatureClient.java

+26-14
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,6 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
105105
FeatureProvider provider;
106106

107107
try {
108-
final EvaluationContext apiContext;
109-
final EvaluationContext clientContext;
110-
111108
// openfeatureApi.getProvider() must be called once to maintain a consistent reference
112109
provider = openfeatureApi.getProvider(this.name);
113110

@@ -117,19 +114,9 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
117114
hookCtx = HookContext.from(key, type, this.getMetadata(),
118115
provider.getMetadata(), ctx, defaultValue);
119116

120-
// merge of: API.context, client.context, invocation.context
121-
apiContext = openfeatureApi.getEvaluationContext() != null
122-
? openfeatureApi.getEvaluationContext()
123-
: new ImmutableContext();
124-
clientContext = this.getEvaluationContext() != null
125-
? this.getEvaluationContext()
126-
: new ImmutableContext();
127-
128117
EvaluationContext ctxFromHook = hookSupport.beforeHooks(type, hookCtx, mergedHooks, hints);
129118

130-
EvaluationContext invocationCtx = ctx.merge(ctxFromHook);
131-
132-
EvaluationContext mergedCtx = apiContext.merge(clientContext.merge(invocationCtx));
119+
EvaluationContext mergedCtx = mergeEvaluationContext(ctxFromHook, ctx);
133120

134121
ProviderEvaluation<T> providerEval = (ProviderEvaluation<T>) createProviderEvaluation(type, key,
135122
defaultValue, provider, mergedCtx);
@@ -157,6 +144,31 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
157144
return details;
158145
}
159146

147+
/**
148+
* Merge hook and invocation contexts with API, transaction and client contexts.
149+
*
150+
* @param hookContext hook context
151+
* @param invocationContext invocation context
152+
* @return merged evaluation context
153+
*/
154+
private EvaluationContext mergeEvaluationContext(
155+
EvaluationContext hookContext,
156+
EvaluationContext invocationContext) {
157+
final EvaluationContext apiContext = openfeatureApi.getEvaluationContext() != null
158+
? openfeatureApi.getEvaluationContext()
159+
: new ImmutableContext();
160+
final EvaluationContext clientContext = this.getEvaluationContext() != null
161+
? this.getEvaluationContext()
162+
: new ImmutableContext();
163+
final EvaluationContext transactionContext = openfeatureApi.getTransactionContext() != null
164+
? openfeatureApi.getTransactionContext()
165+
: new ImmutableContext();
166+
167+
EvaluationContext mergedInvocationCtx = invocationContext.merge(hookContext);
168+
169+
return apiContext.merge(transactionContext.merge(clientContext.merge(mergedInvocationCtx)));
170+
}
171+
160172
private <T> ProviderEvaluation<?> createProviderEvaluation(
161173
FlagValueType type,
162174
String key,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dev.openfeature.sdk;
2+
3+
/**
4+
* A {@link ThreadLocalTransactionContextPropagator} is a transactional context propagator
5+
* that uses a ThreadLocal to persist a transactional context for the duration of a single thread.
6+
*
7+
* @see TransactionContextPropagator
8+
*/
9+
public class ThreadLocalTransactionContextPropagator implements TransactionContextPropagator {
10+
11+
private final ThreadLocal<EvaluationContext> evaluationContextThreadLocal = new ThreadLocal<>();
12+
13+
/**
14+
* {@inheritDoc}
15+
*/
16+
@Override
17+
public EvaluationContext getTransactionContext() {
18+
return this.evaluationContextThreadLocal.get();
19+
}
20+
21+
/**
22+
* {@inheritDoc}
23+
*/
24+
@Override
25+
public void setTransactionContext(EvaluationContext evaluationContext) {
26+
this.evaluationContextThreadLocal.set(evaluationContext);
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package dev.openfeature.sdk;
2+
3+
/**
4+
* {@link TransactionContextPropagator} is responsible for persisting a transactional context
5+
* for the duration of a single transaction.
6+
* Examples of potential transaction specific context include: a user id, user agent, IP.
7+
* Transaction context is merged with evaluation context prior to flag evaluation.
8+
* <p>
9+
* The precedence of merging context can be seen in
10+
* <a href=https://openfeature.dev/specification/sections/evaluation-context#requirement-323>the specification</a>.
11+
* </p>
12+
*/
13+
public interface TransactionContextPropagator {
14+
15+
/**
16+
* Returns the currently defined transaction context using the registered transaction
17+
* context propagator.
18+
*
19+
* @return {@link EvaluationContext} The current transaction context
20+
*/
21+
EvaluationContext getTransactionContext();
22+
23+
/**
24+
* Sets the transaction context.
25+
*/
26+
void setTransactionContext(EvaluationContext evaluationContext);
27+
}

0 commit comments

Comments
 (0)