Skip to content

Commit

Permalink
feat!: client/single-context, shutdown, and event specs
Browse files Browse the repository at this point in the history
Signed-off-by: Todd Baert <toddbaert@gmail.com>
  • Loading branch information
toddbaert committed Jan 10, 2023
1 parent 928d513 commit 77ef7a1
Show file tree
Hide file tree
Showing 9 changed files with 595 additions and 152 deletions.
295 changes: 232 additions & 63 deletions specification.json

Large diffs are not rendered by default.

81 changes: 55 additions & 26 deletions specification/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,36 @@ This document defines some terms that are used across this specification.

<!-- toc -->

- [Feature Flag](#feature-flag)
- [User Roles](#user-roles)
- [Application Author](#application-author)
- [Application Integrator](#application-integrator)
- [Provider Author](#provider-author)
- [Integration Author](#integration-author)
- [Library Author](#library-author)
- [Common](#common)
- [Feature Flag SDK](#feature-flag-sdk)
- [Feature Flag API](#feature-flag-api)
- [Evaluation API](#evaluation-api)
- [Flag Management System](#flag-management-system)
- [Provider](#provider)
- [Integration](#integration)
- [Evaluation Context](#evaluation-context)
- [Evaluating Flag Values](#evaluating-flag-values)
- [Resolving Flag Values](#resolving-flag-values)
- [Flagging specifics](#flagging-specifics)
- [Flag](#flag)
- [Flag Key](#flag-key)
- [Variant](#variant)
- [Values](#values)
- [Targeting](#targeting)
- [Targeting Key](#targeting-key)
- [Fractional Evaluation](#fractional-evaluation)
- [Rule](#rule)
- [Glossary](#glossary)
- [Feature Flag](#feature-flag)
- [User Roles](#user-roles)
- [Application Author](#application-author)
- [Application Integrator](#application-integrator)
- [Provider Author](#provider-author)
- [Integration Author](#integration-author)
- [Library Author](#library-author)
- [Common](#common)
- [Feature Flag SDK](#feature-flag-sdk)
- [Feature Flag API](#feature-flag-api)
- [Evaluation API](#evaluation-api)
- [Flag Management System](#flag-management-system)
- [Provider](#provider)
- [Integration](#integration)
- [Evaluation Context](#evaluation-context)
- [Evaluating Flag Values](#evaluating-flag-values)
- [Resolving Flag Values](#resolving-flag-values)
- [Flagging specifics](#flagging-specifics)
- [Flag](#flag)
- [Flag Key](#flag-key)
- [Variant](#variant)
- [Values](#values)
- [Targeting](#targeting)
- [Targeting Key](#targeting-key)
- [Fractional Evaluation](#fractional-evaluation)
- [Rule](#rule)
- [SDK Paradigms](#sdk-paradigms)
- [Multi-Context Paradigm](#multi-context-paradigm)
- [Single-Context Paradigm](#single-context-paradigm)

<!-- tocstop -->

Expand Down Expand Up @@ -161,3 +165,28 @@ Pseudorandomly resolve flag values using a context property, such as a targeting
### Rule

A rule is some criteria that's used to determine which variant a particular context should be mapped to.

## SDK Paradigms

Feature flag frameworks tend to come in two categories: those designed for use with a single user client application, and those designed for multi-user applications, such as web server applications. Some parts of the OpenFeature specification diverge depending on which paradigm the implementation seeks to adhere to.

### Multi-Context Paradigm

Server-side use cases typically perform flag evaluations on behalf of many users, with each request or event being associated with a particular user or client.
For this reason, server frameworks typically operate something like this:

- the application is initialized with some static context (geography, service name, hostname, etc)
- with each request or event, relevant dynamic context (for example, user session data) is provided to flag evaluations

### Single-Context Paradigm

In contrast with server-side or other service-type applications, client side applications typically operate in the context of a single user.
Most feature flagging libraries for these applications have been designed with this in mind.
Frequently, Client/web libraries operate something like this:

- an initialization occurs, which fetches evaluated flags in bulk for a given context (user)
- the evaluated flags are cached in the library
- flag evaluations take place against this cache, without a need to provide context (context was already used to evaluate flags in bulk)
- Functions are exposed on the libraries that signal the cache is no longer valid, and must be reconciled based on a context change. This frequently involves a network request or I/O operation.

Not all client libraries work this way, but generally, libraries that accept dynamic context per evaluation can build providers which conform to this model with relative ease, while the reverse is not true.
143 changes: 114 additions & 29 deletions specification/sections/01-flag-evaluation.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ The `evaluation API` allows for the evaluation of feature flag values, independe
It's important that multiple instances of the `API` not be active, so that state stored therein, such as the registered `provider`, static global `evaluation context`, and globally configured `hooks` allow the `API` to behave predictably. This can be difficult in some runtimes or languages, but implementors should make their best effort to ensure that only a single instance of the `API` is used.

### Setting a provider

#### Requirement 1.1.2

> The `API` **MUST** provide a function to set the global `provider` singleton, which accepts an API-conformant `provider` implementation.
> The `API` **MUST** provide a `provider mutator`, a function to set the global `provider` singleton, which accepts an API-conformant `provider` implementation.
```typescript
// example provider mutator
Expand All @@ -33,6 +35,12 @@ See [provider](./02-providers.md) for details.

#### Requirement 1.1.3

> The `provider mutator` function **MUST** run the `initialize` function on the newly registered provider.
See [provider initialization](./02-providers.md#24-initialization).

#### Requirement 1.1.4

> The `API` **MUST** provide a function to add `hooks` which accepts one or more API-conformant `hooks`, and appends them to the collection of any previously added hooks. When new hooks are added, previously added hooks are not removed.
```typescript
Expand All @@ -42,7 +50,7 @@ OpenFeature.addHooks([new MyHook()]);

See [hooks](./04-hooks.md) for details.

#### Requirement 1.1.4
#### Requirement 1.1.5

> The API **MUST** provide a function for retrieving the metadata field of the configured `provider`.
Expand All @@ -53,7 +61,7 @@ OpenFeature.getProviderMetadata();

See [provider](./02-providers.md) for details.

#### Requirement 1.1.5
#### Requirement 1.1.6

> The `API` **MUST** provide a function for creating a `client` which accepts the following options:
>
Expand All @@ -68,7 +76,7 @@ OpenFeature.getClient({

The name is a logical identifier for the client.

#### Requirement 1.1.6
#### Requirement 1.1.7

> The client creation function **MUST NOT** throw, or otherwise abnormally terminate.
Expand All @@ -95,11 +103,41 @@ See [hooks](./04-hooks.md) for details.
client.getMetadata().getName(); // "my-client"
```

#### 1.3. Flag Evaluation
### 1.3. Flag Evaluation

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

##### Requirement 1.3.1
#### Condition 1.3.1

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

> The implementation uses the single-context paradigm.
##### Conditional Requirement 1.3.1.1

> The `client` **MUST** provide methods for typed flag evaluation, including boolean, numeric, string, and structure, with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required), and `evaluation options` (optional), which returns the flag value.
```typescript
// example boolean flag evaluation
boolean myBool = client.getBooleanValue('bool-flag', false);

// example overloaded string flag evaluation with optional params
string myString = client.getStringValue('string-flag', 'N/A', options);

// example number flag evaluation
number myNumber = client.getNumberValue('number-flag', 75);

// example overloaded structure flag evaluation with optional params
MyStruct myStruct = client.getObjectValue<MyStruct>('structured-flag', { text: 'N/A', percentage: 75 }, options);
```

See [evaluation context](./03-evaluation-context.md) for details.

#### Condition 1.3.2

> The implementation uses the multi-context paradigm.
##### Conditional Requirement 1.3.2.1

> The `client` **MUST** provide methods for typed flag evaluation, including boolean, numeric, string, and structure, with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required), `evaluation context` (optional), and `evaluation options` (optional), which returns the flag value.
Expand All @@ -119,32 +157,61 @@ MyStruct myStruct = client.getObjectValue<MyStruct>('structured-flag', { text: '

See [evaluation context](./03-evaluation-context.md) for details.

##### Condition 1.3.2
#### Condition 1.3.3

> The implementation language differentiates between floating-point numbers and integers.
###### Conditional Requirement 1.3.2.1
##### Conditional Requirement 1.3.3.1

> The client **SHOULD** provide functions for floating-point numbers and integers, consistent with language idioms.
```go
// example in GO
GetIntValue(flag string, defaultValue int64, evalCtx EvaluationContext, options ...EvaluationOption) (int64, error)
```
int getIntValue(String flag, int defaultValue);
GetFloatValue(flag string, defaultValue float64, evalCtx EvaluationContext, options ...EvaluationOption) (float64, error)
long getFloatValue(String flag, long defaultValue);
```

See [types](../types.md) for details.

##### Requirement 1.3.3
#### Requirement 1.3.4

> The `client` **SHOULD** guarantee the returned value of any typed flag evaluation method is of the expected type. If the value returned by the underlying provider implementation does not match the expected type, it's to be considered abnormal execution, and the supplied `default value` should be returned.
#### 1.4. Detailed Flag Evaluation
### 1.4. Detailed Flag Evaluation

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

##### Requirement 1.4.1

#### Condition 1.4.1

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

> The implementation uses the single-context paradigm.
##### Conditional Requirement 1.4.1.1

> The `client` **MUST** provide methods for detailed flag value evaluation with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required), and `evaluation options` (optional), which returns an `evaluation details` structure.
```typescript
// example detailed boolean flag evaluation
FlagEvaluationDetails<boolean> myBoolDetails = client.getBooleanDetails('bool-flag', false);

// example detailed string flag evaluation
FlagEvaluationDetails<string> myStringDetails = client.getStringDetails('string-flag', 'N/A', options);

// example detailed number flag evaluation
FlagEvaluationDetails<number> myNumberDetails = client.getNumberDetails('number-flag', 75);

// example detailed structure flag evaluation
FlagEvaluationDetails<MyStruct> myStructDetails = client.getObjectDetails<MyStruct>('structured-flag', { text: 'N/A', percentage: 75 }, options);

```

#### Condition 1.4.2

> The implementation uses the multi-context paradigm.
##### Conditional Requirement 1.4.2.1

> The `client` **MUST** provide methods for detailed flag value evaluation with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required), `evaluation context` (optional), and `evaluation options` (optional), which returns an `evaluation details` structure.
Expand All @@ -163,66 +230,84 @@ FlagEvaluationDetails<MyStruct> myStructDetails = client.getObjectDetails<MyStru

```

##### Requirement 1.4.2
#### Requirement 1.4.3

> The `evaluation details` structure's `value` field **MUST** contain the evaluated flag value.
##### Condition 1.4.3
#### Condition 1.4.4

> The language supports generics (or an equivalent feature).
###### Conditional Requirement 1.4.3.1
##### Conditional Requirement 1.4.4.1

> The `evaluation details` structure **SHOULD** accept a generic argument (or use an equivalent language feature) which indicates the type of the wrapped `value` field.
##### Requirement 1.4.4
#### Requirement 1.4.5

> The `evaluation details` structure's `flag key` field **MUST** contain the `flag key` argument passed to the detailed flag evaluation method.
##### Requirement 1.4.5
#### Requirement 1.4.6

> In cases of normal execution, the `evaluation details` structure's `variant` field **MUST** contain the value of the `variant` field in the `flag resolution` structure returned by the configured `provider`, if the field is set.
##### Requirement 1.4.6
#### Requirement 1.4.7

> In cases of normal execution, the `evaluation details` structure's `reason` field **MUST** contain the value of the `reason` field in the `flag resolution` structure returned by the configured `provider`, if the field is set.
##### Requirement 1.4.7
#### Requirement 1.4.8

> In cases of abnormal execution, the `evaluation details` structure's `error code` field **MUST** contain an `error code`.
See [error code](../types.md#error-code) for details.

##### Requirement 1.4.8
#### Requirement 1.4.9

> In cases of abnormal execution (network failure, unhandled error, etc) the `reason` field in the `evaluation details` **SHOULD** indicate an error.
##### Requirement 1.4.9
#### Requirement 1.4.10

> Methods, functions, or operations on the client **MUST NOT** throw exceptions, or otherwise abnormally terminate. Flag evaluation calls must always return the `default value` in the event of abnormal execution. Exceptions include functions or methods for the purposes for configuration or setup.
Configuration code includes code to set the provider, instantiate providers, and configure the global API object.

##### Requirement 1.4.10
#### Requirement 1.4.11

> In the case of abnormal execution, the client **SHOULD** log an informative error message.
Implementations may define a standard logging interface that can be supplied as an optional argument to the client creation function, which may wrap standard logging functionality of the implementation language.

##### Requirement 1.4.11
#### Requirement 1.4.12

> The `client` **SHOULD** provide asynchronous or non-blocking mechanisms for flag evaluation.
It's recommended to provide non-blocking mechanisms for flag evaluation, particularly in languages or environments wherein there's a single thread of execution.

##### Requirement 1.4.12
#### Requirement 1.4.13

> In cases of abnormal execution, the `evaluation details` structure's `error message` field **MAY** contain a string containing additional details about the nature of the error.
#### Evaluation Options
### Evaluation Options

##### Requirement 1.5.1
#### Requirement 1.5.1

> The `evaluation options` structure's `hooks` field denotes an ordered collection of hooks that the client **MUST** execute for the respective flag evaluation, in addition to those already configured.
See [hooks](./04-hooks.md) for details.

### 1.6. Shutdown

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

#### Requirement 1.6.1

> The API **MUST** define a `shutdown` or `dispose` function, which, when called, must call the respective `shutdown`/`dispose` method on the active provider.
see: [`shutdown`/`dispose`](./02-providers.md#24-shutdown)

#### Requirement 1.6.2

> When the API's `provider mutator` is invoked, the `shutdown`/`dispose` method on the active provider **MUST** run, if defined.
Setting a new provider means the previous provider is no longer in use, and should therefor be disposed of to prevent resource leaks.

see: [setting a provider](#setting-a-provider), [shutdown](./02-providers.md#24-shutdown)
Loading

0 comments on commit 77ef7a1

Please sign in to comment.