Merci

Framework for Feature Flags and Runtime Configuration

License

License

Categories

Categories

Checkstyle Application Testing & Monitoring Code Analysis
GroupId

GroupId

com.medallia.merci
ArtifactId

ArtifactId

checkstyle-checks
Last Version

Last Version

0.1.2
Release Date

Release Date

Type

Type

pom
Description

Description

Merci
Framework for Feature Flags and Runtime Configuration
Project URL

Project URL

https://github.com/medallia/merci
Project Organization

Project Organization

com.github.medallia
Source Code Management

Source Code Management

https://github.com/medallia/merci

Download checkstyle-checks

How to add to project

<!-- https://jarcasting.com/artifacts/com.medallia.merci/checkstyle-checks/ -->
<dependency>
    <groupId>com.medallia.merci</groupId>
    <artifactId>checkstyle-checks</artifactId>
    <version>0.1.2</version>
    <type>pom</type>
</dependency>
// https://jarcasting.com/artifacts/com.medallia.merci/checkstyle-checks/
implementation 'com.medallia.merci:checkstyle-checks:0.1.2'
// https://jarcasting.com/artifacts/com.medallia.merci/checkstyle-checks/
implementation ("com.medallia.merci:checkstyle-checks:0.1.2")
'com.medallia.merci:checkstyle-checks:pom:0.1.2'
<dependency org="com.medallia.merci" name="checkstyle-checks" rev="0.1.2">
  <artifact name="checkstyle-checks" type="pom" />
</dependency>
@Grapes(
@Grab(group='com.medallia.merci', module='checkstyle-checks', version='0.1.2')
)
libraryDependencies += "com.medallia.merci" % "checkstyle-checks" % "0.1.2"
[com.medallia.merci/checkstyle-checks "0.1.2"]

Dependencies

compile (2)

Group / Artifact Type Version
com.puppycrawl.tools : checkstyle jar 8.12
org.slf4j : slf4j-api jar 1.7.25

Project Modules

There are no modules declared in this project.

Merci is a framework for feature flags and runtime configuration. It relies on an easy to learn, recursive JSON structure.

Quick Start

This guide describes with examples how to use Merci.

Using Merci in your Java(TM) application

Merci releases can be downloaded from the Maven central repository. Adding Merci to a Java(TM) application just requires adding Merci as a dependency to pom files.

<dependency>
    <groupId>com.medallia.merci</groupId>
    <artifactId>merci-core</artifactId>
    <version>0.1.2</version>
    <type>pom</type>
</dependency>

Central Configuration Files

The core idea behind Merci is to use a small set of central, easy to read files with a recursive JSON or YAML structure. These files are fetched by Merci's configuration loader through a scheduled, aynchronous task.

The following example of a configuration file contains a feature flag called "enable-international-welcome". Its evaluation at runtime results in a true or false value, depending on the environment of the deployed application and the current user. The feature flag would only be true, which means active, if the current user is 'joe' and the environment of the deployed application instance is 'qa'.

{
  "feature-flags": {
    "enable-international-welcome": {
      "value": false,
      "modifiers": {
        "type": "environment",
        "contexts": {
          "qa": {
            "value": false,
            "modifiers": {
              "type": "user",
              "contexts": {
                "joe": {
                  "value": true
                }
              }
            }
          },
          "production": {
            "value": false
          }
        }
      }
    }
  }
}

Runtime configurations - or short configs - share the same recursive structure but instead of boolean values, their values are objects, that are deserialized to immutable config objects.

{
  "configs": {
    "com.medallia.merci.DBConfig": {
      "value": {
        "hosts": [ "invalid-host" ],
        "port": -1
      },
      "modifiers": {
        "type": "environment",
        "contexts": {
          "qa": {
            "value": {
              "hosts": [ "db1.somewhere.in.qa" ],
              "port": 9889
            }
          },
          "production": {
            "value": {
              "hosts": [ "db1.somewhere.in.prod", "db2.somewhere.in.prod", "db3.somewhere.in.prod" ],
              "port": 9889
            }
          }
        }
      }
    }
  }
}

Initializing Merci

Merci's configuration loader, which is responsible for scheduling retrieval and processing of configuration changes, relies on a registered configuration fetcher to retrieve the latest configuration content from a local or remote source. The library provides a generic interface, that applications implement for fetching their configuration files. For testing purposes and for applications, which only read configurations from the local file system, Merci's Filesystem Configuration Fetcher class should be sufficient.

/**
 * Configuration fetcher for this application.
 */
public class MyAppConfigurationFetcher implements ConfigurationFetcher {
    @Override
    public Map<String, String> fetch(List<String> fileNames, String application) throws IOException {
        /* return new map with requested file names and latest configuration content for the provided application name. */
    }
}

Initializing Merci's components should follow the example below. Both configuration manager singletons, the feature flag manager and the config manager instances should be made available to the rest of the application code.

/* Initialize configuration fetcher. */
ConfigurationFetcher fetcher = new MyAppConfigurationFetcher(...);

/* Use Merci to initialize configuration managers and loader. */
Merci merci = new Merci(fetcher);

FeatureFlagManager featureFlagManager = merci.addFeatureFlagManager("myapp").registerFile("/featureflags.json").build();
ConfigManager configManager = merci.addConfigManager("myapp").registerFile("/configs.json").build();

ConfigurationLoader loader = merci.createLoader(Duration.ofSeconds(180));
loader.start();

Toggling Features with Merci

Merci's feature flag manager allows developers to selectively enable and disable parts of their code without redeploying or restarting application instances. In the following code example, the execution path is determined by applying the runtime configuration context to the external definition of the "enable-international-welcome" feature flag.

public class HelloWorld {

    private final FeatureFlagManager featureFlagManager;

    public HelloWorld(FeatureFlagManager featureFlagManager) {
        this.featureFlagManager = featureFlagManager;
    }

    @GET
    public String get(@Context HttpServletRequest request) {
        ConfigurationContext context = ...;
        /* Evaluate, if feature flag is active based on provided runtime context. */
        if (featureFlagManager.isActive("enable-international-welcome", context)) {
            return new Translator().translate("Hello World!", request.getLocale().getLanguage());
        }
        return "Hello World!";
    }

Using Merci for Runtime Configuration

Runtime configs are managed by a registered instance of Merci's config manager class. Using config objects of type HelloWorldConfig allows an external list of supported languages to be passed in at runtime.

public class HelloWorld {

    private final ConfigManager configManager;

    public HelloWorld(ConfigManager configManager) {
        this.configManager = configManager;
    }

    @GET
    public String get(@Context HttpServletRequest request) {
        ConfigurationContext context = ...;
        /* Get config object based on provided runtime context. */
        HelloWorldConfig config = configManager.getConfig(HelloWorldConfig.class, context);
        if (config.isSupportedLanguage(request.getLocale().getLanguage())) {
            return new Translator().translate("Hello World!", request.getLocale().getLanguage());
        }
        return "Hello World!";
    }
    
    /** Config class. */
    public static class HelloWorldConfig {

        private final Set<String> languages;

        public HelloWorldConfig() {
            this(Collections.emptyList());
        }

        @JsonCreator
        public HelloWorldConfig(@JsonProperty("languages") List<String> languages) {
            this.languages = new HashSet<>(languages);
        }

        public boolean isSupportedLanguage(String language) {
            return languages.contains(language);
        }
    }
}
com.medallia.merci

Medallia

Versions

Version
0.1.2