2. Provider
Overview
The provider API defines interfaces that Provider Authors can use to abstract a particular flag management system, thus enabling the use of the evaluation API by Application Authors.
Providers are the "translator" between the flag evaluation calls made in application code, and the flag management system that stores flags and in some cases evaluates flags. At a minimum, providers should implement some basic evaluation methods which return flag values of the expected type. In addition, providers may transform the evaluation context appropriately in order to be used in dynamic evaluation of their associated flag management system, provide insight into why evaluation proceeded the way it did, and expose configuration options for their associated flag management system. Hypothetical provider implementations might wrap a vendor SDK, embed an REST client, or read flags from a local file.
2.1. Feature Provider Interface
Requirement 2.1.1
The provider interface MUST define a
metadatamember or accessor, containing anamefield or accessor of type string, which identifies the provider implementation.
provider.getMetadata().getName(); // "my-custom-provider"
2.2 Flag Value Resolution
Providers are implementations of the feature provider interface, which may wrap vendor SDKs, REST API clients, or otherwise resolve flag values from the runtime environment.
Requirement 2.2.1
The
feature providerinterface MUST define methods to resolve flag values, with parametersflag key(string, required),default value(boolean | number | string | structure, required) andevaluation context(optional), which returns aresolution detailsstructure.
// example flag resolution function
resolveBooleanValue(flagKey, defaultValue, context);
see: flag resolution structure, flag value resolution
Condition 2.2.2
The implementing language type system differentiates between strings, numbers, booleans and structures.
Conditional Requirement 2.2.2.1
The
feature providerinterface MUST define methods for typed flag resolution, including boolean, numeric, string, and structure.
// example boolean flag value resolution
ResolutionDetails resolveBooleanValue(string flagKey, boolean defaultValue, context: EvaluationContext);
// example string flag value resolution
ResolutionDetails resolveStringValue(string flagKey, string defaultValue, context: EvaluationContext);
// example number flag value resolution
ResolutionDetails resolveNumberValue(string flagKey, number defaultValue, context: EvaluationContext);
// example structure flag value resolution
ResolutionDetails resolveStructureValue(string flagKey, JsonObject defaultValue, context: EvaluationContext);
Requirement 2.2.3
In cases of normal execution, the
providerMUST populate theresolution detailsstructure'svaluefield with the resolved flag value.
Requirement 2.2.4
In cases of normal execution, the
providerSHOULD populate theresolution detailsstructure'svariantfield with a string identifier corresponding to the returned flag value.
For example, the flag value might be 3.14159265359, and the variant field's value might be "pi".
The value of the variant field might only be meaningful in the context of the flag management system associated with the provider. For example, the variant may be a UUID corresponding to the variant in the flag management system, or an index corresponding to the variant in the flag management system.
Requirement 2.2.5
The
providerSHOULD populate theresolution detailsstructure'sreasonfield with"STATIC","DEFAULT","TARGETING_MATCH","SPLIT","CACHED","DISABLED","UNKNOWN","STALE","ERROR"or some other string indicating the semantic reason for the returned flag value.
As indicated in the definition of the resolution details structure, the reason should be a string. This allows providers to reflect accurately why a flag was resolved to a particular value.
Requirement 2.2.6
In cases of normal execution, the
providerMUST NOT populate theresolution detailsstructure'serror codefield, or otherwise must populate it with a null or falsy value.
Requirement 2.2.7
In cases of abnormal execution, the
providerMUST indicate an error using the idioms of the implementation language, with an associatederror codeand optional associatederror message.
The provider might throw an exception, return an error, or populate the error code object on the returned resolution details structure to indicate a problem during flag value resolution.
This includes situations where the provider is not yet initialized or has encountered an irrecoverable error; in such cases, the provider indicates the error (e.g. with error codes PROVIDER_NOT_READY or PROVIDER_FATAL), and the client returns the default value per Requirement 1.4.10.
See error code for details.
// example throwing an exception with an error code and optional error message.
throw new ProviderError(ErrorCode.INVALID_CONTEXT, "The 'foo' attribute must be a string.");
Condition 2.2.8
The implementation language supports generics (or an equivalent feature).
Conditional Requirement 2.2.8.1
The
resolution detailsstructure SHOULD accept a generic argument (or use an equivalent language feature) which indicates the type of the wrappedvaluefield.
// example boolean flag value resolution with generic argument
ResolutionDetails<boolean> resolveBooleanValue(string flagKey, boolean defaultValue, context: EvaluationContext);
// example string flag value resolution with generic argument
ResolutionDetails<string> resolveStringValue(string flagKey, string defaultValue, context: EvaluationContext);
// example number flag value resolution with generic argument
ResolutionDetails<number> resolveNumberValue(string flagKey, number defaultValue, context: EvaluationContext);
// example structure flag value resolution with generic argument
ResolutionDetails<MyStruct> resolveStructureValue(string flagKey, MyStruct defaultValue, context: EvaluationContext);
Requirement 2.2.9
The
providerSHOULD populate theresolution detailsstructure'sflag metadatafield.
Requirement 2.2.10
flag metadataMUST be a structure supporting the definition of arbitrary properties, with keys of typestring, and values of typeboolean | string | number.
2.3. Provider hooks
A provider hook exposes a mechanism for provider authors to register hooks to tap into various stages of the flag evaluation lifecycle. These hooks can be used to perform side effects and mutate the context for purposes of the provider. Provider hooks are not configured or controlled by the application author.
Requirement 2.3.1
The provider interface MUST define a
provider hookmechanism which can be optionally implemented in order to addhookinstances to the evaluation life-cycle.
class MyProvider implements Provider {
//...
readonly hooks: Hook[] = [new MyProviderHook()];
// ..or alternatively..
getProviderHooks(): Hook[] {
return [new MyProviderHook()];
}
//...
}
Requirement 2.3.2
In cases of normal execution, the
providerMUST NOT populate theresolution detailsstructure'serror messagefield, or otherwise must populate it with a null or falsy value.
Requirement 2.3.3
In cases of abnormal execution, the
resolution detailsstructure'serror messagefield MAY contain a string containing additional detail about the nature of the error.
2.4 Initialization
Requirement 2.4.1
The
providerMAY define an initialization function which accepts the globalevaluation contextand an optional bounddomain, 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.
A provider that maintains domain-specific state can instead declare itself domain-scoped (see Requirement 2.4.3), 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.
// MyProvider implementation of the initialize function defined in Provider
class MyProvider implements Provider {
//...
// 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
*/
this.flagCache = this.restClient.bulkEvaluate(initialContext);
this.startPolling();
}
//...
}
Condition 2.4.2
The provider defines an
initializefunction.
Conditional Requirement 2.4.2.1
If the provider's
initializefunction fails to render the provider ready to evaluate flags, it SHOULD abnormally terminate.
If a provider is unable to start up correctly, it should indicate abnormal execution by throwing an exception, returning an error, or otherwise indicating so by means idiomatic to the implementation language.
If the error is irrecoverable (perhaps due to bad credentials or invalid configuration) the PROVIDER_FATAL error code should be used.
see: error codes, provider status
Requirement 2.4.3
The
providerMAY declare that it isdomain-scoped, indicating that it maintains state specific to a singledomain, such as a persistent cache, that cannot be shared acrossdomains.
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), guaranteeing the domain supplied to initialize is the only one the instance will ever serve.
Requirement 2.4.4
A
providerthat declares itselfdomain-scopedMUST accept the bounddomainduring 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
Requirement 2.5.1
The provider MAY define a mechanism to gracefully shutdown and dispose of resources.
// MyProvider implementation of the dispose function defined in Provider
class MyProvider implements Provider, AutoDisposable {
//...
void dispose() {
// close connections, terminate threads or timers, etc...
}
Requirement 2.5.2
After a provider's
shutdownfunction has terminated, the provider SHOULD revert to its uninitialized state.
If a provider requires initialization, once it's shut down, it must transition to its uninitialized state. Some providers may allow reinitialization from this state. Providers not requiring initialization are assumed to be ready at all times. Providers in the process of initializing abort initialization if shutdown is called while they are still starting up.
see: initialization
Requirement 2.5.3
A Provider's
shutdownfunction SHOULD be idempotent.
If a provider's shutdown function has been called, subsequent calls (without an intervening call to initialize) should have no effect.
see: initialization
2.6. Provider context reconciliation
Static-context focused providers may need a mechanism to understand when their cache of evaluated flags must be invalidated or updated. An on context changed function can be defined which performs whatever operations are needed to reconcile the evaluated flags with the new context.
Requirement 2.6.1
The provider MAY define an
on context changedfunction, which takes an argument for the previous context and the newly set context, in order to respond to an evaluation context change.
Especially in static-context implementations, providers and underlying SDKs may maintain state for a particular context.
The on context changed function provides a mechanism to update this state, often by re-evaluating flags in bulk with respect to the new context.
// MyProvider implementation of the onContextChanged function defined in Provider
class MyProvider implements Provider {
//...
onContextChanged(EvaluationContext oldContext, EvaluationContext newContext): void {
// update context-sensitive cached flags, or otherwise react to the change in the global context
}
//...
}
see: provider status
Providers may maintain remote connections, timers, threads or other constructs that need to be appropriately disposed of.
Provider authors may implement a shutdown function to perform relevant clean-up actions.
Alternatively, implementations might leverage language idioms such as auto-disposable interfaces or some means of cancellation signal propagation to allow for graceful shutdown.
2.7. Tracking Support
Some flag management systems support tracking functionality, which can be used to associate feature flag evaluations with subsequent user actions or application state.
See tracking.
Condition 2.7.1
The
providerMAY define a function for tracking the occurrence of a particular user action or application state, with parameterstracking event name(string, required),evaluation context(optional) andtracking event details(optional) which returns nothing.
class MyProvider implements Tracking {
//...
/**
* Record a tracking event.
*/
public void track(String trackingEventName, EvaluationContext context, TrackingEventDetails details) {
// perform side effects to record the event
}
//...
}
The track function is a void function (function returning nothing).
The track function performs side effects required to record the tracking event in question, which may include network activity or other I/O; this I/O should not block the function call.
Providers should be careful to complete any communication or flush any relevant uncommitted tracking data before they shut down.
See shutdown.
2.8. Provider status
The SDK derives provider status from events emitted by the provider. Providers signal all state transitions by emitting the appropriate event; the SDK updates its internal status accordingly and runs associated handlers.
Shutdown is the exception: the SDK initiates the shutdown call and infers the NOT_READY transition itself, so no event from the provider is required (see Requirement 1.7.6).
Providers that do not define an initialize function are not required to emit events for initialization; see Condition 2.8.5.
Requirement 2.8.1 applies to all provider status transitions; Requirements 2.8.2-2.8.4 apply only when the provider defines the corresponding lifecycle method.
Where practical, SDKs should couple lifecycle methods with event support so providers defining lifecycle methods can emit the required events.
see: provider lifecycle management, provider events
Requirement 2.8.1
The provider MUST emit an event to signal each status transition, including transitions resulting from lifecycle methods (
initialize,on context changed) and spontaneous transitions.
Providers must not rely on the SDK to infer status from lifecycle method return values.
Instead, the provider emits the appropriate event (e.g. PROVIDER_READY after successful initialization) to signal each transition.
see: provider events, provider event types
Requirement 2.8.2
The provider MUST emit
PROVIDER_READYbefore itsinitializefunction terminates normally.
The provider is the sole source of this event; the SDK does not synthesize it based on the return of initialize.
see: Requirement 1.1.2.4
Requirement 2.8.3
The provider MUST emit
PROVIDER_ERRORbefore itsinitializefunction terminates abnormally.
The provider is the sole source of this event; the SDK does not synthesize it based on the return of initialize.
If the error is irrecoverable, the error code must indicate PROVIDER_FATAL.
see: error codes, Requirement 1.1.2.4
Requirement 2.8.4
The provider MUST emit
PROVIDER_CONTEXT_CHANGEDif itson context changedfunction terminates normally, andPROVIDER_ERRORif it terminates abnormally.
As with initialization, the provider is the sole source of these events; the SDK does not synthesize PROVIDER_CONTEXT_CHANGED or PROVIDER_ERROR based on the return of on context changed.
The on context changed return (or thrown error) is treated by the SDK as a synchronization signal only; the status transition and handler invocation occur only when the SDK receives the provider-emitted event.
see: provider context reconciliation
Condition 2.8.5
The provider does not define an
initializefunction.
Conditional Requirement 2.8.5.1
The SDK MUST treat such providers as
READYfrom registration and MUST runPROVIDER_READYhandlers on their behalf.
Such providers have no initialization to wait for and no associated state transition to signal.
Nothing in this specification prevents such a provider from emitting PROVIDER_ERROR (or other events) spontaneously to signal a problem encountered outside of initialization; SDKs handle such events as outlined elsewhere.