This is part two of the series. For this to make sense, I recommend you first read / complete part one.
Actually Useful Feature Flags
Feature flags as demonstrated in part one of this tutorial are not actually very useful. Yes, they abstract the values to an external storage system, and that is nice.
But the real value of feature flags is when you can combine runtime information and / or environmental and / or client information to adjust the value of the flag in realtime.
An Example
In the previous example, the possible flag values were:
-
#FFFFFF
(white) ifflags.json
did not exist -
#73A53E
(green) because that was thedefaultVariant
However, what if we wanted a ruleset like this:
-
#FFFFFF
(white) ifflags.json
does not exist -
#4AB9D9
(blue) ifflags.json
does exist and the user is using the Google Chrome browser. -
#73A53E
(green) ifflags.json
does exist and the user is using the Safari browser -
#FF0000
(red) ifflags.json
does exist and the user is using any other browser (ie. not Chrome or Safari)
We could, of course, code something like this:
// Assume req is the request object
const userAgent = req.headers['user-agent'];
if (req.headers['user-agent'].includes("Chrome")) {
const backgroundColour = "#4AB9D9"; // blue
}
else if (req.headers['user-agent'].includes("Safari")) {
const backgroundColour = "#73A53E"; // green
}
else {
const backgroundColour = "#FF0000"; // red
}
But that is:
- Missing the advantages of using a feature flag system
- Hardcoded
- Messy
Let the Flag System Perform That Logic
Instead, let flagd perform that logic for you.
New Flag Definition
First, overwrite your existing flags.json
file with this content:
{
"$schema": "https://flagd.dev/schema/v0/flags.json",
"flags": {
"background-colour": {
"state": "ENABLED",
"variants": {
"white": "#D6D4D2",
"green": "#73A53E",
"orange": "#FF7C00",
"lime": "#D3D309",
"blue": "#4AB9D9",
"red": "#FF0000"
},
"defaultVariant": "red",
"targeting": {
"if": [
{
"in": [
"Chrome",
{
"var": "uA"
}
]
},
"blue",
{
"if": [
{
"in": [
"Safari",
{
"var": "uA"
}
]
},
"green"
]
}
]
}
}
}
}
You can read that as:
- One flag called
background-colour
is available and enabled. - It has
6
possible values: white, green, orange, lime, blue and red. - By default, the flag variant will be
red
and thus return a value of#FF0000
. - When the flag is evaluated, if some additional contextual data with the key of
uA
is present and the value ofuA
contains the wordChrome
then returnblue
, notred
. - When the flag is evaluated, if
uA
is present andChrome
is not present butSafari
is, returngreen
, notred
. - In all other cases (
uA
is not present oruA
is present but contains neitherChrome
orSafari
), return the default:red
.
Here you can see that, with relatively simple JSON Logic, flagd can build complex rules. flagd can perform lots of other logic and the flagd playground is a great place to experiment in realtime.
New app.js
Now overwrite your app.js
file to this:
var fs = require('fs');
var http = require('http');
var port = 9123;
var html = fs.readFileSync('index.html').toString();
var {OpenFeature} = require("@openfeature/server-sdk")
var {FlagdProvider} = require("@openfeature/flagd-provider")
var getBackgroundColour = async function(req) {
const userAgent = req.headers['user-agent'];
const backgroundColour = await client.getStringValue("background-colour", "#FFFFFF", { 'uA': userAgent });
return backgroundColour;
}
// OpenFeature Initialisation Code
// Register your feature flag provider
OpenFeature.setProvider(new FlagdProvider());
// create a new client
const client = OpenFeature.getClient();
// =======================================================
// This is our main HttpServer Handler
// =======================================================
var server = http.createServer(async function (req, res) {
res.writeHead(200, 'OK', {'Content-Type': 'text/html'});
// replace background colour
var finalHtml = html.replace(/BACKGROUND-COLOUR/g, await getBackgroundColour(req));
res.write(finalHtml);
res.end();
});
// Listen on port 9123, IP defaults to 127.0.0.1
server.listen(port);
// Put a friendly message on the terminal
console.log('Server running at http://127.0.0.1:' + port + '/');
console.log("Info", "Service is up and running - feed me with data!");
Open http://127.0.0.1:9123
in Google Chrome and then again in Safari.
Explanation
Lots of the content is the same as part one, but let's run through things.
First look at app.js
. Again we initialised OpenFeature and the flagd provider.
This time though, when we request the background colour, we pass the req
(request) object into getBackgroundColour
.
The User-Agent
string is extracted from the req
object:
const userAgent = req.headers['user-agent'];
The "user agent" is sent to flagd with a key of uA
(you can pass any values you wish to flagd - we just chose to use uA
in an attempt to be clear that we choose the key).
If flagd is not available (because we're using in-process
this would mean the flags.json
file is not available), then the value #FFFFFF
would be returned.
flagd performs the logic (which you saw is specified in the flags.json
file) and returns the correct value back to the OpenFeature client.
Summary
So, now you know how to use contextual runtime information with OpenFeature and flagd.
Whether you're using flagd or any other OpenFeature provider, the client code is identical.
You are limited only by your imagination: User agents, browser versions, timestamps, geolocations, semver release versions and anything else you wish to use as a contextual attribute.
As long as your chosen backend can deal with the data you send, you can achieve the same as the demo above.
Happy feature flagging! ๐
Top comments (0)