4. Hooks
Overviewโ
Hooks are a mechanism whereby application developers can add arbitrary behavior to flag evaluation. They operate similarly to middleware in many web frameworks.
Hooks add their logic at any of four specific stages of flag evaluation:
before, immediately before flag evaluationafter, immediately after successful flag evaluationerror, immediately after an unsuccessful flag evaluationfinally, unconditionally after flag evaluation
Hooks can be configured to run globally (impacting all flag evaluations), per client, or per flag evaluation invocation. Some example use cases for a hook include adding additional data to the evaluation context, performing validation on the received flag value, providing data to telemetric tools, and logging errors.
Definitionsโ
Hook: Application author/integrator-supplied logic that is called by the OpenFeature framework at a specific stage.
Stage: An explicit portion of the flag evaluation lifecycle. e.g. before being "before the resolution is run.
Invocation: A single call to evaluate a flag. client.getBooleanValue(..) is an invocation.
API: The global API singleton.
4.1. Hook contextโ
Hook context exists to provide hooks with information about the invocation and propagate data between hook stages.
Requirement 4.1.1โ
Hook context MUST provide: the
flag key,flag value type,evaluation context,default value, andhook data.
The evaluation context provided in the hook context refers to the merged evaluation context as specified in Requirement 3.2.3.
Requirement 4.1.2โ
The
hook contextSHOULD provide access to theclient metadataand theprovider metadatafields.
Requirement 4.1.3โ
The
flag key,flag type, anddefault valueproperties MUST be immutable. If the language does not support immutability, the hook MUST NOT modify these properties.
Condition 4.1.4โ
The implementation uses the dynamic-context paradigm.
Conditional Requirement 4.1.4.1โ
The evaluation context MUST be mutable only within the
beforehook.
Requirement 4.1.5โ
The
hook dataMUST be mutable.
Either the hook data reference itself must be mutable, or it must allow mutation of its contents.
Mutable reference:
hookContext.hookData = {'my-key': 'my-value'}
Mutable content:
hookContext.hookData.set('my-key', 'my-value')
4.2. Hook Hintsโ
Requirement 4.2.1โ
hook hintsMUST be a structure supports definition of arbitrary properties, with keys of typestring, and values of typeboolean | string | number | datetime | structure.
Condition 4.2.2โ
The implementation language supports a mechanism for marking data as immutable.
Conditional Requirement 4.2.2.1โ
Condition:
Hook hintsMUST be immutable.
Conditional Requirement 4.2.2.2โ
Condition: The client
metadatafield in thehook contextMUST be immutable.
Conditional Requirement 4.2.2.3โ
Condition: The provider
metadatafield in thehook contextMUST be immutable.
4.3. Hook creation and parametersโ
Requirement 4.3.1โ
Hooks MUST specify at least one stage.
Requirement 4.3.2โ
Hook dataMUST must be created before the firststageinvoked in a hook for a specific evaluation and propagated between eachstageof the hook. The hook data is not shared between different hooks.
Example showing data between before and after stage for two different hooks.
Condition 4.3.2โ
The implementation uses the dynamic-context paradigm.
Conditional Requirement 4.3.2.1โ
The
beforestage MUST run before flag resolution occurs. It accepts ahook context(required) andhook hints(optional) as parameters and returns either anevaluation contextor nothing.
EvaluationContext | void before(HookContext hookContext, HookHints hints);
Condition 4.3.3โ
The implementation uses the static-context paradigm.
Conditional Requirement 4.3.3.1โ
The
beforestage MUST run before flag resolution occurs. It accepts ahook context(required) andhook hints(optional) as parameters. It has no return value.
void before(HookContext hookContext, HookHints hints);
Requirement 4.3.4โ
Any
evaluation contextreturned from abeforehook MUST be passed to subsequentbeforehooks (viaHookContext).
Requirement 4.3.5โ
When
beforehooks have finished executing, any resultingevaluation contextMUST be merged with the existingevaluation context.
Evaluation context merge order is defined in Context levels and merging.
Requirement 4.3.6โ
The
afterstage MUST run after flag resolution occurs. It accepts ahook context(required),evaluation details(required) andhook hints(optional). It has no return value.
Requirement 4.3.7โ
The
errorhook MUST run when errors are encountered in thebeforestage, theafterstage or during flag resolution. It acceptshook context(required),exceptionrepresenting what went wrong (required), andhook hints(optional). It has no return value.
Requirement 4.3.8โ
The
finallyhook MUST run after thebefore,after, anderrorstages. It accepts ahook context(required),evaluation details(required) andhook hints(optional). It has no return value.
The evaluation details passed to the finally stage matches the evaluation details returned to the application author.
Condition 4.3.9โ
finallyis a reserved word in the language.
Conditional Requirement 4.3.9.1โ
Instead of
finally,finallyAfterSHOULD be used.
4.4. Hook registration & orderingโ
Requirement 4.4.1โ
The API, Client, Provider, and invocation MUST have a method for registering hooks.
OpenFeature.addHooks(new Hook1());
//...
Client client = OpenFeature.getClient();
client.addHooks(new Hook2());
`
//...
client.getValue('my-flag', 'defaultValue', new Hook3());
Requirement 4.4.2โ
Hooks MUST be executed "stack-wise" with respect to flag resolution, prioritizing increasing specificity (API, Client, Invocation, Provider) first, and the order in which they were added second.
Before flag resolution (the before stage), hooks run in the order API -> Client -> Invocation -> Provider, and within those, in the order in which they were added. After flag evaluation (the after, error, or finally stages), hooks run in the order Provider -> Invocation -> Client -> API, and within those, in reverse of the order in which they were added. This achieves intuitive "stack-like" or "LIFO" behavior for side effects and transformations.
Given hooks A - H, each implementing the both the before and after stages, added at the following levels and order:
- API: [A, B]
- Client: [C, D]
- Invocation: [E, F]
- Provider: [G, H]
The expected order of execution is:
Requirement 4.4.3โ
If a
finallyhook abnormally terminates, evaluation MUST proceed, including the execution of any remainingfinallyhooks.
In languages with try/catch semantics, this means that exceptions thrown in finally hooks should be caught and not propagated up the call stack.
Requirement 4.4.4โ
If an
errorhook abnormally terminates, evaluation MUST proceed, including the execution of any remainingerrorhooks.
In languages with try/catch semantics, this means that exceptions thrown in error hooks should be caught, and not propagated up the call stack.
Requirement 4.4.5โ
If an error occurs in the
beforeorafterhooks, theerrorhooks MUST be invoked.
Requirement 4.4.6โ
If an error occurs during the evaluation of
beforeorafterhooks, any remaining hooks in thebeforeorafterstages MUST NOT be invoked.
Requirement 4.4.7โ
If an error occurs in the
beforehooks, the default value MUST be returned.
Before hooks can impact evaluation by various means, such as mutating the evaluation context. Therefore, an error in the before hooks is considered abnormal execution, and the default should be returned.
Flag evaluation optionsโ
Usage might look something like:
val = client.get_boolean_value('my-key', False, evaluation_options={
'hooks': new MyHook(),
'hook_hints': {'side-item': 'onion rings'}
})
Requirement 4.5.1โ
Flag evaluation optionsMAY containhook hints, a map of data to be provided to hook invocations.
Requirement 4.5.2โ
hook hintsMUST be passed to each hook.
Requirement 4.5.3โ
The hook MUST NOT alter the
hook hintsstructure.
4.6. Hook dataโ
Hook data exists to allow hook stages to share data for a specific evaluation. For instance a span for OpenTelemetry could be created in a before stage and closed in an after stage.
Hook data is scoped to a specific hook instance. The different stages of a hook share the same data, but different hooks have different hook data instances.
public Optional<EvaluationContext> before(HookContext context, HookHints hints) {
SpanBuilder builder = tracer.spanBuilder('sample')
.setParent(Context.current().with(Span.current()));
Span span = builder.startSpan()
context.hookData.set("span", span);
}
public void after(HookContext context, FlagEvaluationDetails details, HookHints hints) {
// Only accessible by this hook for this specific evaluation.
Object value = context.hookData.get("span");
if (value instanceof Span) {
Span span = (Span) value;
span.end();
}
}
Requirement 4.6.1โ
hook dataMUST be a structure supporting the definition of arbitrary properties, with keys of typestring, and values of any type.
Access to hook data is restricted to only a single hook instance, and it has no serialization requirements, and as a result does not require any value type restrictions.
Example TypeScript definition:
type HookData = Record<string, unknown>;