LaunchDarkly Developer Documentation

Get started in under 30 minutes.
LaunchDarkly provides feature flags as a service for Java · Python · Ruby · Go · Node.js · PHP · .NET. Control feature launches -- who sees what and when -- without multiple code deploys. Easy dashboard for phased rollouts, targeting and segmenting.
Need more help? write us at support@launchdarkly.com

Get Started    Documentation

SDK contributor's guide

Our SDKs are all open source, and we encourage pull-requests and other contributions from the community. This document provides a detailed explanation of how our SDKs work, with an eye towards helping anyone create a LaunchDarkly SDK for a new platform from scratch or contribute to an existing SDK implementation.

Algorithms

There are three main components to a LaunchDarkly SDK implementation: receiving feature flag updates, evaluating feature flags, and backend event recording.

Receiving feature flag updates

Our server-side SDKs store all feature flags in memory. Flag updates (triggered by making changes on your LaunchDarkly dashboard) must be received asynchronously by one of two mechanisms:

  1. Via our streaming API. An SDK can subscribe to our streaming API at https://stream.launchdarkly.com/all. This call must include an Authorization header with value ${sdk_key}, where ${sdk_key} is the SDK key the client application passed to the LaunchDarkly client configuration. Our streaming API uses the server-sent events protocol.
  2. By polling our evaluation API. An SDK can poll https://app.launchdarkly.com/sdk/latest-all. This call must include an Authorization header with value ${sdk_key}, where ${sdk_key} is the API key the client application passed to the LaunchDarkly client configuration. Ideally, the SDK implementor should poll this resource once per second. The implementation must be throttled to make at most one call per second. This resource may return caching headers. In particular, etags and conditional requests should be leveraged to reduce network transfer in the common case where no flags have been modified between requests.

The SDK can use either of the above mechanisms to populate a feature flag store, which is a thread-safe, in-memory map from feature flag keys to feature flag JSON. This makes evaluating a feature flag extremely cheap-- simply read the flag from the in-memory store, and evaluate it using the following algorithm.

The flag store itself should be exposed as an interface so that alternative implementations (e.g. a Redis-backed store) can be implemented.

Evaluating feature flags

To implement the variation method in a server side SDK, a feature flag is retrieved from the store and evaluated directly in the SDK. For evaluation rules please refer to Flag evaluation rules in server-side SDKs.

Our Go SDK serves as our reference evaluation algorithm.

Recording events

The SDKs send six kinds of events: "feature” and "debug" events produced every time a feature flag is evaluated (depending on what controls are enabled for that flag), “identify” and "index" events which push user data to LaunchDarkly, “custom” events that are triggered by the client application via an SDK method, and "summary" events that roll up a series of "feature" events. Events must be batched and sent to the LaunchDarkly server asynchronously. This is a requirement for all SDKs. Conceptually, this is accomplished by creating a background job that runs every 30 seconds and makes a POST call to /api/events/bulk. This call must have a Content-Type header set to application/json, an Authorization header set to api_key ${api_key}, and must include a JSON body consisting of a list of events. On platforms with poor multithreading support, you may need to get creative.

The time between batch calls to the event API should be configurable. The SDK should have a configuration element for the maximum number of events to store between batch calls. If this capacity is exceeded before a batch call, events should be discarded and a warning logged.

“Feature” events have the following shape:

 {
    "kind": "feature",
    "userKey": "feature@test.com",
    "creationDate": 1462220944000,
    "key": "my.feature.key",
    "value": false,
    "default": false,
    "version": 42,
    "prereqOf": "parent-flag"
  }

The kind should always be feature. A "feature" event should only by generated it the "trackEvents" attribute of the flag is sent. The "prereqOf" attribute should be set to a flag key if this flag evaluation was only performed in order to determine whether the prerequisite values were met for the indicated flag.

The "debug" event is a variant on the "feature" event, with two differences. It has has "kind" set to "debug" and it inlines the "user" value. It is only sent if the "debugEventsUntilDate" attribute is set for a feature flag and indicates a unix timestamp (in milliseconds) that has not yet elapsed. "debug" events are not controlled by the "trackEvents" field.

 {
    "kind": "debug",
    "user": {
      "key": "feature@test.com",
      "custom": {
        "groups": [
          "microsoft",
          "google"
        ]
      }
    },
    "creationDate": 1462220944000,
    "key": "my.feature.key",
    "value": false,
    "default": false,
    "version": 42,
    "prereqOf": "parent-flag"
  }

​The user has the same schema as user objects for feature flag evaluation. The creationDate must be the time the feature flag was requested as Unix epoch time in milliseconds. The key is the key of the feature flag requested. The value is the value of the feature flag returned by feature flag evaluation. Finally, the default field, which is optional, should be set to true if feature flag evaluation failed and the value returned was the default value passed to variation. If the default field is omitted, it is assumed to be false.

Because the system time available to the SDK may not be in sync with our server time, we encourage the developer to prevent a second check to prevent writing "debug" when a previous Date header returned when posting to /api/events/bulk has passed the "debugEventsUntilDate".

The details of the "user" object used in a feature flag evaluation as reported by the "feature" event are transmitted periodically via a separate "index" event. It is expected that the SDK will only send "index" events no more than 5 seconds and that user attributes will not change between most evaluations.

  {
    "kind": "index",
    "user": {
      "key": "feature@test.com",
      "custom": {
        "groups": [
          "microsoft",
          "google"
        ]
      }
    },
    "creationDate": 1462220944000,
  }

The kind for an index event is "index". The value of "user" is the same as described above for a "debug" event.

“Identify” events are produced when the client application calls an SDK method called identify. Identify events have an identical structure to "index" events:

{
  "kind": "identify",
  "user": {
    "key": "user@test.com"
  },
  "creationDate": 1462220944000
}

“Custom” events are produced when the client application calls an SDK method called track. Custom events have the following shape:

{
  "kind": "custom",
  "user": {
    "key": "user@test.com"
  },
  "creationDate": 1416003645758,
  "key": "custom.event.key",
  "data": {
    "custom": "value"
  }
}

Most of the fields are self-explanatory. The data field is optional, and can contain any arbitrary JSON.

Summary events

The SDK is expected to send summary events for every flush interval describing all of the feature evaluations that occurred during that interval. Summary events include all feature evaluations, regardless of whether the trackEvents field was set for individual flags.

{
  "startDate": 1517350765387,
  "endDate":  1517350825243,
  "features": {
    "flag-key": {
      "default": "default-value",
      "counters": [
        {
          "value": "result-value"
          "version": 17,
          "count": 23,
          "variation": 1,
        },
        {
          "value": "another-value"
          "version": 17,
          "count": 2,
          "variation": 0,
        }
      ]
    },
    "another-flag-key" :{
      "default": "some-other-result-value",
      "counters": [{
        "value": "some-other-result-value",
        "version": 19,
        "count": 29,
        "variation": null (or leave empty)
      }],
    "nonexistent-flag-key" : {
      "default": false,
      "counters": [
        {
          "unknown": true,
          "value": false,
          "count": 3,
        }]
    }
}

The summary event represents a set of feature flag evaluations occurring during an interval defined by startDate and endDate, broken out by feature flag. For each feature flag, the following data should be provided:

Field
Meaning

default

the default value the SDK received for the feature (sampled at some point during the interval)

startDate

the timestamp of the first feature flag evaluation included in this packet. This value is a UNIX Epoch time in milliseconds.

endDate

the timestamp of the last feature flag evaluation included in this packet. This value is a UNIX Epoch time in milliseconds.

counters

A set of counters as described below.

The counters field is an array of objects with the following shape:

Field
Meaning

version

the version of the feature flag evaluated

value

the value returned by the SDK for the flag evaluation

variation

A zero-based index into the list of variations. If this field not provided, a default value was used.

count

the number of times this value/version/variation combination was an evaluation result during during the interval

unknown (optional)

if this is present and true it indicates that no flag by this name was known to LaunchDarkly (and therefore the SDK default value was returned)

The SDK should generate be a separate entry in counters for each unique value/version/variation combination seen during the interval.

The call to /api/events/bulk should contain a JSON list of events.

Principles

  1. Minimize external dependencies — try to use as few external libraries as possible. If all you need from a library is a single utility method, prefer inlining it to importing the library (license permitting). This helps minimize version conflicts if client applications happen to be using the same libraries.
  2. Be consistent with the conventions and style of the target language. For example, if most libraries in that language are written in snake case, use snake case. If camel case is standard, use camel case. Target language consistency trumps consistency with other LaunchDarkly SDKs.
  3. Use the target language's preferred module and package layout. Java — jar files, Ruby — gems, etc.
  4. Publish libraries to the most popular module hosting service for the target language. For example, the Java SDK is published to Maven central. The Python SDK is published to PyPi. .Net should be published via NuGet. Try not to publish to a nonstandard location that requires clients to customize their project build definitions (for example, don't use Bintray).
  5. If there's a platform standard for generating and publishing documentation, use it. For example, the Java SDK includes Javadoc comments and a build target to produce Javadoc pages.
  6. All SDKs should be open sourced and published with the Apache 2.0 license.
  7. SDK versions should follow semantic versioning guidelines.
  8. Unit tests should be written using a standard testing framework.
  9. In statically typed languages, make the SDK as type-safe as possible.
  10. Use best practices with respect to logging. A proper logging package should be used, and log entries should have appropriate log levels. Simply logging output to standard out is (probably) not appropriate.
  11. Use best practices with respect to error handling. Be extremely defensive and assume anything can fail. For variation calls, use a top-level catch-all error handler that returns the default value if any exceptions / unexpected errors occur. Log when the default value is returned because of an exception or unexpected error.
  12. The SDK should be thread-safe. SDKs should not be expected to have to introduce locking around API calls for thread-safety. Furthermore, the LDClient instance itself should be a thread-safe singleton— clients should be expected to instantiate one LDClient for the lifetime of their application.
  13. All SDK calls to the LaunchDarkly REST API with an entity body should have the Content-Type header set to application/json.
  14. All timestamps should be sent as Unix time in milliseconds. Event timestamps should reflect (as nearly as possible) the time that the event was created, not the time that the event was sent.
  15. The HTTP client used in the SDK should have reasonable timeouts set by default. If supported by the client library, separate connection and read timeouts should be specified. A reasonable but conservative set of default values is 1 second for connection timeouts and 2 seconds for read timeouts.

Common pitfalls

We've seen a few common mistakes in integrating the LaunchDarkly SDK at customer sites. It's helpful for the SDKs to introduce specific logging around these errors and / or handle them as gracefully as possible:

  1. Passing null (or the language equivalent) as the user key, or omitting the key property altogether. SDKs should log this at error level and return the default value.
  2. Including a trailing slash in the base URL of a custom config object. Example: https://app.launchdarkly.com/, which when appended to something like /api, yields https://app.launchdarkly.com//api. SDKs should deal with this by trimming trailing slashes off of custom base URL parameters.

More resources

Some examples from other LaunchDarkly SDK implementations:

The LaunchDarkly REST API documentation may be helpful as well.



SDK contributor's guide


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.