Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: context propagation #227

Merged
merged 18 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion specification.json
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@
{
"id": "Requirement 3.2.3",
"machine_id": "requirement_3_2_3",
"content": "Evaluation context MUST be merged in the order: API (global; lowest precedence) - client - invocation - before hooks (highest precedence), with duplicate values being overwritten.",
"content": "Evaluation context MUST be merged in the order: API (global; lowest precedence) - transaction - client - invocation - before hooks (highest precedence), with duplicate values being overwritten.",
"RFC 2119 keyword": "MUST",
"children": []
},
Expand All @@ -565,6 +565,65 @@
}
]
},
{
"id": "Condition 3.3.1",
"machine_id": "condition_3_3_1",
"content": "The implementation uses the dynamic-context paradigm.",
"RFC 2119 keyword": null,
"children": [
{
"id": "Conditional Requirement 3.3.1.1",
"machine_id": "conditional_requirement_3_3_1_1",
"content": "The API SHOULD have a method for setting a `transaction context propagator`.",
"RFC 2119 keyword": "SHOULD",
"children": []
}
]
},
{
"id": "Condition 3.3.1.2",
"machine_id": "condition_3_3_1_2",
"content": "The SDK implements context propagation.",
"RFC 2119 keyword": null,
"children": [
{
"id": "Conditional Requirement 3.3.1.2.1",
"machine_id": "conditional_requirement_3_3_1_2_1",
"content": "The API MUST have a method for setting the `evaluation context` of the `transaction context propagator` for the current transaction.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Conditional Requirement 3.3.1.2.2",
"machine_id": "conditional_requirement_3_3_1_2_2",
"content": "A `transaction context propagator` MUST have a method for setting the `evaluation context` of the current transaction.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Conditional Requirement 3.3.1.2.3",
"machine_id": "conditional_requirement_3_3_1_2_3",
"content": "A `transaction context propagator` MUST have a method for getting the `evaluation context` of the current transaction.",
"RFC 2119 keyword": "MUST",
"children": []
}
]
},
{
"id": "Condition 3.3.2",
"machine_id": "condition_3_3_2",
"content": "The implementation uses the static-context paradigm.",
"RFC 2119 keyword": null,
"children": [
{
"id": "Conditional Requirement 3.3.2.1",
"machine_id": "conditional_requirement_3_3_2_1",
"content": "The API MUST NOT have a method for setting a `transaction context propagator`.",
"RFC 2119 keyword": "MUST NOT",
"children": []
}
]
},
{
"id": "Requirement 4.1.1",
"machine_id": "requirement_4_1_1",
Expand Down
5 changes: 5 additions & 0 deletions specification/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ This document defines some terms that are used across this specification.
- [Domain](#domain)
- [Integration](#integration)
- [Evaluation Context](#evaluation-context)
- [Transaction Context Propagator](#transaction-context-propagator)
- [Evaluating Flag Values](#evaluating-flag-values)
- [Resolving Flag Values](#resolving-flag-values)
- [Flagging specifics](#flagging-specifics)
Expand Down Expand Up @@ -120,6 +121,10 @@ An SDK-compliant secondary function that is abstracted by the Feature Flag API,

Context object for flag evaluation, which may contain information about the runtime environment, details of the transport method encapsulating the flag evaluation, the host, the client, the subject (user), etc. This data may be used as a basis for differential evaluation of feature flags based on rules that can be defined in the flag system. Context data may be provided by merging static global context, arguments to flag evaluation, and implicit language-dependant state propagation mechanisms (thread-local storage, promise chains, continuations, etc).

### Transaction Context Propagator

An SDK-compliant implementation that stores and returns transaction-specific evaluation context. A _transaction_ might be a web request or application event, which carries its contextual data in a thread or continuation storage.

### Evaluating Flag Values

The process of retrieving a feature flag value in it's entirety, including:
Expand Down
108 changes: 98 additions & 10 deletions specification/sections/03-evaluation-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,25 @@ See [setting a provider](./01-flag-evaluation.md#setting-a-provider), [domain](.

#### Requirement 3.2.3

> Evaluation context **MUST** be merged in the order: API (global; lowest precedence) -> client -> invocation -> before hooks (highest precedence), with duplicate values being overwritten.
> Evaluation context **MUST** be merged in the order: API (global; lowest precedence) -> transaction -> client -> invocation -> before hooks (highest precedence), with duplicate values being overwritten.
lukas-reining marked this conversation as resolved.
Show resolved Hide resolved

Any fields defined in the client `evaluation context` will overwrite duplicate fields defined globally, and fields defined in the invocation `evaluation context` will overwrite duplicate fields defined globally or on the client. Any resulting `evaluation context` from a [before hook](./04-hooks.md#requirement-434) will overwrite duplicate fields defined globally, on the client, or in the invocation.
Any fields defined in the transaction `evaluation context` will overwrite duplicate fields defined in the global `evaluation context`, any fields defined in the client `evaluation context` will overwrite duplicate fields defined in the transaction `evaluation context`, and fields defined in the invocation `evaluation context` will overwrite duplicate fields defined globally or on the client. Any resulting `evaluation context` from a [before hook](./04-hooks.md#requirement-434) will overwrite duplicate fields defined globally, on the client, or in the invocation.

```mermaid
flowchart LR
global("API (global)")
client("Client")
invocation("Invocation")
hook("Before Hooks")
global --> client
client --> invocation
invocation --> hook
global("API (global)")
transaction("Transaction")
client("Client")
invocation("Invocation")
hook("Before Hooks")
global --> transaction
transaction --> client
client --> invocation
invocation --> hook
```

This describes the precedence of all `evaluation context` variants. Depending on the `paradigm`, not all variants might be available in an `SDK` implementation.

#### Condition 3.2.4

[![experimental](https://img.shields.io/static/v1?label=Status&message=experimental&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental)
Expand All @@ -128,4 +132,88 @@ The SDK implementation must run the `on context changed` handler on all register

> When the `evaluation context` for a specific provider is set, the `on context changed` handler **MUST** only run on the associated provider.

The SDK implementation must run the `on context changed` handler only on the provider that is scoped to the mutated `evaluation context`.
The SDK implementation must run the `on context changed` handler only on the provider that is scoped to the mutated `evaluation context`.

### 3.3 Context Propagation

[![experimental](https://img.shields.io/static/v1?label=Status&message=experimental&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental)

`Transaction context` is a container for transaction-specific `evaluation context` (e.g. user id, user agent, IP).
Transaction context can be set where specific data is available (e.g. an auth service or request handler) and by using the `transaction context propagator` it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread).

The following shows a possible TypeScript implementation using [AsyncLocalStorage (async_hooks)](https://nodejs.org/api/async_context.html):

```typescript
export class AsyncLocalStorageTransactionContext implements TransactionContextPropagator {
private asyncLocalStorage = new AsyncLocalStorage<EvaluationContext>();

getTransactionContext(): EvaluationContext {
return this.asyncLocalStorage.getStore() ?? {};
}
setTransactionContext(context: EvaluationContext, callback: () => void): void {
this.asyncLocalStorage.run(context, callback);
}
}

/**
* This example is based on an express middleware.
*/
app.use((req: Request, res: Response, next: NextFunction) => {
const ip = res.headers.get("X-Forwarded-For")
OpenFeature.setTransactionContext({ targetingKey: req.user.id, ipAddress: ip }, () => {
// The transaction context is used in any flag evaluation throughout the whole call chain of next
next();
});
})
```

#### Condition 3.3.1

> The implementation uses the dynamic-context paradigm.

see: [dynamic-context paradigm](../glossary.md#dynamic-context-paradigm)

##### Conditional Requirement 3.3.1.1

> The API **SHOULD** have a method for setting a `transaction context propagator`.

If there already is a `transaction context propagator`, it is replaced with the new one.

#### Condition 3.3.1.2

> The SDK implements context propagation.

A language may not have any applicable way of implementing `transaction context propagation` so the language SDK might not implement context propagation.

##### Conditional Requirement 3.3.1.2.1

> The API **MUST** have a method for setting the `evaluation context` of the `transaction context propagator` for the current transaction.

If a `transaction context propagator` is set, the SDK will call the [method defined in 3.3.1.3](#conditional-requirement-33122) with this `evaluation context` and so this `evaluation context` will be available during the current transaction.
If no `transaction context propagator` is set, this `evaluation context` is not used for evaluations.
This method then can be used for example in a request handler to add request-specific information to the `evaluation context`.

##### Conditional Requirement 3.3.1.2.2

> A `transaction context propagator` **MUST** have a method for setting the `evaluation context` of the current transaction.

A `transaction context propagator` is responsible for persisting context for the duration of a single transaction.
Typically, a transaction context propagator will propagate the context using a language-specific carrier such as [ThreadLocal (Java)](https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html), [async hooks (Node.js)](https://nodejs.org/api/async_hooks.html), [Context (Go)](https://pkg.go.dev/context) or another similar mechanism.

##### Conditional Requirement 3.3.1.2.3

> A `transaction context propagator` **MUST** have a method for getting the `evaluation context` of the current transaction.

This will be used by the SDK implementation when merging the context for evaluating a feature flag.

#### Condition 3.3.2

> The implementation uses the static-context paradigm.

see: [static-context paradigm](../glossary.md#static-context-paradigm)

##### Conditional Requirement 3.3.2.1

> The API **MUST NOT** have a method for setting a `transaction context propagator`.

In the static-context paradigm, context is global, so there must not be different contexts between transactions.
Loading