[OFREP] Flipt-agnostic flag evaluation in combination with environments/namespacing
One of the powerful features of Flipt is separation by environment and/or namespace. Evaluation a flag across those dimensions is trivial using Flipt SDKs but is not really possible if you use OFREP.
The problem arises from the fact that environments and namespaces are not concepts in the openfeature specification. Flags in openfeature specification can just be evaluated with provided context, which is practically a map with key/values that can be matched against. This context can be leveraged to create the illusion of environments and namespaces.
In order to integrate OFREP evaluation on Flipt, I would argue it makes sense to define constants for environment and namespace context keys that are not Flipt-specific, in order to avoid implementation-detail specific properties to leak through the OFREP abstraction on the client side.
To be explicit, this is how I would envision such an integration from the client side:
import (
"github.com/open-feature/go-sdk-contrib/providers/ofrep"
"github.com/open-feature/go-sdk/openfeature"
)
provider := ofrep.NewProvider("https://my.flipt.server.com")
openfeature.SetNamedProvider("demo-app", provider)
client := openfeature.NewClient("demo-app")
output := client.String(context.TODO(), "<flag>", "<default>", openfeature.NewTargetlessEvaluationContext(map[string]any{
"environment": "<env>",
"namespace": "<namespace>",
}))
Flipt should be able to interpret environment and namespace evaluation context, and match it to Flipt environment and namespace to fetch the flag provided.
This will be a breaking change for people who are using these keys to match with existing rules, but in my opinion a better way of integrating OFREP with Flipt-specific concepts.
I am available for opening up a pull request if this gets support. :)
Hey @DerkSchooltink
Thank you for raising this up. I believe this was discussed few times with Open Feature team and they recommended to use http headers for such information. So it would be better to use ofrep.WithHeaderProvider option during the ofrep provider creation for this.
Flipt uses X-Flipt-Environment http header for evaluation API internally. X-Flipt-Namespace could be another option for ofrep integration.
We are happy to see your contributions to the project. You are welcome!
Interesting. That should work, but doesn't remove the problem of making the OFREP implementation on the client side Flipt-agnostic.
Purely pragmatical, this doesn't matter and I can easily set up my local stack such that all clients are required to provide these headers, but from a conceptual standpoint it would be nice to leave clients unaware that Flipt is my OFREP server 😁
It depends on your use case. But let's say there is some configuration management which provides the ofrep url and extra headers for the app service(like environment variables, .env file or any provider like k8s secrets, vault, aws secretsmanager...). Your client code may look like
// use some static configuration to initialize this
providerURI := "https://my.flipt.server.com"
providerExtraHeaders := map[string]string{
// ... values here loaded from static configuration
}
// init ofrep provider
ops := make([]ofrep.Option, 0)
for k, v := range providerExtraHeaders {
ops = append(ops, ofrep.WithHeaderProvider(func() (string, string) {
return k, v
}),
)
}
provider := ofrep.NewProvider(providerURI, ops...)
openfeature.SetNamedProviderAndWait("demo-app", provider)
// use openfeature
client := openfeature.NewClient("demo-app")
output := client.String(context.TODO(), "<flag>", "<default>",
openfeature.NewTargetlessEvaluationContext(map[string]any{}))
Your evaluation context contains only business related properties. It shouldn't include extra info in each api call which is easy to miss sometimes.
what do you think about it @DerkSchooltink?
That makes sense when there's central management of the integration. It becomes a lot harder to orchestrate such an implementation detail across an entire organisation.
My main concern is that at some point maybe Flipt is no longer sufficient for my use-case, and migrating to another OFREP-capable feature flag application will be a pain because swapping out the X-Flipt headers has to be orchestrated across multiple applications/teams. I would trust a generic environment eval parameter over a specific X-Flipt header for that reason.
Of course I understand that it is not really a Flipt issue. And perhaps it is even in Flipt's best interest to slightly vendor-lock you into itself by having to rely on these headers. But I would argue that if you are really all-in in supporting OFREP compatibility, it would be possible to customise environment mapping WITHOUT Flipt-specific configuration
As a meet-me-in-the-middle kind of solution, perhaps it is an option to configure the header name which Flipt maps to an environment and namespace. Such that we can define our own mapping from OFREP clients to Flipt. And to keep backwards compatibility, fall back to existing X-Flipt header mappings if no config is provided.
@erka let me know what you think!
To be honest, I don’t see how these two headers create any vendor lock-in. These headers are Flipt-specific and not part of the OFREP spec. Other OFREP servers don’t have namespace or environment features; they usually serve a single set of flags per instance. Including these attributes in the evaluation context won’t help with migrating to another provider. Also the evaluation context will contain extra unexpected attributes after migration (vendor-locked to Flipt?). That said, your project is huge and I might not fully understand your use case.
If you really want to stay agnostic and avoid putting anything Flipt-specific in your client code, I suggest using a URL like <ns>-<env>.features.example.com. Place Nginx in front of Flipt as a reverse proxy with some configuration like this:
server {
...
server_name ~^(?<ns>[^-]+)-(?<env>[^.]+)\.features.example\.com$;
location / {
proxy_pass http://flipt.example.com;
# Inject custom headers from regex captures
proxy_set_header X-Flipt-Namespace $ns;
proxy_set_header X-Flipt-Environment $env;
}
...
}
If you want to swap Flipt with another OFREP-compliant server, you can easily do it in a single place and your client code won't need any changes. @DerkSchooltink