Giter Site home page Giter Site logo

monarch's Introduction

Monarch 🦋

Monarch is a small, flexible, type safe, and multiplatform abstraction for feature flags.

In chaos theory, the butterfly effect is the sensitive dependence on initial conditions in which a small change in one state of a deterministic nonlinear system can result in large differences in a later state.

Wikipedia

Download

[versions]
monarch = "0.2.2"

[libraries]
monarch-compose = { module = "io.github.kevincianfarini.monarch:compose", version.ref = "monarch" }
monarch-core = { module = "io.github.kevincianfarini.monarch:core", version.ref = "monarch" }
monarch-integration-environment = { module = "io.github.kevincianfarini.monarch:environment-integration", version.ref = "monarch" }
monarch-integration-launchdarkly = { module = "io.github.kevincianfarini.monarch:launch-darkly-integration", version.ref = "monarch" }
monarch-mixin-kotlinxjson = { module = "io.github.kevincianfarini.monarch:kotlinx-serialization-mixin", version.ref = "monarch" }
monarch-test = { module = "io.github.kevincianfarini.monarch:test", version.ref = "monarch" }

Usage

Defining flags

Monarch provides compile time safety for the feature flags you define and consume. Flag keys are bound to a type and a default value.

object FancyFeatureEnabled : BooleanFeatureFlag(
    key = "fancy_feature_enabled",
    default = false,
)

The FancyFeatureEnabled flag can later be referenced in code and checked by the compiler.

Monarch provides several feature flag types in the 'core' artifact.

  • BooleanFeatureFlag
  • LongFeatureFlag
  • DoubleFeatureFlag
  • StringFeatureFlag

Obtaining values

Values can be obtained from a FeatureFlagManager for a given feature flag.

fun showFeature(manager: FeatureFlagManager) {
    if (manager.currentValueOf(FancyFeatureEnabled)) {
        showFancyFeature()
    } else {
        showBoringFeature()
    }
}

Observing value changes as a Flow

Some third party SDKs, like LaunchDarkly, provide callbacks for flags when their values change. Monarch provides an additional abstraction for this, called ObservableFeatureFlagManager, which exposes these changes as a Flow.

suspend fun showFeature(manager: ObservableFeatureFlagManager) {
    manager.valuesOf(FancyFeatureEnabled).collect { enabled -> 
        if (enabled) {
            showFancyFeature()
        } else {
            showBoringFeature()
        }
    }
}

Observing value changes as a Compose State

Monarch offers a companion artifact that makes observing flag values as Compose State simple.

@Composable 
fun ShowFeature(manager: ObservableFeatureFlagManager) {
    val enabled by manager.stateOf(FancyFeatureEnabled)
    if (enabled) {
        FancyFeature()
    } else {
        BoringFeature()
    }
}

Testing

Monarch provides an out-of-the-box test implementation of an ObservableFeatureFlagManager called InMemoryFeatureFlagManager. It can be mutated under test to exercise specific branches of code dictated by your flags.

@Test 
fun test_flag_changes() = runTest {
   val manager = InMemoryFeatureFlagManager()
   manager.valuesOf(FancyFeatureEnabled).test {
       assertFalse(awaitItem())
       manager.setCurrentValueOf(FancyFeatureEnabled, true)
       assertTrue(awaitItem())
   }
}

Supplying a FeatureFlagManager

Monarch's built-in implementations of FeatureFlagManager take lists of FeatureFlagManagerMixin and a FeatureFlagDataStore.

A FeatureFlagDataStore is the entity closely tied to the underlying feature flagging SDK. It's unlikely you will implement this unless you're integrating with a feature flagging platform that Monarch doesn't currently support.

Typical usage of FeatureFlagManager implementations expects that the FeatureFlagDataStore, all FeatureFlagManagerMixin instances, and the FeatureFlagManager itself will be provided as part of your dependency graph. Below is a sample with Dagger.

@Module object FeatureFlaggingModule {
    
    @Provides 
    fun providesDataStore(): FeatureFlagDataStore { /* omitted */ }
    
    @Provides @IntoSet 
    fun providesJsonMixin(
        json: Json
    ): FeatureFlagManagerMixin = JsonFeatureFlagManagerMixin(json)
    
    @Provides 
    fun providesManager(
        dataStore: FeatureFlagDataStore, 
        mixins: Set<FeatureFlagManagerMixin>,
    ): FeatureFlagManager = MixinFeatureFlagManager(
        store = dataStore, 
        mixins = mixins.toList(),
    )
}

monarch's People

Contributors

kevincianfarini avatar renovate[bot] avatar

Stargazers

Igor Romashkin avatar Nimai Walsh avatar Ranjith avatar  avatar Stacy Devino avatar Iván Carracedo avatar Danil Yudov avatar fergdev avatar Roman Belinsky avatar  avatar Scott Rayapoullé avatar Indrajit Chakrabarty avatar Ali Rahimpour avatar Md. Mahmudul Hasan Shohag avatar Andriy Deputat avatar Todd Bednarczyk avatar Roman avatar Danil Nikolaev avatar Edoardo Luppi avatar Ivan avatar Saud Khan avatar Raman Gupta avatar Abdul Basit avatar Vengatesh M avatar  avatar Randhir Kumar Gupta avatar Nicholas Lythall avatar Sean Watkins avatar Raphael Tarita avatar Andrew Sherepenko avatar Mikołaj Pich avatar Litrik De Roy avatar Manoj Selvam avatar Aleksey Mikhailov avatar Christopher-Marcel Esser avatar Nikhil Bansal avatar Zokirjon avatar huskacsaca avatar Mervyn McCreight avatar M R 3 Y avatar Suresh avatar Andrew Watson avatar Sinan Kozak avatar Márton Braun avatar  avatar Art Shendrik avatar Samuel Gagarin avatar  avatar Ivan “CLOVIS” Canet avatar Oleg Yukhnevich avatar Nav Singh avatar Casey Brooks avatar Ataul Munim avatar  avatar Thomas Marsh avatar Boy Hoody avatar Justin Brooks avatar Mitch Ware avatar Veyndan Stuart avatar Nicholas Doglio avatar stylianosgakis avatar

Watchers

Lucian avatar Richard Leggett avatar Alex Brown avatar  avatar Oleg Yukhnevich avatar

monarch's Issues

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/release.yml
  • actions/checkout v4
  • actions/setup-java v4.2.1
.github/workflows/test.yml
  • actions/checkout v4
  • actions/setup-java v4.2.1
gradle
gradle.properties
settings.gradle.kts
build.gradle.kts
compose/gradle.properties
compose/build.gradle.kts
core/gradle.properties
core/build.gradle.kts
gradle/libs.versions.toml
  • org.jetbrains.compose.runtime:runtime 1.6.11
  • org.jetbrains.kotlin-wrappers:kotlin-node 18.16.12-pre.634
  • org.jetbrains.kotlin:kotlin-test 2.0.0
  • org.jetbrains.kotlinx:kotlinx-coroutines-core 1.8.1
  • org.jetbrains.kotlinx:kotlinx-coroutines-test 1.8.1
  • org.jetbrains.kotlinx:kotlinx-serialization-core 1.7.1
  • org.jetbrains.kotlinx:kotlinx-serialization-json 1.7.1
  • com.launchdarkly:launchdarkly-android-client-sdk 5.3.1
  • app.cash.molecule:molecule-runtime 2.0.0
  • app.cash.turbine:turbine 1.1.0
  • com.android.library 8.5.0
  • org.jetbrains.dokka 1.9.20
  • org.jetbrains.kotlin.plugin.compose 2.0.0
  • org.jetbrains.kotlin.multiplatform 2.0.0
  • org.jetbrains.kotlin.plugin.serialization 2.0.0
  • com.vanniktech.maven.publish 0.29.0
integrations/environment-variable/gradle.properties
integrations/environment-variable/build.gradle.kts
integrations/launch-darkly/gradle.properties
integrations/launch-darkly/build.gradle.kts
mixins/kotlinx-serialization-json/gradle.properties
mixins/kotlinx-serialization-json/build.gradle.kts
gradle-wrapper
gradle/wrapper/gradle-wrapper.properties
  • gradle 8.9

  • Check this box to trigger a request for Renovate to run again on this repository

Reconsider the necessity of having `ByteArray` as a primitive flag type

Most flagging SDKs don't expose a way to acquire byte arrays easily. Firebase Remote Config does, but not as a simple function on their client. Instead, you have to query for a FirebaseRemoteConfigValue and then call asByteArray on it. Similarly, LaunchDarkly doesn't expose this at all and in our integration we just throw a NotImplementedError.

In the environment variable integration I chose to encode bytes as hex strings for the interface. This feels like too opinionated of a decision for me to make -- what if someone wants to store their flag's bytes as base64 encoded strings?

I think this probably lives better as a mixin. Perhaps a base64 mixin and a hex mixin. Though, I am dubious of the merits of exposing bytes in feature flags at all.

Reconsider the necessity of suspending operations

Right now, currentValueFor is a suspending function. This was chosen with the use case of a client-server RDBMS backing a data store, and thus potentially desiring suspending ops.

No client library for feature flags we've evaluated yet requires suspending simply to get a value. Does imposing suspending functions on consumers of this API seem like overkill? Possibly.

Include artifact for firebase remote config

My workplace no longer uses Firebase Remote Config to manage our feature flags, so this does not have as high of a priority as it once did. I am happy to accept PRs implementing this feature, but I see two major impediments:

  1. The Firebase Android SDK is an Android library and offers no JVM API that can be used outside of Android. This means that our module will have to declare an androidTarget sourceset, which is in flux.
  2. The Firebase Android SDK's main entrypoint -- FirebaseRemoteConfig.java -- offers no interface for us to program against. In effect this means that it would be impossible for us to test our integration with Firebase without having to perform IO and use a real firebase account. This is really gross.

Our implementation should strive to work solely on the JVM if possible, though not required. We will not accept tests which stipulate real network connections and a real firebase account. Overcoming this will likely require a feature request on the Firebase repo, an internal interface which we can control under test that's implemented with FirebaseRemoteConfig in production, or a clean-room implementation of the remote config SDK with the HTTP API.

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.