Evaluate dynamic flags through a backend¶
Opt a feature in¶
Only a descriptor that reports IsDynamic() may be overridden at evaluation time. Everything
else is answered from the Set, whatever a backend says. Keep the features that shape a
command tree, a help screen or a generated artefact static; a request-time decision inside
a running service is what a dynamic feature is for.
Build the evaluator¶
flags := features.Dynamic(set, backend,
features.WithInitTimeout(2*time.Second),
features.WithOnFallback(func(id features.ID, err error) {
logger.Warn("flag fell back to the static state", "feature", id, "error", err)
}),
)
Dynamic fixes the set of dynamic IDs at construction, so evaluating a static-only feature
is one map lookup before the backend is considered.
Lifecycle¶
if err := flags.Init(ctx); err != nil {
// The evaluator is usable regardless: it answers with fallbacks until Ready.
}
defer flags.Close()
for change := range flags.Watch(ctx) {
// A backend that pushes updates says a flag (or everything) may have moved;
// the next Evaluate sees it. Nil channel when the backend does not push.
}
A short-lived process gives Init a short WithInitTimeout and lives with fallbacks; a
long-lived one gives it none and ties Init and Close to its own lifecycle.
Evaluate¶
dec, err := flags.Evaluate(ctx, "beta-ranking", features.EvalContext{
TargetingKey: userID,
Attributes: map[string]any{"tier": tier},
})
Decision.Reason says how the answer was reached:
| Reason | Meaning | err |
|---|---|---|
static |
a static-only feature, or a Set answered |
nil |
default |
the backend's default rule | nil |
targeting |
the backend matched the EvalContext |
nil |
disabled |
the backend has the flag disabled; the static state applies | nil |
fallback |
the backend errored, was not ready, or did not know the flag; the static state applies | the cause |
error |
no such feature | ErrUnknownFeature |
The static state is the fallback for every failure. It is the tool's own decision (its
manifest, its config), made once, which is the only place an offline default can honestly
live. Variant is empty for a plain toggle; a multivariate backend fills it.
With no backend yet¶
features.SetBackend(set) is a Backend that answers statically, and a Set is itself
an Evaluator. Wire a service against Evaluator today and swap a Dynamic in later
without touching a call site.