Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 31 additions & 2 deletions specification.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
{
"id": "Requirement 1.1.2.2",
"machine_id": "requirement_1_1_2_2",
"content": "The `provider mutator` function MUST invoke the `initialize` function on the newly registered provider before using it to resolve flag values.",
"content": "The `provider mutator` function MUST invoke the `initialize` function on the newly registered provider before using it to resolve flag values, supplying the bound `domain`, if any.",
"RFC 2119 keyword": "MUST",
"children": []
},
Expand Down Expand Up @@ -70,6 +70,21 @@
"RFC 2119 keyword": "MUST NOT",
"children": []
},
{
"id": "Condition 1.1.8",
"machine_id": "condition_1_1_8",
"content": "The `provider` declares that it is `domain-scoped`.",
"RFC 2119 keyword": null,
"children": [
{
"id": "Conditional Requirement 1.1.8.1",
"machine_id": "conditional_requirement_1_1_8_1",
"content": "The `provider mutator` MUST NOT bind a `domain-scoped` provider instance to more than one `domain`, rejecting any attempt to bind an already-bound instance to an additional `domain`.",
"RFC 2119 keyword": "MUST NOT",
"children": []
}
]
},
{
"id": "Requirement 1.2.1",
"machine_id": "requirement_1_2_1",
Expand Down Expand Up @@ -510,7 +525,7 @@
{
"id": "Requirement 2.4.1",
"machine_id": "requirement_2_4_1",
"content": "The `provider` MAY define an initialization function which accepts the global `evaluation context` as an argument and performs initialization logic relevant to the provider.",
"content": "The `provider` MAY define an initialization function which accepts the global `evaluation context` and an optional bound `domain`, which performs initialization logic relevant to the provider.",
"RFC 2119 keyword": "MAY",
"children": []
},
Expand All @@ -529,6 +544,20 @@
}
]
},
{
"id": "Requirement 2.4.3",
"machine_id": "requirement_2_4_3",
"content": "The `provider` MAY declare that it is `domain-scoped`, indicating that it maintains state specific to a single `domain`, such as a persistent cache, that cannot be shared across `domains`.",
"RFC 2119 keyword": "MAY",
"children": []
},
{
"id": "Requirement 2.4.4",
"machine_id": "requirement_2_4_4",
"content": "A `provider` that declares itself `domain-scoped` MUST accept the bound `domain` during initialization.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Requirement 2.5.1",
"machine_id": "requirement_2_5_1",
Expand Down
17 changes: 15 additions & 2 deletions specification/sections/01-flag-evaluation.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ See [provider](./02-providers.md), [creating clients](#creating-clients) for det

#### Requirement 1.1.2.2

> The `provider mutator` function **MUST** invoke the `initialize` function on the newly registered provider before using it to resolve flag values.
> The `provider mutator` function **MUST** invoke the `initialize` function on the newly registered provider before using it to resolve flag values, supplying the bound `domain`, if any.

Application authors can await the newly set `provider's` readiness using the `PROVIDER_READY` event.
Provider instances which are already active (because they have been bound to another `domain` or otherwise) need not be initialized again.
Expand Down Expand Up @@ -147,6 +147,19 @@ See [setting a provider](#setting-a-provider), [domain](../glossary.md#domain) f

Clients may be created in critical code paths, and even per-request in server-side HTTP contexts. Therefore, in keeping with the principle that OpenFeature should never cause abnormal execution of the first party application, this function should never throw. Abnormal execution in initialization should instead occur during provider registration.

#### Condition 1.1.8

> The `provider` declares that it is `domain-scoped`.

see: [Requirement 2.4.3](./02-providers.md#requirement-243)

##### Conditional Requirement 1.1.8.1

> The `provider mutator` **MUST NOT** bind a `domain-scoped` provider instance to more than one `domain`, rejecting any attempt to bind an already-bound instance to an additional `domain`.

A `domain-scoped` provider keys per-`domain` state on the single `domain` supplied to its `initialize` function.
Rejection should occur in a manner idiomatic to the implementation language (throwing, returning an error, etc.), and leaves any existing binding intact.

### 1.2. Client Usage

#### Requirement 1.2.1
Expand Down Expand Up @@ -577,7 +590,7 @@ import { createIsolatedOpenFeatureAPI } from '@openfeature/web-sdk/isolated';
> A `provider` instance **SHOULD NOT** be registered with more than one `API` instance simultaneously.

Because the `API` instance manages the [lifecycle](./02-providers.md) of its associated providers (including initialization, shutdown, and event handling), binding a `provider` to more than one `API` instance could result in undefined behavior.
A `provider` instance can be registered with multiple `domains` within a single `API` instance.
A `provider` instance can be registered with multiple `domains` within a single `API` instance, unless it declares itself `domain-scoped` (see [Requirement 2.4.3](./02-providers.md#requirement-243)), in which case it can be bound to at most one `domain`.
When a provider is no longer associated with an `API` instance, it can be registered to another.

See [setting a provider](#setting-a-provider), [domain](../glossary.md#domain) for details.
27 changes: 24 additions & 3 deletions specification/sections/02-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,19 +170,25 @@ class MyProvider implements Provider {

#### Requirement 2.4.1

> The `provider` **MAY** define an initialization function which accepts the global `evaluation context` as an argument and performs initialization logic relevant to the provider.
> The `provider` **MAY** define an initialization function which accepts the global `evaluation context` and an optional bound `domain`, which performs initialization logic relevant to the provider.

Many feature flag frameworks or SDKs require some initialization before they can be used.
They might require the completion of an HTTP request, establishing persistent connections, or starting timers or worker threads.
The initialization function is an ideal place for such logic.

The `domain` the provider is registered under is also supplied, allowing the provider to scope domain-specific behavior, such as partitioning a persistent cache, so that multiple providers sharing the same storage do not collide.
A `provider` instance is initialized only once, even when bound to multiple `domains`; in that case the `domain` supplied is the one under which it was first registered.
Comment thread
toddbaert marked this conversation as resolved.
Comment thread
toddbaert marked this conversation as resolved.
A provider that maintains `domain`-specific state can instead declare itself `domain-scoped` (see [Requirement 2.4.3](#requirement-243)), in which case it is restricted to a single `domain` and this ambiguity does not arise.
The default provider, which is not bound to a domain, is initialized without one.

```java
// MyProvider implementation of the initialize function defined in Provider
class MyProvider implements Provider {
//...

// the global context is passed to the initialization function
void initialize(EvaluationContext initialContext) {
// the global context and the bound domain are passed to the initialization function
void initialize(EvaluationContext initialContext, @Nullable String domain) {
this.domain = domain;
/*
A hypothetical initialization function: make an initial call doing some bulk initial evaluation, start a worker to do periodic updates
*/
Expand All @@ -207,6 +213,21 @@ If the error is irrecoverable (perhaps due to bad credentials or invalid configu

see: [error codes](../types.md#error-code)

#### Requirement 2.4.3

> The `provider` **MAY** declare that it is `domain-scoped`, indicating that it maintains state specific to a single `domain`, such as a persistent cache, that cannot be shared across `domains`.

Most providers are stateless with respect to their `domain` and can safely back multiple `domains` from a single instance.
Providers that persist or cache `domain`-specific data need a stable, unambiguous `domain` to key that state on.
By declaring itself `domain-scoped`, such a provider signals that the `API` must bind it to at most one `domain` (see [Requirement 1.1.8](./01-flag-evaluation.md#condition-118)), guaranteeing the `domain` supplied to `initialize` is the only one the instance will ever serve.

#### Requirement 2.4.4

> A `provider` that declares itself `domain-scoped` **MUST** accept the bound `domain` during initialization.

A `domain-scoped` declaration is only meaningful if the provider consumes the `domain` it is given to scope its state.
This is a contract on the provider; implementations may not be able to detect or reject a violation automatically, so it is not guaranteed to surface as a runtime error.

### 2.5. Shutdown

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