Documentation
¶
Overview ¶
Package featureflags provides a feature flag evaluation interface for controlling feature availability per user, with implementations for LaunchDarkly and PostHog.
Three answers, not two ¶
An evaluation can end three ways, and the difference between the last two is the reason this package has a sentinel of its own:
(value, nil) the provider decided (default, ErrFlagNotFound) the provider answered, and no such flag exists (default, err) the provider could not answer
The middle case used to be indistinguishable from the last, which made it a bug rather than a distinction. Every provider here guards its evaluations with a circuit breaker, and that breaker is one per FeatureFlagManager, shared by every flag the process evaluates. Reporting a missing flag as a provider failure therefore made evaluating a flag nobody had created a vote to disable every other flag in the process.
That is not a hypothetical shape. A rollout normally ships the code that reads the flag before the flag itself is created, so for the length of that window the flag name is live in code and absent from the provider. Under real request volume that is enough to open the breaker, at which point flags that do exist and are load-bearing start returning circuitbreaking.ErrCircuitBroken.
So a FLAG_NOT_FOUND resolution returns ErrFlagNotFound and scores the breaker a success. The breaker exists to give a failing service breathing room, and answering "no such flag" is not what a failing service does — it is a correct negative answer, which is health. Everything else — an unready provider, an unreachable one, a flag whose value will not parse — is still a failure the breaker hears about.
Drawing the line at all takes a backend that can tell a missing flag from a present one, and that is not universal. PostHog's API answers false for a boolean flag it has never heard of, indistinguishably from one that exists and is off, so its CanUseFeature returns (false, nil) and never reports the flag missing. The outcome is the same inert answer by a shorter route. What this package guarantees is not that an unresolvable flag surfaces as an error — it is that when one does, it says which kind it is.
Choosing what a missing flag means ¶
The value returned alongside ErrFlagNotFound is the caller's default, so a caller with nothing better to do can ignore which error it got and take the value. A caller that does care matches the sentinel:
enabled, err := flags.CanUseFeature(ctx, "new-checkout", evalCtx)
switch {
case errors.Is(err, featureflags.ErrFlagNotFound):
// Not created yet. Not the same as off, and not an outage.
case err != nil:
// The provider could not answer.
default:
// enabled is a decision.
}
CanUseFeature deliberately takes no default parameter, unlike its four siblings. Their defaults are real values in a range; a boolean default has two settings and one of them is degenerate, because "an unresolvable flag should not stop me" is the same as not consulting the flag on that path at all.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrFlagNotFound = platformerrors.New("feature flag not found")
ErrFlagNotFound reports that the provider resolved the evaluation and the flag does not exist.
It is deliberately distinct from an unreachable or erroring provider. A flag nobody has created says nothing about whether the service that would know about it is healthy, and conflating the two lets the normal shape of a rollout — ship the code that reads the flag, then create the flag — score failures against a circuit breaker that every other flag in the process shares. Implementations therefore report this rather than a generic error, and do not count it against the breaker.
Implementations wrap it with the flag key, so match it with errors.Is. The value returned alongside it is the caller's default (or false for CanUseFeature) — the same value an errored evaluation returns, because a caller with nothing better to do with either answer should not have to tell them apart.
Functions ¶
This section is empty.
Types ¶
type EvaluationContext ¶
EvaluationContext carries targeting information for a single flag evaluation. TargetingKey is the primary subject identifier — typically a user ID, but it can be any stable string a provider's targeting rules can match against. Attributes carry arbitrary additional signals (tenant, plan tier, country, beta cohort, region, etc.) that provider rules can target on.
This type is intentionally repo-owned rather than aliasing the OpenFeature SDK's EvaluationContext: it keeps the openfeature import out of caller code, lets the noop and mock implementations satisfy the signature without importing openfeature, and leaves room to swap providers later. Each provider converts to its own representation internally.
type FeatureFlagManager ¶
type FeatureFlagManager interface {
// CanUseFeature evaluates a boolean flag. Returns false on error, and false
// with ErrFlagNotFound when the provider reports no such flag.
CanUseFeature(ctx context.Context, feature string, evalCtx EvaluationContext) (bool, error)
// GetStringValue evaluates a string-typed flag, returning defaultValue on error
// and defaultValue with ErrFlagNotFound when no such flag exists.
GetStringValue(ctx context.Context, feature, defaultValue string, evalCtx EvaluationContext) (string, error)
// GetInt64Value evaluates an int64-typed flag, returning defaultValue on error
// and defaultValue with ErrFlagNotFound when no such flag exists.
GetInt64Value(ctx context.Context, feature string, defaultValue int64, evalCtx EvaluationContext) (int64, error)
// GetFloat64Value evaluates a float64-typed flag, returning defaultValue on error
// and defaultValue with ErrFlagNotFound when no such flag exists.
GetFloat64Value(ctx context.Context, feature string, defaultValue float64, evalCtx EvaluationContext) (float64, error)
// GetObjectValue evaluates an object-typed (JSON) flag, returning defaultValue
// on error and defaultValue with ErrFlagNotFound when no such flag exists. The
// concrete type of the returned value is provider-specific — callers typically
// type-assert or json.Marshal it back into a known struct.
GetObjectValue(ctx context.Context, feature string, defaultValue any, evalCtx EvaluationContext) (any, error)
// Close releases any backend resources held by the FeatureFlagManager.
Close() error
}
FeatureFlagManager evaluates feature flags. Implementations must be safe for concurrent use.
Every method answers one of three ways: a value the provider decided on, a default alongside ErrFlagNotFound because no such flag exists, or a default alongside some other error because the provider could not answer. A caller that needs to tell "the flag is off" from "the flag was never created" matches ErrFlagNotFound; one that treats every unusable answer alike can take the returned value and ignore which it was.
The middle answer requires the backend to be able to tell a missing flag from a present one, which is not universal — see the posthog implementation's CanUseFeature. Where it cannot, a missing flag is reported as its default with no error, which is the same inert answer by a shorter route. Nothing here promises an unresolvable flag will surface as an error; what it promises is that when one does, it says which kind.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package featureflagscfg selects and builds a featureflags.FeatureFlagManager from configuration: LaunchDarkly, PostHog, or the noop manager.
|
Package featureflagscfg selects and builds a featureflags.FeatureFlagManager from configuration: LaunchDarkly, PostHog, or the noop manager. |
|
internal
|
|
|
openfeatureflags
Package openfeatureflags is the flag evaluation both of this module's OpenFeature-backed providers do.
|
Package openfeatureflags is the flag evaluation both of this module's OpenFeature-backed providers do. |
|
Package launchdarkly evaluates feature flags against LaunchDarkly, by way of OpenFeature.
|
Package launchdarkly evaluates feature flags against LaunchDarkly, by way of OpenFeature. |
|
Package featureflagsmock provides mock implementations of the featureflags package's interfaces.
|
Package featureflagsmock provides mock implementations of the featureflags package's interfaces. |
|
Package noop is the featureflags.FeatureFlagManager for a process with no flag system: each typed getter returns the default value it was handed, and CanUseFeature returns false.
|
Package noop is the featureflags.FeatureFlagManager for a process with no flag system: each typed getter returns the default value it was handed, and CanUseFeature returns false. |
|
Package posthog evaluates feature flags against PostHog, by way of OpenFeature.
|
Package posthog evaluates feature flags against PostHog, by way of OpenFeature. |