Write a backend adapter¶
A backend is a separate module (gitlab.com/phpboyscout/go/features-<name>), never a
dependency of the core: depfootprint_test.go refuses vendor SDKs here. It implements:
type Backend interface {
Resolve(ctx context.Context, id ID, fallback bool, ec EvalContext) (Decision, error)
Init(ctx context.Context) error
Ready() bool
Watch(ctx context.Context) <-chan Change
Close() error
}
Resolve, and why the fallback is an input¶
fallback is the caller's static state. Return it, with ReasonDisabled and no error,
when the flag is disabled in the management system or the system has no rule for it; return
it with an error when you cannot answer. This is the shape the OpenFeature specification
uses (defaultValue), and it is what makes a disabled kill switch a clean answer rather
than an error the evaluator would otherwise fall back over.
Map your vendor's reasons onto ReasonDefault, ReasonTargeting and ReasonDisabled;
ReasonFallback and ReasonError are the evaluator's own and a backend does not return
them. Fill Variant when the vendor has one.
Init, Ready, Close¶
Init opens the connection and blocks until the first answers are trustworthy or ctx is
done; Dynamic bounds it with WithInitTimeout and answers fallback for as long as
Ready is false. Flip Ready off again if the vendor reports the stream stale. Close
releases what Init opened.
Watch¶
Return a channel that receives a Change{ID} when a flag's value may have moved, or
Change{} when everything may have; return nil if the vendor does not push. Buffer it
with one slot and drop a notice when it is full: one pending notice is enough, since the
next Evaluate reads the vendor anyway.
Caching¶
The evaluator has no cache. If evaluation is a network round trip per call, cache in the adapter, where the staleness is yours to reason about.
Proving it¶
Drive Dynamic over your adapter with a scripted vendor: a targeting match, a disabled
flag, an unknown flag and a vendor error should come back as targeting, disabled (no
error, fallback value), fallback and fallback respectively, and a static-only feature
must never reach Resolve.