Skip to main content

· 2 min read
Todd Baert

Early this year, OpenFeature announced its intent to bring a standard to the rapidly growing development practice of feature flagging. In June it was accepted as a Cloud Native Computing Foundation Sandbox project. Now, we're pleased to announce a new milestone: OpenFeature has released 1.0 versions of its .NET, Go, Java, and JavaScript SDKs!

The release includes stable versions the following features:

  • the Evaluation API, providing application authors with consistent, vendor neutral, feature flag evaluation
  • provider interfaces for flexible integration with a variety of feature flag systems

The specification documents associated with these features have been marked as hardening, meaning breaking changes are no longer allowed and usage of these features are encouraged in production environments. The release of these SDKs and the stabilization of the specification represent a culmination of efforts by a dedicated group of vendors, practitioners and subject matter experts. Providers are already available for major vendors and popular community projects. It's our hope that the efforts to stabilize the OpenFeature specification and SDKs will lead to more adoption of both OpenFeature and feature flagging in general, and promote a vibrant ecosystem around this increasingly important development pattern.

In addition to those mentioned above, experimental features available in the 1.0 SDKs include:

  • hooks, for adding arbitrary behavior to feature flag evaluation, ideal for telemetry integration, validation, and logging
  • the Evaluation Context interfaces, used as the basis for dynamic flag evaluation
  • implicit context propagation (JavaScript SDK only)

What's next?

Our goal in the upcoming months will be to harden our existing experimental features. Additionally, we'll work to develop and standardize new capabilities, including: client-side feature flagging, improved cloud native tooling, and implicit transaction-scoped data propagation of contextual attributes. Furthermore, we're working on SDKs for additional languages, including PHP, Python, and Ruby.

If you're interested in contributing or learning more about OpenFeature, please join our expanding and friendly community. Visit our GitHub, join the OpenFeature slack channel on the CNCF Slack instance, or hop into our bi-weekly community meeting.

· 2 min read
Skye Gill

Logging

Logging is the act of keeping a log. A log (in this case) records events that occur in software.

Subject to many opinions and differing principles of best practice, the best thing we could do for the go-sdk was to create an implementation as open & configurable as possible. To achieve this, we've integrated logr, this allows the use of any logger that conforms to its API.

Applications may already have a chosen logging solution at the point of introducing openfeature. An integration with logr may already exist for their chosen solution (integrations exist for many of the popular logger packages in go). If not, they could write their own integration.

Objective

Configure the popular go logger zap with the go-sdk.

Prerequisites

  • Golang 1.17+

Scaffolding

  1. Go get the following dependencies
go get github.com/open-feature/go-sdk
go get go.uber.org/zap
go get github.com/go-logr/logr
go get github.com/go-logr/zapr # an integration of zap with logr's API
  1. Import all of the above into your main.go and create func main()
package main

import (
"github.com/go-logr/logr"
"github.com/go-logr/zapr"
"github.com/open-feature/go-sdk/pkg/openfeature"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"context"
"log"
)

func main() {

}

Integrating the logger

  1. Create the zap logger with preset development config (for the sake of this tutorial)
func main() {
zc := zap.NewDevelopmentConfig()
zc.Level = zap.NewAtomicLevelAt(zapcore.Level(-1)) // the level here decides the verbosity of our logs
z, err := zc.Build()
if err != nil {
log.Fatal(err)
}
}
  1. Create the zapr logger (zap logger that conforms to logr's interface)
l := zapr.NewLogger(z)
  1. Set the logger to the global openfeature singleton
openfeature.SetLogger(l)
  1. Create an openfeature client and invoke a flag evaluation
c := openfeature.NewClient("log")

evalCtx := openfeature.NewEvaluationContext("foo", nil)

c.BooleanValue(context.Background(), "bar", false, evalCtx)
  1. Check the result of go run main.go
2022-09-02T14:22:31.109+0100    INFO    openfeature/openfeature.go:76   set global logger
2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:230 evaluating flag {"flag": "bar", "type": "bool", "defaultValue": false, "evaluationContext": {"targetingKey":"foo","attributes":null}, "evaluationOptions": {}}
2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:336 executing before hooks
2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:349 executed before hooks
2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:355 executing after hooks
2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:364 executed after hooks
2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:318 evaluated flag {"flag": "bar", "details": {"FlagKey":"bar","FlagType":0,"Value":false,"ErrorCode":"","Reason":"","Variant":""}, "type": "bool"}
2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:377 executing finally hooks
2022-09-02T14:22:31.110+0100 DEBUG openfeature/client.go:383 executed finally hooks
  1. (optional) Tweak the level set in step 1 to decrease the verbosity

· 8 min read
James Milligan

Providers

A Provider is responsible for performing flag evaluation, they can be as simple as an interface for a key value store, or act as an abstraction layer for a more complex evaluation system. Only one Provider can be registered at a time, and OpenFeature will no-op if one has not been defined. Before writing your own Provider, it is strongly recommended to familiarize yourself with the OpenFeature spec.
In this tutorial I will demonstrate the steps taken to create a new Provider whilst conforming to the OpenFeature spec using a simple flag implementation. The flag evaluation will be handled by a simple JSON evaluator and flag configurations will be stored as environment variables.

The following section describes how the flag evaluator portion of this project has been constructed, to skip to the Provider specific implementations, click here.

Creating the flag evaluator

The core of any flag Provider is the evaluation logic used to provide the flag values from the provided metadata (referred to as the Evaluation Context). For this example I have put together a very simple json evaluator. Flags are configured using the structs described below, and are stored as environment variables:

type StoredFlag struct {
DefaultVariant string `json:"defaultVariant"`
Variants []Variant `json:"variant"`
}

type Variant struct {
Criteria []Criteria `json:"criteria"`
TargetingKey string `json:"targetingKey"`
Value interface{} `json:"value"`
Name string `json:"name"`
}

type Criteria struct {
Key string `json:"key"`
Value interface{} `json:"value"`
}

example JSON:

{
"defaultVariant":"not-yellow",
"variants": [
{
"name": "yellow-with-key",
"targetingKey":"user",
"criteria": [
{
"color":"yellow"
}
],
"value":true
},
{
"name": "yellow",
"targetingKey":"",
"criteria": [
{
"color":"yellow"
}
],
"value":true
},
{
"name": "not-yellow",
"targetingKey":"",
"criteria": [],
"value":false
}
]
}

Each flag value contains an array of Variants, each with their own array of Criteria. When a flag request needs to be evaluated, the Variants slice is iterated over, if the FlattenedContext matches all required Criteria for a specific Variant, the associated flag value will be returned from the evaluator. If a matching Variant is not found the DefaultVariant is returned in the response. The response also includes the the variant name, the reason for the resulting value (such as ERROR, STATIC or TARGETING_MATCH) and any associated error (such as PARSE_ERROR). These values form the type naive ResolutionDetails structure, which is then wrapped in a type specific parent for each response type, such as BoolResolutionDetail. This will be discussed in the Creating a Compliant Provider section.

import (
"encoding/json"
"errors"
"github.com/open-feature/go-sdk/pkg/openfeature"
"os"
)

func (f *StoredFlag) Evaluate(evalCtx map[string]interface{}) (string, openfeature.Reason, interface{}, error) {
var defaultVariant *Variant
for _, variant := range f.Variants {
if variant.Name == f.DefaultVariant {
defaultVariant = &variant
}
if variant.TargetingKey != "" && variant.TargetingKey != evalCtx["targetingKey"] {
continue
}
match := true
for _, criteria := range variant.Criteria {
val, ok := evalCtx[criteria.Key]
if !ok || val != criteria.Value {
match = false
break
}
}
if match {
return variant.Name, openfeature.TargetingMatchReason, variant.Value, nil
}
}
if defaultVariant == nil {
return "", openfeature.ErrorReason, nil, openfeature.NewParseErrorResolutionError("")
}
return defaultVariant.Name, openfeature.DefaultReason, defaultVariant.Value, nil
}

The above function demonstrates how this basic evaluator will work in this example, of course in other providers these can be far more complex, and the logic does not need to sit within the application.
This JSON evaluator can then be paired with a simple function for reading and parsing the StoredFlag values from environment variables (as seen in the example below), leaving only the integration with the go-sdk to go. (and some testing!)

func FetchStoredFlag(key string) (StoredFlag, error) {
v := StoredFlag{}
if val := os.Getenv(key); val != "" {
if err := json.Unmarshal([]byte(val), &v); err != nil {
return v, openfeature.NewParseErrorResolutionError(err.Error())
}
return v, nil
}
return v, openfeature.NewFlagNotFoundResolutionError("")
}

Creating a Compliant Provider

Repository Setup

Providers written for the go-sdk are all maintained in the go-sdk-contrib repository, containing both hooks and providers.
The following commands can be used to setup the go-sdk-contrib repository, they will clone the repository and set up your provider specific go module under /providers/MY-NEW-PROVIDER-NAME adding a go.mod and README.md file. Your module will them be referenced in the top level go.work file.

git clone https://github.com/open-feature/go-sdk-contrib.git
cd go-sdk-contrib
make PROVIDER=MY-NEW-PROVIDER-NAME new-provider
make workspace-init

Creating Your Provider

In order for your feature flag Provider to be compatible with the OpenFeature go-sdk, it will need to comply with the OpenFeature spec. For the go-sdk this means that it will need to fit to the following interface:

type FeatureProvider interface {
Metadata() Metadata
BooleanEvaluation(flagKey string, defaultValue bool, evalCtx FlattenedContext) BoolResolutionDetail
StringEvaluation(flagKey string, defaultValue string, evalCtx FlattenedContext) StringResolutionDetail
FloatEvaluation(flagKey string, defaultValue float64, evalCtx FlattenedContext) FloatResolutionDetail
IntEvaluation(flagKey string, defaultValue int64, evalCtx FlattenedContext) IntResolutionDetail
ObjectEvaluation(flagKey string, defaultValue interface{}, evalCtx FlattenedContext) InterfaceResolutionDetail
Hooks() []Hook
}
ArgumentDescription
flagKeyA string key representing the flag configuration used in this evaluation
defaultValueThe default response to be returned in the case of an error
evalCtxThe underlying type of FlattenedContext is map[string]interface{}, this provides ambient information for the purposes of flag evaluation. Effectively acting as metadata for a request
ProviderResolutionDetailThe provider response object from a flag evaluation, it contains the following fields: Variant (string), Reason (openfeature.Reason), ResolutionError (openfeature.ResolutionError)
XxxResolutionDetailThe type specific wrapper for the ProviderResolutionDetail struct. Contains two attributes: Value (type specific), ProviderResolutionDetail (ProviderResolutionDetail)

We can use our previously defined logic to build the Evaluation methods with ease, in the below example the core logic has been refactored into a separate function (resolveFlag()) to reduce code repetition, returning the ResolutionDetail structure directly. This means that the only type specific code required is a type cast the returned interface{} value, and for the type specific ResolutionDetail to be returned, e.g. BoolResolutionDetail.

type Provider struct {
EnvFetch func(key string) (StoredFlag, error)
}

func (p *Provider) resolveFlag(flagKey string, defaultValue interface{}, evalCtx openfeature.FlattenedContext) openfeature.InterfaceResolutionDetail {
// fetch the stored flag from environment variables
res, err := p.EnvFetch(flagKey)
if err != nil {
var e openfeature.ResolutionError
if !errors.As(err, &e) {
e = openfeature.NewGeneralResolutionError(err.Error())
}

return openfeature.InterfaceResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
ResolutionError: e,
Reason: openfeature.ErrorReason,
},
}
}
// evaluate the stored flag to return the variant, reason, value and error
variant, reason, value, err := res.Evaluate(evalCtx)
if err != nil {
var e openfeature.ResolutionError
if !errors.As(err, &e) {
e = openfeature.NewGeneralResolutionError(err.Error())
}
return openfeature.InterfaceResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
ResolutionError: e,
Reason: openfeature.ErrorReason,
},
}
}

// return the type naive ResolutionDetail structure
return openfeature.InterfaceResolutionDetail{
Value: value,
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
Variant: variant,
Reason: reason,
},
}
}

func (p *Provider) BooleanEvaluation(flagKey string, defaultValue bool, evalCtx openfeature.FlattenedContext) openfeature.BoolResolutionDetail {
res := p.resolveFlag(flagKey, defaultValue, evalCtx)
// ensure the returned value is a bool
v, ok := res.Value.(bool)
if !ok {
return openfeature.BoolResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
ResolutionError: openfeature.NewTypeMismatchResolutionError(""),
Reason: openfeature.ErrorReason,
},
}
}
// wrap the ResolutionDetail in a type specific parent
return openfeature.BoolResolutionDetail{
Value: v,
ProviderResolutionDetail: res.ProviderResolutionDetail,
}
}

Based upon this BooleanEvaluation method, the remaining Evaluation methods are simple to populate, leaving only 2 more methods, Metadata and Hooks.

the Metadata() method is very simple to implement, it simply needs to return a Metadata object, currently this object only requires one field - Name

func (p *Provider) Metadata() openfeature.Metadata {
return openfeature.Metadata{
Name: "environment-flag-evaluator",
}
}

The Hooks() method gives the go-sdk access to Provider hooks, this sits outside the scope of this tutorial, so for now we will just return an empty slice of hooks. Link to spec.

func (p *Provider) Hooks() []openfeature.Hook {
return []openfeature.Hook{}
}

Now that the Provider conforms to the OpenFeature spec, it can be registered to the OpenFeature go-sdk and used for flag evaluation.

Example usage

package main

import (
"context"
"encoding/json"
"fmt"
"os"

fromEnv "github.com/open-feature/go-sdk-contrib/providers/from-env/pkg"
"github.com/open-feature/go-sdk/pkg/openfeature"
)

// init function sets a bool flag environment variable called AM_I_YELLOW
func init() {
flagDefinition := fromEnv.StoredFlag{
DefaultVariant: "not-yellow",
Variants: []fromEnv.Variant{
{
Name: "yellow-with-targeting",
TargetingKey: "user",
Value: true,
Criteria: []fromEnv.Criteria{
{
Key: "color",
Value: "yellow",
},
},
},
{
Name: "yellow",
TargetingKey: "",
Value: true,
Criteria: []fromEnv.Criteria{
{
Key: "color",
Value: "yellow",
},
},
},
{
Name: "not-yellow",
TargetingKey: "",
Value: false,
Criteria: []fromEnv.Criteria{
{
Key: "color",
Value: "not yellow",
},
},
},
},
}
flagM, _ := json.Marshal(flagDefinition)
os.Setenv("AM_I_YELLOW", string(flagM))
}

func main() {
// create instance of the new provider
provider := fromEnv.FromEnvProvider{}
// register the provider against the go-sdk
openfeature.SetProvider(&provider)
// create a client from via the go-sdk
client := openfeature.NewClient("am-i-yellow-client")

// we are now able to evaluate our stored flags, providing different FlattenedContexts to manipulate the response
fmt.Println("I am yellow!")
boolRes, err := client.BooleanValueDetails(
context.Background(),
"AM_I_YELLOW",
false,
openfeature.NewEvaluationContext(
"",
map[string]interface{}{
"color": "yellow",
},
),
)
printResponse(boolRes.Value, boolRes.ResolutionDetail, err)

fmt.Println("I am yellow with targeting!")
boolRes, err = client.BooleanValueDetails(
context.Background(),
"AM_I_YELLOW",
false,
openfeature.NewEvaluationContext(
"user",
map[string]interface{}{
"color": "yellow",
},
),
)
printResponse(boolRes.Value, boolRes.ResolutionDetail, err)

fmt.Println("I am asking for a string!")
strRes, err := client.StringValueDetails(
context.Background(),
"AM_I_YELLOW",
"i am a default value",
openfeature.NewEvaluationContext(
"",
map[string]interface{}{
"color": "not yellow",
},
),
)
printResponse(strRes.Value, strRes.ResolutionDetail, err)
}

// simple response printing function
func printResponse(value interface{}, resDetail openfeature.ResolutionDetail, err error) {
fmt.Printf("value: %v\n", value)
if err != nil {
fmt.Printf("error: %v\n", err)
} else {
fmt.Printf("variant: %v\n", resDetail.Variant)
fmt.Printf("reason: %v\n", resDetail.Reason)
}
fmt.Println("--------")
}

Console output:

I am yellow!
value: true
variant: yellow
reason: TARGETING_MATCH
--------
I am yellow with targeting!
value: true
variant: yellow-with-targeting
reason: TARGETING_MATCH
--------
I am asking for a string!
value: i am a default value
error: evaluate the flag: TYPE_MISMATCH
--------

The provider used in this example can be found here

· 3 min read
Skye Gill

Hooks

A Hook taps into one or more of the flag evaluation's lifecycle events (before/after/error/finally) to perform the same action at that point for every evaluation.

Objective

Create and integrate a spec compliant hook that validates that the result of a flag evaluation is a hex color.

Prerequisites

  • Golang 1.17+

Repository setup

Hooks written for the go-sdk are all maintained in the go-sdk-contrib repository, containing both hooks and providers. The following commands can be used to setup the go-sdk-contrib repository, this clones the repository and sets up your hook specific go module under /hooks/MY-NEW-HOOK-NAME adding a go.mod and README.md file. The module will then be referenced in the top level go.work file.

git clone https://github.com/open-feature/go-sdk-contrib.git
cd go-sdk-contrib
make HOOK=MY-NEW-HOOK-NAME new-hook
make workspace-init

Creating the hook

In order for the Hook to be compatible with the OpenFeature go-sdk, it will need to comply to the OpenFeature spec. For the go-sdk this means that it will need to conform to the following interface:

type Hook interface {
Before(hookContext HookContext, hookHints HookHints) (*EvaluationContext, error)
After(hookContext HookContext, flagEvaluationDetails InterfaceEvaluationDetails, hookHints HookHints) error
Error(hookContext HookContext, err error, hookHints HookHints)
Finally(hookContext HookContext, hookHints HookHints)
}

In order to conform to the interface we are forced to implement all of these functions, despite only wanting to tap into the After lifecycle event. Let's leave the other functions empty to achieve this:

// Hook validates the flag evaluation details After flag resolution
type Hook struct {
Validator validator
}

func (h Hook) Before(hookContext of.HookContext, hookHints of.HookHints) (*of.EvaluationContext, error) {
return nil, nil
}

func (h Hook) After(hookContext of.HookContext, flagEvaluationDetails of.InterfaceEvaluationDetails, hookHints of.HookHints) error {
err := h.Validator.IsValid(flagEvaluationDetails)
if err != nil {
return err
}

return nil
}

func (h Hook) Error(hookContext of.HookContext, err error, hookHints of.HookHints) {}

func (h Hook) Finally(hookContext of.HookContext, hookHints of.HookHints) {}

Notice the Validator field of type validator in the Hook struct. This is defined as such:

type validator interface {
IsValid(flagEvaluationDetails of.InterfaceEvaluationDetails) error
}

This allows us to supply any validator that implements this function signature, you can either create your own validator or use one of the existing validators. This tutorial uses the existing hex regex validator.

Integrating the hook

  1. Install dependencies
go get github.com/open-feature/go-sdk
go get github.com/open-feature/go-sdk-contrib/hooks/validator
  1. Import the dependencies
package main

import (
"context"
"fmt"
"github.com/open-feature/go-sdk-contrib/hooks/validator/pkg/regex"
"github.com/open-feature/go-sdk-contrib/hooks/validator/pkg/validator"
"github.com/open-feature/go-sdk/pkg/openfeature"
"log"
)
  1. Create an instance of the validator hook struct using the regex hex validator
func main() {
hexValidator, err := regex.Hex()
if err != nil {
log.Fatal(err)
}
v := validator.Hook{Validator: hexValidator}
}
  1. Register the NoopProvider, this simply returns the given default value on flag evaluation. This step is optional, the sdk uses the NoopProvider by default but we're explicitly setting it for completeness
openfeature.SetProvider(openfeature.NoopProvider{})
  1. Create the client, call the flag evaluation using the validator hook at the point of invocation
client := openfeature.NewClient("foo")

result, err := client.
StringValueDetails(
context.Background(),
"blue",
"invalidhex",
openfeature.EvaluationContext{},
openfeature.WithHooks(v),
)
if err != nil {
fmt.Println("err:", err)
}
fmt.Println("result:", result)
  1. Check that the flag evaluation returns an error as invalidhex is not a valid hex color
go run main.go
err: execute after hook: regex doesn't match on flag value
result {blue 1 {invalidhex }}

Note that despite getting an error we still get a result.

· 6 min read
Pete Hodgson

I've recently been involved1 in OpenFeature, an effort to define a standard API and SDK for feature flagging. At first glance, you might wonder whether feature flagging needs a standard. It's just a function call and an if statement, right? Well, no, not really. I'll explain why, and then talk about some of the benefits that I hope OpenFeature will bring to the space.

The Feature Flagging iceberg

When I talk to people about adopting feature flags, I often describe feature flag management as a bit of an iceberg. On the surface, feature flagging seems really simple, almost trivial. You call a function to find out the state of a flag, and then you either go down one code path, or the other. However, once you get into it turns out that there's a fair bit of complexity lurking under the surface.

Organizations that begin using feature flags at any sort of scale quickly learn that they need some of that functionality lurking under the surface. This is why flag management platforms like LaunchDarkly and Split.io exist. Their value is not in providing a fancy if statement, it's in all those extra features lurking below the surface - a web-based management interface, the ability to perform controlled incremental rollout, permissions and audit trails, integration into analytics systems, and so on.

Everybody needs an SDK

While most of the value of a flag management platform lies beneath the surface, each platform still has to provide that surface-level capability - the ability to evaluate a flag at runtime. And that ability needs to be available in each tech stack. So every flag management vendor ends up maintaining a small flock of feature flagging SDKs in various tech stacks. Even when we're just talking about glorified if statements, this is actually a lot of work, and it's work that is duplicated by each feature management platform.

This is where OpenFeature comes in. By defining a standard API and providing a common SDK, it allows vendors to focus on just implementing a small vendor-specific integration kernel (a "provider") in each language, which then plugs into the OpenFeature SDK. This leaves the bulk of the flag evaluation functionality in a given tech stack to be built once in a shared, vendor-neutral implementation.

OpenTelemetry, but for feature flags

The model here is similar to the (very successful) OpenTelemetry project in the observability space. By defining a shared, open core OpenTelemetry has allowed vendors in the observability space to work within a shared ecosystem of open-source instrumentation libraries.

Rather than each vendor building their own library to instrument the myriad libraries and runtimes that are out there, every system can use the same shared set of instrumentation libraries. Looking in the Javascript ecosystem, for example, there are instrumentation libraries for Next.js, Express, Fastify, Mongo, knex, typeorm, Redis, GraphQL, and many more.

From Effort(n*m) to Effort(n+m)

Before OpenTelemetry, it wasn't really feasible to develop really high-quality instrumentation for every library. Observability vendors didn't have the capacity or deep experience to build rich, idiomatic instrumentation into every library out there, and the library maintainers certainly didn't have the bandwidth to create instrumentation support for every observability platform. OpenTelemetry solved this by creating a single target for both sides to support. It turned an N*M problem into an N+M problem.

My hope is that OpenFeature will do the same for feature flagging. This can happen at two levels. Firstly, OpenFeature will provide a single target for framework-specific feature flag evaluation, which can be done in a really idiomatic, ergonomic way. For example, a React feature flagging client can provide flag evaluation mechanisms using hooks, providers and components, rather than plain old Javascript functions. This idiomatic addition is an example which various vendors have made already, but such ergonomic improvements are spread across each vendor-specific library, and frankly none of them are perfect. If we could get to a single, vendor-neutral library then engineering effort could be focused in one place, and users of the client would not need to re-learn a slightly different API for each vendor. This same situation applies for every runtime that needs feature flag evaluation - we can build a single, standard feature flag evaluation client for Android, React Native, iOS, Spring Boot, Elixir, Django, Fastify, Vertx, gin, and so on. And each of these clients can provide a rich, idiomatic API that provides a great developer experience in each of these frameworks.

Flag evaluation requires context

As a side note, "feature flag evaluation" means more than just "check a flag and return a boolean". Any non-trivial flag evaluation also requires context - which user is this flag being evaluated for, or which demographic market, or which environment, or which server. That contextual information is often available in one place (a request handler for example) but needed in another place - at the point a flag is being evaluated. A naive feature flagging client forces the developer to track this context themselves, so that they can pass it into the client during flag evaluation. A delightful feature flagging client provides the ability for the context to be recorded in one place (often using thread-local storage or an equivalent), and then automatically applied during flag evaluation. Most flagging clients do not do this automatically, because of this N*M problem. OpenFeature would make this sort of delightful experience much more feasible. It would be very straightforward to write a little extension to an authentication library such as Passport.js that would record context about the current user into OpenFeature, so that it's automatically applied during any subsequent flag evaluation.

A win-win-win

My hope is that OpenFeature will provide a benefit that's greater than the sum of its parts, something that's a win for vendors, for open-source maintainers, and for teams using feature flags. Flag management platforms will be freed from having to each maintain their tiresome heap of flag evaluation clients. Framework communities will have the opportunity to write rich, idiomatic flag evaluation clients which target a standard, vendor-neutral flag evaluation API. Finally, developers using feature flags will have more ergonomic and delightful feature flagging capabilities, using an API which remains constant no matter which feature flagging platform they're using.

OpenFeature is actively looking for more participants. If you'd like to get involved, don't be shy! Join a community call, or join the #OpenFeature CNCF slack channel, and help us build a great open standard that benefits the industry.


  1. I'm currently serving as a member of the Bootstrap Governing Committee