Coder Social home page Coder Social logo

saantiaguilera / feature-flags Goto Github PK

View Code? Open in Web Editor NEW
20.0 2.0 0.0 177 KB

Feature Toggles (aka Feature Flags) in Kotlin

License: GNU General Public License v3.0

Kotlin 83.41% Java 16.59%
feature-flags feature-toggles feature-toggle flag android android-architecture java jvm

feature-flags's Introduction



Feature Toggles (aka Feature Flags) in Kotlin

Build

This project is based completely on Feature Toggles by Pete Hodgson (martinfowler.com), it's available for any JVM environment

Feature Toggles (often also referred to as Feature Flags) are a powerful technique, allowing teams to modify system behavior without changing code. They fall into various usage categories, and it's important to take that categorization into account when implementing and managing toggles. Toggles introduce complexity. We can keep that complexity in check by using smart toggle implementation practices and appropriate tools to manage our toggle configuration, but we should also aim to constrain the number of toggles in our system.

Set Up

We are using sem-ver. You can find the latest stable version under the Github Releases tab.

implementation "com.saantiaguilera.featureflags:feature-flags:<version>"

No proguard / R8 rules needed, this works transparently with it :)

Usage

Usage is very diverse, since it can fit most of the developers needs. The basics should be:

Creating a feature-flag

Features are easily declared through a FeatureFlag class. A feature flag contains only 3 properties:

  • key: Used for identifying the feature you are referring. This key can easily be used as an index in a database, as an ID from a backend response, etc.
  • defaultValue: Used as a default when no feature is present at the providers for the given key. This is to avoid possible bugs or undesired behaviours. Consumer safety first.
  • usage: String specifying the usage / description / explanation of this feature.

When defining them, you can keep them centralized under a catalog or scattered around your project, it's up to you.

If you have a completely modular app, you may probably want a finite number of catalogs (grouped by modules)

Single feature

val frescoImages = FeatureFlag("feature.home.fresco_images", true, "Enables fresco API for loading images")

Catalog features

object FeatureCatalog {
    val homeV2 = FeatureFlag("feature.home.new_design_v2", false, "Shows the newly designed Home V2")
    val horizontalSignIn = FeatureFlag("feature.login.horizontal_sign_in", false, "Shows the horizontal sign in modal")
    val frescoImages = FeatureFlag("feature.home.fresco_images", true, "Enables fresco API for loading images")
    val cache2K = FeatureFlag("feature.arch.cache_2k", false, "Enables cache 2K for something")
    val cardPaymentsWithVisa = FeatureFlag("feature.checkout.card_payments_visa", false, "Enables card payments with VISA")
}

Creating a provider

You can find some implemented providers to use as examples under the testapp directory

A provider is in charge of deciding if a given feature-flag is enabled or disabled. It can be through a cache, api-call, shared-preference, or any other strategy. If it doesn't has a requested feature-flag, it should return the default feature value marking it as missing. An implementation can contain any specific states to enrich its behaviours, eg. it can have a User to request specific user flags (or have more complex decisions depending on the user's data). It could also use this same User to perform AB Testings if it wanted to.

An example of a simple provider implementation using an external service (backend) as a feature-flag storage could be:

class RepositoryProvider(private val repository: RepositoryApi) : FeatureFlagProvider {

    // This API sample returns a map of String:Boolean denoting a feature's key and if it's enabled or not.
    // Eg: "feature.home.new_design_v2": true
    private var features: Map<String, Boolean> = emptyMap()

    init {
        // Consider doing it in another thread or coroutine ;)
        features = repository.getFeatures()
    }

    override fun provide(feature: FeatureFlag): FeatureFlagResult {
        return repository.getFeatures()[feature.key]
            ?.let { FeatureFlagResult(feature, it) } // If not null, we return an existing result with it's value 
            ?: FeatureFlagResult(feature) // If null we return the default value
    }
}

You can easily create other types such as:

  • Cache providers
  • Repository providers
  • Firebase Remote Config providers
  • SharedPreferences providers
  • Entity specific providers (eg. that depends on User or any DTO)
  • AB Testing providers
  • Free/Subscribed/Premium feature-flag provider (permission toggles)
  • Mock providers (for testing or debug builds)

Since it's just a contract, you can create them with whatever you want to. You can even make them refreshable if you'd like (by creating a Refreshable interface and calling it whenever you want to)

All of the above can be found under the testapp directory

Priority Providers

If you wish to have provider groupings, we provide by default a PriorityFeatureFlagProvider that groups providers and through a Comparator you can decide the priority in which a key will be looked up.

You can find an example of usage under the testapp directory or in the tests.

Consuming a provider

Once you have your providers and feature-flags you can start using them anywhere.

In the following samples I will simply show how to consume the API. It's recommended to apply inversion of decision so you can avoid conditionals and code branches.

Functional usage

fun navigateHome(featureFlagProvider: FeatureFlagProvider) {
    featureFlagProvider.provide(FeatureCatalog.HomeV2)
        .onEnabled {
            // Navigate to home v2
        }
        .onDisabled {
            // Navigate to home v1
        }
}

Conditional usage

fun navigateHome(featureFlagProvider: FeatureFlagProvider) {
    val result = featureFlagProvider.provide(FeatureCatalog.HomeV2)

    if (result.enabled) {
        // Navigate to home v2
    } else {
        // Navigate to home v1
    }
}

Handling missing feature-flags

Regardless of the result, a flag may have been missing at the provider (and the provided result was simply a default one).

Functional usage

fun navigateHome(featureFlagProvider: FeatureFlagProvider) {
    featureFlagProvider.provide(FeatureCatalog.HomeV2)
        .onMissing {
            // Do something? It wasn't found at the provider, you may want to log it somewhere
            // so you get notice of it.
            // Don't worry though, the default feature value will still be executed so it's bug free
        }
}

Conditional usage

fun navigateHome(featureFlagProvider: FeatureFlagProvider) {
    val result = featureFlagProvider.provide(FeatureCatalog.HomeV2)
    
    if (!result.exists) {
        // Do something? It wasn't at the provider, you may want to log it somewhere
        // so you get notice of it.
        // Don't worry though, the default feature value will still be used afterwards
        // for checking if it's enabled
    }
}

Mentions

The API was heavily inspired by the following contents:

feature-flags's People

Contributors

saantiaguilera avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

feature-flags's Issues

proper credit on README

Hi Santi, great work on this lib.
This (minor) issue is just to point out that the author of the Feature Toggles article you reference twice in README is not Martin Fowler. The author is Pete Hodgson. It just turns out the article is hosted on martinfowler.com.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.