DEV Community

Cover image for Generating Types for Feature Flags
Mark Phelps for Flipt

Posted on • Originally published at flipt.io

Generating Types for Feature Flags

Feature flags are amazing tools to help you ship your code safely by guarding your end users against incomplete features in production. Many teams use feature flags in this way, to ship code that makes up a larger feature, enabling testing by a group of internal or beta users without having to deploy the code all at once.

This ability to slowly roll out and guard functionality is especially helpful in environments where multiple applications (services) make up the larger end-user experience. Sometimes these services are owned by orthogonal teams, which makes the communication around and planning of feature releases all the more important.

We believe that feature flags give engineers superpowers, allowing them to change application functionality at runtime, without requiring re-deploys. But, as you may have heard, "with great power, comes great responsibility". This ability to modify a running application through the click of a button or external configuration change (see our post on Flipt for GitOps ❤️) also introduces risk.

Feature flags introduce a dependency on an external system, which means your application code is no longer guaranteed to work the same in all environments. At Flipt we believe that the power and flexibility gained through the use of feature flags in production is worth the risk they introduce, however, we do think there is more we can do to help mitigate this risk and remove some of the sharp edges.

Flag Identifiers

In Flipt, flag keys are unique (within a namespace) identifiers that are assigned to each feature flag in order to differentiate them from one another. It is important to choose meaningful and descriptive flag keys that accurately represent the functionality they control. This will make it easier for developers and stakeholders to understand and manage the feature flags within the system.

When defining flag keys, we recommend to follow a consistent naming convention. This can help maintain a clear and organized system of flags, especially when dealing with a large number of flags. For example, using a prefix or a specific format for flag keys can provide better context and improve readability.

Because flag keys are simple strings in most feature flag domains, they are extremely flexible and can contain almost any value. However, as most programmers know, strings are also easily mistyped which can lead to errors. In the case of feature flags, these errors include incorrect or non-existent flags being referenced, leading to runtime exceptions or unexpected behavior.

To mitigate the risk associated with mistyped flag keys, one common approach is to use a constant to define and manage flag keys. This can provide type safety and help catch errors at compile time rather than runtime for compiled languages. Using constants for flag keys, however, does have its downsides. Nothing stops multiple constants from being created for the same value, which means there is no guarantee that when searching your code for a specific constant identifier, you will find all references for that flag key. There is also nothing stopping you from defining a constant with an invalid value in the first place.

Also, using constants does not give the end user flexibility to define their own type which can enforce limiting a field being of a specific set of values.

interface EvaluationRequest {
  requestId?: string;
  flagKey: string; // we only want a subset of possible values here
  entityId: string;
  context?: Record<string, string | undefined>;
}

let req: EvaluationRequest = {
  flagKey: "foo", // this is allowed!
  entityId: "1234",
};
Enter fullscreen mode Exit fullscreen mode

Adding Type Safety

Recently, a user of Flipt proposed adding type safety to our Node/Typescript SDK. After some back and forth and some learning on our part, we concluded that we could leverage some of the pieces we already have in Flipt and create a CLI to generate these types.

When it comes to generating code, you first need a source or specification that can be parsed and then used for writing the code. We realized that for our feature flag data we already have this source, our features.yml file format which we use for imports/exports and to power our GitOps, Local, and Object Storage data backends!

Again, that same amazing user created a prototype/proof of concept CLI tool that parses the above-mentioned features.yml and generates Flag and Context union types for Typescript. We just open-sourced and published this tool that we’re calling Typed!

Usage info is in the README, but here’s how you could quickly generate type-safe Typescript code from your features data in Flipt.

  1. Export your feature data from Flipt, either via our API or through a direct DB connection using our export command: flipt export
  2. Run npx @flipt-io/typed --lang ts --input /path/to/features.yml to output the generated Typescript code, or pipe it to a file.
  3. Import the types and use in your code!
import { Flag } from "./types/flipt";

interface EvaluationRequest {
  requestId?: string;
  flagKey: Flag["key"]; // enforces we can only use valid flag keys that exist in our Flipt instance
  entityId: string;
  context?: Record<string, string | undefined>;
}

let invalid: EvaluationRequest = {
  flagKey: "foo", // this will now error!
  entityId: "1234",
};

let req: EvaluationRequest = {
  flagKey: "language", // correct!
  entityId: "1234",
};
Enter fullscreen mode Exit fullscreen mode

Here's what the generated types look like:

// Generated by @flipt-io/typed
export type Flag = {
  key: "language";
  value?: "es" | "en" | "fr";
};
Enter fullscreen mode Exit fullscreen mode

You can see that the generated types are very simple, and only contain the information that is needed to enforce type safety. The Flag type contains a union string type for the flag key and value.

Typed also supports the generation of a Context type which is the union of all the possible contexts that are defined in the features.yml file. This means that the Context type will contain all the possible fields that can be used in the EvaluationRequest interface.

Contributing

Typed is still very early days, and considered experimental. There are plenty of features/support we want to add and rough DX edges we want to smooth over, but importantly, we wanted to get it out there and see if anyone finds it useful. We also want to support more languages than Typescript in the future, including but not limited to Go, Java, Rust, Python.. pretty much every language that we have an ‘official’ SDK for.

We’d love it if you gave Typed a try and helped us out by filing issues, feature or language requests, or even contributing code. No contribution is too small and we’d love it if the open source community helped drive our roadmap.

In a future post, we’ll discuss another project we’re working on to improve the developer experience of working with feature flags.. so stay tuned!

As always, feel free to reach out on GitHub or come chat in our Discord. We’d love to learn more about your specific workflow when it comes to adding and working with feature flags in your codebases.

Top comments (0)