quarkus
quarkus copied to clipboard
Lightweight feature flags extension
Description
Feature flags (or toggles, flips, switches...) are a very powerful tool for cloud-native apps and the current Quarkus offer in that area is a bit limited (unless I missed something 😄). There is of course quarkus-unleash based on the excellent Unleash but one does not always want to deploy something as big as Unleash, with the external Unleash server requirement and all the set up / maintenance costs and the SPOF risk that come with it. There are many very good reasons to use Unleash and many reasons as well to not use it (depending on the project requirements), but that's not the subject of this issue.
I'm part of a https://console.redhat.com team that uses a lot of feature flags for several reasons, including:
- we often need to merge parts of unfinished huge features that must remain disabled until everything's ready for production
- we have to deal with risky migrations that sometimes affect the entire app and require extra precautions (intensive testing, automated and manual) before we actually migrate the production data
Our feature flags are in-memory and based on some custom Java code with no external dependency. At first, I wanted to write a Quarkus blog post to show how we use these flags and how we run our tests with any flags combinations very easily. Then I realized this could actually be the base of a new Quarkus extension.
Here's how things would look like from a user perspective:
// Some package
import quarkus.featureflag.FeatureFlags;
@FeatureFlags
public class FeatureFlipper {
@ConfigProperty(name = "quarkus.feature-flag.huge-feature.enabled", defaultValue = "true")
boolean hugeFeatureEnabled;
@ConfigProperty(name = "quarkus.feature-flag.risky-feature.enabled", defaultValue = "false")
boolean riskyFeatureEnabled;
/* getters and setters */
}
// Other package
@ApplicationScoped
public class MyService {
@Inject
FeatureFlipper featureFlipper;
public void doSomething() {
if (featureFlipper.isHugeFeatureEnabled()) {
// Do something with the huge feature
} else {
// Do something without the huge feature, or even nothing at all
}
}
}
- The
@FeatureFlags
annotation would only be allowed on one class of a Quarkus app because it's a good practice to centralize all flags at the same place for easier maintenance. - The
@Singleton
CDI scope would automatically be added to the annotated class. - Each
@ConfigProperty boolean
field of that class would be automatically considered as a feature flag and adefaultValue
would be mandatory. - Flags values would be changed from environment variables, i.e.
QUARKUS_FEATURE_FLAG_RISKY_FEATURE_ENABLED=true
. - An extension config key
quarkus.feature-flag.log-at-startup=true|false
(default true) would be used to log all flags at application startup because that usually helps a lot when something related to the flags goes wrong on prod. - An extension config key
quarkus.feature-flag.rest-endpoint.enabled=true|false
(default false) would be used to expose all feature flags through a REST API because that allows a UI to determine what it should or should not show to the users depending on the enabled features. One of the Quarkus resteasy extensions would obviously be needed for that and a warning would be displayed if the config key was enabled with no resteasy extension available in the app. - All feature flags could be displayed/managed from the Quarkus Dev Console.
- An extra extension annotation could be used on feature flags fields to add information such as a nice description to use while logging the flags status at app startup.
All annotations or config keys names suggested here are open for discussion, I'm sure we could better name them than my proposal 😄
The way my team uses these lightweight (because of the absence of external dependency) feature flags is based on our experience and has proven to be very reliable and easy to use. There are many other ways of including feature flags in a Quarkus app and they all come with their advantages and drawbacks. One key aspect of our approach is that feature flags values can be overriden from tests at any time, allowing all flags combinations within any test with no restriction. This is not possible using things like System.setProperty("the-flag", "true")
or Quarkus test profiles (which are less flexible).
I think this could be useful to the Quarkus community. WDYT?
If the creation of this extension is confirmed, I will take care of everything listed here (and probably more).
Interested in this extension, please +1 via the emoji/reaction feature of GitHub (top right).
Repository name
quarkus-feature-flags
Short description
Lightweight, in-memory and easy to use feature flags
Repository Homepage URL
https://quarkiverse.github.io/quarkiverse-docs/<REPOSITORY_NAME>/dev/
Repository Topics
- quarkus-extension
...
Team Members
- @gwenneg
GitHub Applications?
- [ ] Stale - Automatically close stale Issues and Pull Requests that tend to accumulate during a project
Additional context
No response
@gwenneg i think this is a good idea to have a lightweight option. However THANK YOU for putting me on to Unleash I had never heard of this product before but its actually exactly what my client is looking for!
Thanks for bringing this up @gwenneg! I think this sounds like a very useful feature.
To be honest, I think this would be benefit from an initial discussion on the mailing list and opinions on how to move forward have consolidated, it would be great to transfer the decisions here and move forward.
P.S. One thing I would like to figure out in the aforementioned discussion is how much it would make sense to have a declarative first approach - where classes and/or methods are swapped based on the presence of a feature set, using annotations and config. The idea being that method bodies should be unaffected. Another thing to ponder is how much work we can do at build time vs runtime.
To be honest, I think this would be benefit from an initial discussion on the mailing list and opinions on how to move forward have consolidated, it would be great to transfer the decisions here and move forward.
Okay, I'll initiate the discussion there.
P.S. One thing I would like to figure out in the aforementioned discussion is how much it would make sense to have a declarative first approach - where classes and/or methods are swapped based on the presence of a feature set, using annotations and config. The idea being that method bodies should be unaffected.
Just to be sure, you meant the test method bodies, right?
Another thing to ponder is how much work we can do at build time vs runtime.
My plan was to do as much as possible at build time. I started experimenting about this with a small POC where I already log the feature flags status at startup using generated bytecode in the class annotated with @FeatureFlags
. I'll continue experimenting a bit this week and then I'll create the mailing list discussion. What I wrote above contained ideas that I need to confirm with actual code 😃
Just to be sure, you meant the test method bodies, right?
No, I meant production code. I want to explore whether it's possible to have different classes and / or methods depending on the feature set.
Discussion initiated on the mailing list.