Using The LaunchDarkly Dashboard And JSON Types To Create Light-Weight Application Administrative Features
In the LaunchDarkly Lunch-n-Learn panel discussion in NYC, I had theorized that you could use LaunchDarkly as a way to "hack together" light-weight administrative features for your web application. Since the LaunchDarkly multivariate feature flag values are so open-ended, I suggested that you could provide JSON (JavaScript Object Notation) payloads to your application which could then act somewhat like a light-weight database table. What I didn't realize until I went to play with this idea (after the lunch-n-learn) was that LaunchDarkly actually has native support for JSON. Meaning, instead of shoe-horning the concept of JSON into a "String type", LaunchDarkly actually supports a "JSON type" right out of the box. This allows you to pass full Object and Array data structures to your application via the LaunchDarkly dashboard and client. These Object and Array data structures can then be used to implement light-weight administrative and operational functionality in your web application.
As a thought experiment, imagine that you wanted to start black-listing certain IP-addresses in your application (or, white-listing IP-addresses in a "fail closed" kind of scenario). To do this, you'd have to:
- Have a database in which to store the IP addresses.
- Create an administrative interface for updating said database.
- Create a permissions system around said administrative interface.
- Create a data-access abstraction in your application for said database.
- Consume said data-access abstraction in your application (when evaluating IP-addresses).
None of this is particularly complicated. But, it's tedious, it requires human effort allocation, and it probably won't do much to further the feature-set of your core product. In other words, you're not in the business of blocking IP-addresses; so, having to build infrastructure to support IP-address blocking is likely a distraction from your company's goals and mission statement.
Now, if you squint a little bit, the LaunchDarkly dashboard and client can provide all of that functionality with next-to-no effort. If you're a LaunchDarkly customer, you already have a means to securely define and modify data (the dashboard). And, you already have a way to consume said data in your application (the client). All you need is a sufficiently flexible data-structure with which to implement your "database tables" - and that's where the "JSON Type" comes into play.
To demonstrate, let's create a feature flag in our LaunchDarkly dashboard that will provide an Array of IP-addresses that we'd like to block in our web application. I'm going to call this feature flag, "operations-ip-blacklist". Since we're going to be using the JSON Type, we need to create a multivariate feature-flag. This is actually nice because it means that we can have a history of IP-address configurations rather than always changing the same value.
NOTE: In general, I prefix long-term operational feature flags with "OPERATIONS-". This way, it is exceedingly clear that this is not a roll-out feature flag; but, rather, one geared towards the health and stability of the underlying system.
Here's what the variations for this feature flag look like:
Notice that the variation values are being provided as Array structures - not JSON strings. This is really great because you don't have to deal with complicated character escaping when defining the structures - you just define them as you would in the code. Then, LaunchDarkly takes care of serializing them, streaming them to the client (via server-sent events), and deserializing (and presumably caching) them in your application memory space.
And now that we have our "operations-ip-blacklist" LaunchDarkly feature flag, let's create a simple Node.js script that performs some mock HTTP requests with a given IP-address:
// Require the core node modules.
var chalk = require( "chalk" );
var LaunchDarkly = require( "ldclient-node" );
// Require the application modules.
var config = require( "./config" );
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
// NOTE: Internally, the LaunchDarkly client will set up an INTERVAL, which will hold
// the node.js process open until you .close() the client. In a long-running application,
// this doesn't matter. But, in a "test" script, it's just something to be aware of.
var launchDarklyClient = LaunchDarkly.init( config.launchDarkly.apiKey );
// Since we don't really have an HTTP server, we're just going to mock some traffic to a
// mock function that handles mock request / response structures :D Mock all the things!
(async function sendMockHttpRequest() {
try {
// Wait until the LaunchDarkly client is ready before we start sending requests.
// If we don't do this, we're more likely to get the fall-back value rather than
// the synchronized value.
await launchDarklyClient.waitUntilReady();
await mockHandleHttpRequest({
ip: "192.168.10.50"
});
} catch ( error ) {
console.warn( "Error sending mock HTTP request." );
console.error( error );
}
setTimeout( sendMockHttpRequest, 1000 );
})();
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
// I handle incoming HTTP requests.
async function mockHandleHttpRequest( request ) {
// Get the collection of blocked IP-addresses. Internally, we've defined our feature
// flag value as a JSON (JavaScript Object Notation) type. This means that
// LaunchDarkly automatically handles the stringification, streaming, and parsing of
// it for us. In other words, this .variation() call doesn't return a JSON payload -
// it returns the original data structure that we provided in the LD dashboard.
// --
// NOTE: I'm using a static "key" in this case since I don't want to do graduated
// roll-outs - this ipBlacklist value will be the same for all requests at once.
var ipBlacklist = await launchDarklyClient.variation(
"operations-ip-blacklist",
{
key: "my-app" // The static "user" for this task.
},
// Since the JSON type is automatically parsed by the LaunchDarkly client, our
// default value should be the same type as the intended payload. In other words,
// the default value is NOT a JSON STRING but, rather, AN ARRAY. In this case,
// we're defaulting to the empty array so the site can FAIL OPEN.
[]
);
// Check to see if the incoming request IP should be blocked.
// --
// NOTE: Again, notice that ipBlacklist is ALREADY AN ARRAY - we didn't have to parse
// it; that's how it came out of the LaunchDarkly .variation() call.
if ( ipBlacklist.includes( request.ip ) ) {
console.log( chalk.red( "Blocking IP", chalk.bold( request.ip ) ) );
console.log( chalk.red.italic( `... blocking one of ${ ipBlacklist.length } known IP addresses.` ) );
} else {
console.log( chalk.green( "Allowing IP", chalk.bold( request.ip ) ) );
}
}
In this code, I'm just calling my mock HTTP request handler every second. And, in the mock HTTP handler, I'm grabbing the "operations-ip-blacklist" feature flag. The Promise returned by the .variation() method resolves to an Array structure that contains the values from our LaunchDarkly dashboard. There's no parsing or deserialization needed - it's all handled implicitly by the LaunchDarkly client. All we have to do is grab the array of blacklisted IP-addresses and see if the incoming IP-address is in the list.
Now, if we kick off the Node.js test script, then go into the LaunchDarkly dashboard and switch to a variation that contains the mock IP-address, we get the following output:
As you can see, the moment we switched Array structure variations in the LaunchDarkly dashboard, the change was streamed to the Node.js client where it immediately started blocking the mock incoming HTTP requests (you can see this very clearly in the video).
The mechanics of this exploration aren't really any different from the mechanics of my previous LaunchDarkly explorations: set a flag, stream the value, apply the value to the application control flow. The aspect of this exploration that makes it so different is the use of the complex Array structure as a "Type" of feature flag. The ability to use Arrays and Objects brings a degree of flexibility and sophistication that you can't get with simple Booleans, Strings, and Numbers. Complex data structures really allow you to implement light-weight data-intensive features in your application with almost no effort. In this case, we've managed to build an IP-blacklisting feature with what amounts to one or two lines of the code - the rest of the functionality is all provided by the LaunchDarkly infrastructure.
Now, I'm not suggesting that you replace all of your administrative features with LaunchDarkly feature flags. But, I am suggesting that the JSON Type affords a high degree of power. And, depending on what you want to be prioritizing in your business, the JSON Type can stand-in as a means to power light-weight but data-intensive administrative features with almost no effort at all.
Want to use code from this post? Check out the license.
Reader Comments