• HOME
  • INTEGRATIONS
  • SDKS
  • GUIDES
  • API DOCS
No results for ""
EXPAND ALL
launchdarkly.com

EDIT ON GITHUB

SDK contributor's guide

Read time: 10 minutes
Last edited: May 14, 2021

Overview

This topic explains how LaunchDarkly SDKs work. It is intended to help anyone create a LaunchDarkly SDK for a new platform from scratch or contribute to an existing SDK implementation.

Our SDKs are all open source, and we encourage pull-requests and other contributions from the community.

Algorithms

There are three main components to a LaunchDarkly SDK implementation:

Receiving feature flag updates

LaunchDarkly's SDKs store all feature flags in memory. SDKs typically receive flag updates asynchronously through our streaming protocol (as is the default case in most SDKs). While LaunchDarkly's various SDKs typically support the same two mechanisms for receiving feature flag updates, their implementation details vary.

The SDKs can use either of the following mechanisms to populate a feature flag store, which is a thread-safe, in-memory map from feature flag keys to feature flag JSON. Evaluating a feature flag is a very lightweight and inexpensive effort and requires no per-evaluation requests to LaunchDarkly's servers. Server-side SDKs can read flags from the in-memory store and evaluate them using the evaluation algorithm, and client-side SDKs can retrieve pre-evaluated flag values from the in-memory store. To learn more, read Evaluating feature flags.

Receiving flag updates in server-side SDKs

Flag updates are triggered when you make changes on your LaunchDarkly dashboard, and arrive by one of two mechanisms:

  1. Through LaunchDarkly's streaming API: A server-side SDK can subscribe to our streaming API at https://stream.launchdarkly.com/all. This call must include an Authorization header with the value ${sdk_key}, where ${sdk_key} is the SDK key the client application passes to the LaunchDarkly client configuration. Our streaming API uses the server-sent events protocol.

  2. By polling LaunchDarkly's evaluation API: A server-side SDK can poll https://sdk.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 periodically poll this resource. We recommend making one call every thirty seconds; at a minimum, you must throttle the implementation to make at most one call per second. This resource may return caching headers. Use ETags and conditional requests to reduce network transfer when no flags have been modified between requests. This is a common use case, so remember to account for it.

Expose the store itself as an interface so that you can easily implement alternative configurations (for example, a Redis-backed store).

Additionally, server-side SDKs often support a mechanism for receiving feature flag updates without establishing a direct connection to LaunchDarkly. This mechanism relies on the LaunchDarkly Relay Proxy to connect to LaunchDarkly, receive feature flag updates, and keep a persistent feature store up-to-date. When a server-side SDK is configured to operate in this mode, it will defer to the Relay Proxy to update the persistent feature store whenever a feature flag is updated. This mode should be configurable and disabled by default. To learn more, read Using a persistent feature store without connecting to LaunchDarkly.

Receiving flag updates in SDKs using the client-side ID

This section corresponds to a subset of LaunchDarkly's client-side SDKs. To learn more about the client-side ID, read Keys.

Flag updates are triggered when you make changes on your LaunchDarkly dashboard, and arrive by one of two mechanisms:

  1. Through LaunchDarkly's streaming API: These SDKs can subscribe to our streaming API at https://clientstream.launchdarkly.com/eval/<clientSideId>/<user>. The clientSideId variable should be replaced with the LaunchDarkly environment's client-side ID. The user variable should be replaced with the base64-encoded, URL-safe version of the user's JSON object. Our streaming API uses the server-sent events protocol.

  2. By polling LaunchDarkly's evaluation API: These SDKs can poll https://clientsdk.launchdarkly.com/sdk/evalx/<clientSideId>/users/<user>. The clientSideId variable should be replaced with the LaunchDarkly environment's client-side ID. The user variable should be replaced with the base64-encoded, URL-safe version of the user's JSON object. Ideally, the SDK implementor should periodically poll this resource. We recommend making one call every thirty seconds; at a minimum, you must throttle the implementation to make at most one call per second. This resource may return caching headers. Use ETags and conditional requests to reduce network transfer when no flags have been modified between requests. This is a common use case, so remember to account for it.

Receiving flag updates in SDKs using the mobile key

This section corresponds to a subset of LaunchDarkly's client-side SDKs. To learn more about the mobile key, read Keys.

Flag updates are triggered when you make changes on your LaunchDarkly dashboard, and arrive by one of two mechanisms:

  1. Through LaunchDarkly's streaming API: These SDKs can subscribe to our streaming API at https://clientstream.launchdarkly.com/meval/<user>. This call must include an Authorization header with the value ${mobile_key}, where ${mobile_key} is the mobile key the client application passes to the LaunchDarkly client configuration. The user variable should be replaced with the base64-encoded, URL-safe version of the user's JSON object. Our streaming API uses the server-sent events protocol.

  2. By polling LaunchDarkly's evaluation API: These SDKs can poll https://clientsdk.launchdarkly.com/sdk/evalx/users/<user>. This call must include an Authorization header with value ${mobile_key}, where ${mobile_key} is the mobile key the client application passed to the LaunchDarkly client configuration. The user variable should be replaced with the base64-encoded, URL-safe version of the user's JSON object. Ideally, the SDK implementor should periodically poll this resource. We recommend making one call every thirty seconds; at a minimum, you must throttle the implementation to make at most one call per second. This resource may return caching headers. Use ETags and conditional requests to reduce network transfer when no flags have been modified between requests. This is a common use case, so remember to account for it.

Evaluating feature flags

Server-side and client-side SDKs significantly differ in how they evaluate feature flags. See Flag evaluations for a high-level explanation.

Flag evaluation in server-side SDKs

To implement the variation method in a server-side SDK, a feature flag is retrieved from the store and evaluated directly in the SDK. To learn more about evaluation rules, read Flag evaluation rules in server-side SDKs.

Our Go SDK serves as our reference evaluation algorithm.

Flag evaluation in client-side SDKs

As described in Flag evaluations, client-side SDKs do not evaluate flags on their own. LaunchDarkly's services are responsible for evaluating flag rules.

To implement the variation method in a client-side SDK, return the last-known evaluated value from the store for the specified flag key.

Recording events

The SDKs send eight kinds of events:

  • feature and debug events that identify which feature flags are being evaluated,
  • index and identify events which push user data to LaunchDarkly,
  • alias events which associate two users for analytics purposes,
  • custom events that are triggered by the client application by an SDK method,
  • summary events that summarize a series of feature events, and
  • diagnostic events that report on how the SDK is configured and operating.

Events must be batched and sent to the LaunchDarkly server asynchronously. This is a requirement for all SDKs. Do this 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 and debug events

Feature events have the following shape:

1{
2 "kind": "feature",
3 "userKey": "feature@test.com",
4 "contextKind": "user",
5 "creationDate": 1462220944000,
6 "key": "my.feature.key",
7 "value": false,
8 "default": false,
9 "version": 42,
10 "prereqOf": "parent-flag"
11 }

The kind should always be "feature". A feature event should only by generated if 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 optional contextKind attribute should be either "user" or "anonymousUser" depending on the corresponding user's anonymous property. If omitted, the context kind will be interpreted as "user".

The debug event is a variant on the feature event, with two differences. It 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.

1{
2 "kind": "debug",
3 "user": {
4 "key": "feature@test.com",
5 "custom": {
6 "groups": [
7 "microsoft",
8 "google"
9 ]
10 }
11 },
12 "creationDate": 1462220944000,
13 "key": "my.feature.key",
14 "value": false,
15 "default": false,
16 "version": 42,
17 "prereqOf": "parent-flag"
18 }

​The user has the same schema as user objects for feature flag evaluation. The creationDate is 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.

Index and identify events

The details of the user object used in a feature flag evaluation as reported by the feature event are transmitted periodically from a separate index event. It is expected that the SDK will only send index events when user attributes have changed.

1{
2 "kind": "index",
3 "user": {
4 "key": "feature@test.com",
5 "custom": {
6 "groups": [
7 "microsoft",
8 "google"
9 ]
10 }
11 },
12 "creationDate": 1462220944000,
13 }

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, except the kind is set to identify:

1{
2 "kind": "identify",
3 "user": {
4 "key": "user@test.com"
5 },
6 "creationDate": 1462220944000
7}

Alias events

Alias events are produced when the client application calls an SDK's alias method. Additionally, by default, client-side SDKs automatically produce alias events when changing (through calling identify) from an anonymous user to an identified user.

Alias events have the following shape:

1{
2 "kind": "alias",
3 "key": "new-user",
4 "contextKind": "user",
5 "previousKey": "previous-user",
6 "previousContextKind": "anonymousUser",
7 "creationDate": 1462220944000
8}

The kind for an alias event is "alias". The key and previousKey attributes are the users' keys. The contextKind and previousContextKind define the kind of previous and new user; valid values are "user" and "anonymousUser". The creationDate is the time the alias event was generatedas Unix epoch time in milliseconds.

Custom events

Custom events are produced when the client application calls an SDK method called track.

Custom events have the following shape:

1{
2 "kind": "custom",
3 "user": {
4 "key": "user@test.com"
5 },
6 "contextKind": "user",
7 "creationDate": 1416003645758,
8 "key": "custom.event.key",
9 "data": {
10 "custom": "value"
11 }
12}

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

The optional contextKind attribute should be either "user" or "anonymousUser" depending on the corresponding user's anonymous property. If omitted, the context kind will be interpreted as "user".

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.

1{
2 "startDate": 1517350765387,
3 "endDate": 1517350825243,
4 "features": {
5 "flag-key": {
6 "default": "default-value",
7 "counters": [
8 {
9 "value": "result-value"
10 "version": 17,
11 "count": 23,
12 "variation": 1,
13 },
14 {
15 "value": "another-value"
16 "version": 17,
17 "count": 2,
18 "variation": 0,
19 }
20 ]
21 },
22 "another-flag-key" :{
23 "default": "some-other-result-value",
24 "counters": [{
25 "value": "some-other-result-value",
26 "version": 19,
27 "count": 29,
28 "variation": null (or leave empty)
29 }],
30 "nonexistent-flag-key" : {
31 "default": false,
32 "counters": [
33 {
34 "unknown": true,
35 "value": false,
36 "count": 3,
37 }]
38 }
39 }
40 }
41}

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:

FieldMeaning
defaultThe default value the SDK received for the feature (sampled at some point during the interval).
startDateThe timestamp of the first feature flag evaluation included in this packet. This value is a Unix Epoch time in milliseconds.
endDateThe timestamp of the last feature flag evaluation included in this packet. This value is a Unix Epoch time in milliseconds.
countersA set of counters as described below.

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

FieldMeaning
versionThe version of the feature flag evaluated.
valueThe value returned by the SDK for the flag evaluation.
variationA zero-based index into the list of variations. If this field not provided, a default value was used.
countThe 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.

Diagnostic events

Diagnostic events are optional events which report on the SDK's configuration and operational behavior. These events are strictly for use by LaunchDarkly's internal teams and are intended to provide insights into how SDKs are operating in the field. Diagnostic events are not used for any customer-facing features nor is the resulting data accessible to customers. Support for diagnostic events varies across LaunchDarkly's SDKs.

Diagnostic events have two variants:

  1. An initial diagnostic event is sent upon SDK initialization. This event summarizes high-level information about the underlying platform such as the operating system name and version and the platform version name and version (e.g. Java 1.8 or Python 3.5). Additionally, this event includes information about how the SDK is configured.
  2. A diagnostic event is sent periodically throughout the SDK's lifetime. By default this event is sent every 15 minutes. This event describes the SDK's operational behavior in the time lapsed since the prior diagnostic event's submission. For example, the event includes information about the SDK's attempts to establish a streaming connection to LaunchDarkly and a counter of how many events were most recently sent to LaunchDarkly.

The technical details of how to implement diagnostic events in a LaunchDarkly SDK are beyond the scope of this document. It is not expected that a third-party would implement diagnostic events when building a new SDK from scratch.

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 by 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://sdk.launchdarkly.com/, which when appended to /api, yields https://sdk.launchdarkly.com//api. SDKs should deal with this by trimming trailing slashes off of custom base URL parameters.

More resources

Here are some examples from other LaunchDarkly SDK implementations:

The LaunchDarkly REST API documentation may be helpful as well.