Skip to content

Commit

Permalink
feat: context propagation
Browse files Browse the repository at this point in the history
Signed-off-by: Sviatoslav Sharaev <[email protected]>
  • Loading branch information
ssharaev authored and Kavindu-Dodan committed Mar 18, 2024
1 parent b249bfb commit d8c39f8
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 42 deletions.
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,17 @@ See [here](https://javadoc.io/doc/dev.openfeature/sdk/latest/) for the Javadocs.

## 🌟 Features

| Status | Features | Description |
| ------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|| [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
|| [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
|| [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
|| [Logging](#logging) | Integrate with popular logging packages. |
|| [Named clients](#named-clients) | Utilize multiple providers in a single application. |
|| [Eventing](#eventing) | React to state changes in the provider or flag management system. |
|| [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
|| [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
| Status | Features | Description |
| ------ |-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
|| [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
|| [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
|| [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
|| [Logging](#logging) | Integrate with popular logging packages. |
|| [Named clients](#named-clients) | Utilize multiple providers in a single application. |
|| [Eventing](#eventing) | React to state changes in the provider or flag management system. |
|| [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
|| [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). |
|| [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dev.openfeature.sdk;

/**
* A {@link TransactionContextPropagator} that simply returns null.
*/
public class NoOpTransactionContextPropagator implements TransactionContextPropagator {

/**
* {@inheritDoc}
* @return null
*/
@Override
public EvaluationContext getTransactionContext() {
return null;
}

/**
* {@inheritDoc}
*/
@Override
public void setTransactionContext(EvaluationContext evaluationContext) {

}
}
42 changes: 42 additions & 0 deletions src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
private ProviderRepository providerRepository;
private EventSupport eventSupport;
private EvaluationContext evaluationContext;
private TransactionContextPropagator transactionContextPropagator;

protected OpenFeatureAPI() {
apiHooks = new ArrayList<>();
providerRepository = new ProviderRepository();
eventSupport = new EventSupport();
transactionContextPropagator = new NoOpTransactionContextPropagator();
}

private static class SingletonHolder {
Expand Down Expand Up @@ -96,6 +98,46 @@ public EvaluationContext getEvaluationContext() {
}
}

/**
* Return the transaction context propagator.
*/
public TransactionContextPropagator getTransactionContextPropagator() {
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
return this.transactionContextPropagator;
}
}

/**
* Sets the transaction context propagator.
*
* @throws IllegalArgumentException if {@code transactionContextPropagator} is null
*/
public void setTransactionContextPropagator(TransactionContextPropagator transactionContextPropagator) {
if (transactionContextPropagator == null) {
throw new IllegalArgumentException("Transaction context propagator cannot be null");
}
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
this.transactionContextPropagator = transactionContextPropagator;
}
}

/**
* Returns the currently defined transaction context using the registered transaction
* context propagator.
*
* @return {@link EvaluationContext} The current transaction context
*/
public EvaluationContext getTransactionContext() {
return this.transactionContextPropagator.getTransactionContext();
}

/**
* Sets the transaction context using the registered transaction context propagator.
*/
void setTransactionContext(EvaluationContext evaluationContext) {
this.transactionContextPropagator.setTransactionContext(evaluationContext);
}

/**
* Set the default provider.
*/
Expand Down
40 changes: 26 additions & 14 deletions src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,6 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
FeatureProvider provider;

try {
final EvaluationContext apiContext;
final EvaluationContext clientContext;

// openfeatureApi.getProvider() must be called once to maintain a consistent reference
provider = openfeatureApi.getProvider(this.name);

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

// merge of: API.context, client.context, invocation.context
apiContext = openfeatureApi.getEvaluationContext() != null
? openfeatureApi.getEvaluationContext()
: new ImmutableContext();
clientContext = this.getEvaluationContext() != null
? this.getEvaluationContext()
: new ImmutableContext();

EvaluationContext ctxFromHook = hookSupport.beforeHooks(type, hookCtx, mergedHooks, hints);

EvaluationContext invocationCtx = ctx.merge(ctxFromHook);

EvaluationContext mergedCtx = apiContext.merge(clientContext.merge(invocationCtx));
EvaluationContext mergedCtx = mergeEvaluationContext(ctxFromHook, ctx);

ProviderEvaluation<T> providerEval = (ProviderEvaluation<T>) createProviderEvaluation(type, key,
defaultValue, provider, mergedCtx);
Expand Down Expand Up @@ -157,6 +144,31 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
return details;
}

/**
* Merge hook and invocation contexts with API, transaction and client contexts.
*
* @param hookContext hook context
* @param invocationContext invocation context
* @return merged evaluation context
*/
private EvaluationContext mergeEvaluationContext(
EvaluationContext hookContext,
EvaluationContext invocationContext) {
final EvaluationContext apiContext = openfeatureApi.getEvaluationContext() != null
? openfeatureApi.getEvaluationContext()
: new ImmutableContext();
final EvaluationContext clientContext = this.getEvaluationContext() != null
? this.getEvaluationContext()
: new ImmutableContext();
final EvaluationContext transactionContext = openfeatureApi.getTransactionContext() != null
? openfeatureApi.getTransactionContext()
: new ImmutableContext();

EvaluationContext mergedInvocationCtx = invocationContext.merge(hookContext);

return apiContext.merge(transactionContext.merge(clientContext.merge(mergedInvocationCtx)));
}

private <T> ProviderEvaluation<?> createProviderEvaluation(
FlagValueType type,
String key,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dev.openfeature.sdk;

/**
* A {@link ThreadLocalTransactionContextPropagator} is a transactional context propagator
* that uses a ThreadLocal to persist a transactional context for the duration of a single thread.
*
* @see TransactionContextPropagator
*/
public class ThreadLocalTransactionContextPropagator implements TransactionContextPropagator {

private final ThreadLocal<EvaluationContext> evaluationContextThreadLocal = new ThreadLocal<>();

/**
* {@inheritDoc}
*/
@Override
public EvaluationContext getTransactionContext() {
return this.evaluationContextThreadLocal.get();
}

/**
* {@inheritDoc}
*/
@Override
public void setTransactionContext(EvaluationContext evaluationContext) {
this.evaluationContextThreadLocal.set(evaluationContext);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dev.openfeature.sdk;

/**
* {@link TransactionContextPropagator} is responsible for persisting a transactional context
* for the duration of a single transaction.
* Examples of potential transaction specific context include: a user id, user agent, IP.
* Transaction context is merged with evaluation context prior to flag evaluation.
* <p>
* The precedence of merging context can be seen in
* <a href=https://openfeature.dev/specification/sections/evaluation-context#requirement-323>the specification</a>.
* </p>
*/
public interface TransactionContextPropagator {

/**
* Returns the currently defined transaction context using the registered transaction
* context propagator.
*
* @return {@link EvaluationContext} The current transaction context
*/
EvaluationContext getTransactionContext();

/**
* Sets the transaction context.
*/
void setTransactionContext(EvaluationContext evaluationContext);
}
Loading

0 comments on commit d8c39f8

Please sign in to comment.