Code Monkey home page Code Monkey logo

configcat / elixir-sdk Goto Github PK

View Code? Open in Web Editor NEW
20.0 6.0 4.0 561 KB

ConfigCat SDK for Elixir. ConfigCat is a hosted feature flag service: https://configcat.com. Manage feature toggles across frontend, backend, mobile, desktop apps. Alternative to LaunchDarkly. Management app + feature flag SDKs.

Home Page: https://configcat.com/docs/sdk-reference/elixir

License: MIT License

Elixir 99.93% Euphoria 0.07%
feature-flags feature-toggles configcat elixir-sdk elixir feature-management sdk featureflags remote-config

elixir-sdk's Introduction

ConfigCat SDK for Elixir

https://configcat.com

ConfigCat SDK for Elixir provides easy integration for your application to ConfigCat.

ConfigCat is a feature flag and configuration management service that lets you separate releases from deployments. You can turn your features ON/OFF using ConfigCat Dashboard even after they are deployed. ConfigCat lets you target specific groups of users based on region, email or any other custom user attribute.

ConfigCat is a hosted feature flag service. Manage feature toggles across frontend, backend, mobile, desktop apps. Alternative to LaunchDarkly. Management app + feature flag SDKs.

Elixir CI Coverage Status Hex.pm HexDocs.pm Hex.pm Hex.pm Last Updated

Getting Started

1. Add configcat to your list of dependencies in mix.exs:

def deps do
  [
    {:configcat, "~> 4.0.2"}
  ]
end

2. Go to the ConfigCat Dashboard to get your SDK Key:

SDK-KEY

3. Add ConfigCat to your application Supervisor tree:

def start(_type, _args) do
  children = [
    {ConfigCat, [sdk_key: "YOUR SDK KEY"]},
    MyApp
  ]

  opts = [strategy: :one_for_one, name: MyApp.Supervisor]
  Supervisor.start_link(children, opts)
end

4. Get your setting value:

isMyAwesomeFeatureEnabled = ConfigCat.get_value("isMyAwesomeFeatureEnabled", false)
if isMyAwesomeFeatureEnabled do
  do_the_new_thing()
else
  do_the_old_thing()
end

Getting user specific setting values with Targeting

Using this feature, you will be able to get different setting values for different users in your application by passing a ConfigCat.User object to the ConfigCat.get_value/3 function.

Read more about Targeting here.

user = ConfigCat.User.new("#USER-IDENTIFIER#")

isMyAwesomeFeatureEnabled = ConfigCat.get_value("isMyAwesomeFeatureEnabled", false, user)
if isMyAwesomeFeatureEnabled do
    do_the_new_thing()
else
    do_the_old_thing()
end

Sample/Demo apps

Polling Modes

The ConfigCat SDK supports 3 different polling mechanisms to acquire the setting values from ConfigCat. After latest setting values are downloaded, they are stored in the internal cache then all requests are served from there. Read more about Polling Modes and how to use them at ConfigCat Docs.

Need help?

https://configcat.com/support

Contributing

Contributions are welcome. For more info please read the Contribution Guideline.

About ConfigCat

elixir-sdk's People

Contributors

ischolten avatar kianmeng avatar kp-cat avatar laliconfigcat avatar randycoulman avatar z4kn4fein 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  avatar  avatar  avatar  avatar

elixir-sdk's Issues

Make it more convenient to use multiple ConfigCat instances

It is currently possible to run multiple instances of the ConfigCat application by providing a different name option for each instance, then providing that name in all of the public API calls as ConfigCat.get_value(key, default, user, client: <name>).

We could make this cleaner by following the approach in the "Improving the User Experience" section at the end of https://keathley.io/blog/reusable-libraries.html.

I think that all we'd need to do is define a __using__ block in ConfigCat that automatically supplies a name option of __MODULE__ and then defines versions of the public API functions that call the normal versions with the client option pre-specified.

This would allow us to add this convenience without changing the public API.

Timeouts in GenServer calls leak to the calling application

Describe the bug

Occasionally, a call to ConfigCat.get_value will time out. The timeout is not handled and therefore leaks to the calling application which can crash a process if the caller doesn't handle the timeout either.

Erlang error: {:timeout, {GenServer, :call, [ConfigCat.Client, {:get_value, "myFeatureFlag", false, nil}, 10000]}}

To reproduce

It is not easy to reproduce this, as it only happens occasionally.

Expected behavior

Any ConfigCat API call that has a default value should rescue the timeout error and return the default value rather than leaking the timeout back to the calling application. This includes get_value and get_variation_id.

It is less clear what should happen for calls like get_all_keys, get_all_variation_ids, and get_key_and_value, but probably returning empty lists for the first two and nil for the last would be best.

force_refresh should return {:error, :timeout} or similar.

My intention when I implemented this library is that get_value and similar calls shouldn't block on HTTP requests which is what seems to be happening here, so it would also be worth figuring out how these timeouts are happening in the first place to see if we can fix the root cause. I'll look into that as well.

Screenshots

If applicable, add screenshots to help explain your problem.

Support get_variation_id

There is an other get function called get_variation_id, which main purpose is to provide a variation_id which can be sent e.g. to an analytics provider. This feature is currently behind a feature flag in ConfigCat, but it will be turned on soon. You will be able to get the variation_id from the dasboard for a rule/percentage item/default value and match it with your analytics tool.

Originally posted by @laliconfigcat in #2 (comment)

Call on_change callback asynchronously

When the user provides an on_change callback, we call it directly from the auto-polling process. If the user-provided function is expensive or long-running, it will block our polling loop.

To avoid this issue, we should call the function asynchronously instead.

Add documentation

Add full documentation, including:

  • Fleshing out the README with more information about how to use the library and add it to a supervision tree
  • Documentation for the ConfigCat website
  • Adding module documentation for publishing to hexdocs.pm

Upgrade httpoison dependency

Is your feature request related to a problem? Please describe.

Hi, I'm trying to install the configcat dependency to my Elixir project, but get the following error

Resolving Hex dependencies...
Resolution completed in 1.007s
Because your app depends on configcat ~> 4.0.0 which depends on httpoison ~> 1.7, httpoison ~> 1.7 is required.
So, because your app depends on httpoison ~> 2.1, version solving failed.
** (Mix) Hex dependency resolution failed

Describe the solution you'd like

Is there a way we can make the dependency more flexible? for example {:httpoison, "~> 1.7 or ~> 2.0"}

Describe alternatives you've considered

Forking this project?!

Read feature flag values from an ETS table instead from a GenServer

Is your feature request related to a problem? Please describe.

Currently reading feature flag values always happens through a GenServer.call/3. From my understanding for a given SDK key it's a single process handles all read requests. This creates a natural bottleneck.

If for example a feature flag is accessed in a hot code path, potentially from thousands of processes every single second, then this will invariably overwhelm the process that handles the feature flag requests, slowing down the system as a whole.

Describe the solution you'd like

Instead relying on GenServer.call/3 to fetch feature flag values I suggest to use an ets table. In a nutshell an ets table can be created on start - e.g. by the ConfigCat.Client process - potentially with the read_concurrency option enabled (I'd assume that reading is more frequent than writing).

Then feature flags can be read from the ets table, eliminating the need for most message passing and as such the natural bottleneck.

Describe alternatives you've considered

/

Additional context

This approach combines neatly with the "auto" and "manual" polling modes. It does get a bit more complicated when considering the "lazy" mode.

A naive implementation of the "lazy" mode would check the ets table first and - if the value is missing - send a message to fetch the value. But in a high load scenario this can mean that thousands of messages pour in when a value expires.

To solve this problem I suggest what I call an "optimistic lazy" approach, not unlike the stale-while-revalidate HTTP Cache-Control behaviour. In addition to reading values from the ets table you'd keep track of the last access time for each key - potentially on another ets table this time with write_concurrency enabled.

The "optimistic lazy" cache refresh implementation would then look at which keys are about to expire and refresh those that were accessed very recently, whereas "very recently" would be a matter of configuration with a sensible default (e.g. 5 seconds).

Improve public interface

The idea is to change the current public interface from:

{:ok, client} = ConfigCat.start_link("sdk_key", fetch_policy: FetchPolicy.manual())
value = ConfigCat.get_value("any_feature", "default", client: client)

To:

{:ok, client} = ConfigCat.create_client("sdk_key", fetch_policy: FetchPolicy.manual())
value = client.get_value("any_feature", "default")

This change would make our public interface more similar to the ones we currently have on other SDKs.

Screenshot 2020-10-20 at 10 11 06

Previous discussion:
https://configcat-community.slack.com/archives/C01CN2YNWGK/p1603106388007500

Publish package and documentation

Publish the library to hex.pm and the documentation to hexdocs.pm.

We'll have previously set up CI for building and testing (in #6) and written the documentation (in #4), but once we're ready to officially release this SDK, we'll want to start publishing to hex.pm and hexdocs.pm.

Getting a GenServer timeout when refreshing config

Describe the bug

Using the auto policy, we are often getting a GenServer timeout from the FetchConfig genserver.

{:timeout, {GenServer, :call, [ConfigCat.ConfigFetcher, :fetch, 5000]}}

In CacheControlConfigFetcher, the handle_call for :fetch is making a request and using the HTTPoison library which has some timeouts that are larger than the GenServer.call/2 default time.

https://hexdocs.pm/elixir/GenServer.html#call/3

call(server, request, timeout \ 5000)

https://hexdocs.pm/httpoison/HTTPoison.Request.html

:timeout - timeout for establishing a TCP or SSL connection, in milliseconds. Default is 8000
:recv_timeout - timeout for receiving an HTTP response from the socket. Default is 5000

To reproduce

Run ConfigCat with the auto policy and let it run, this will periodically happen.

Expected behavior

The genserver allows enough time for the fetching of the updated config from ConfigCat or handles this error in some way.

Add Dialyzer & Typespecs

Many Elixir projects rely on Dialyzer typespecs. As a library, we should provide proper type specs for our clients to rely on. They also help document use of the library.

As I've been adding behaviours to the system, I've specified some typespecs. For now, I've hard-coded map() as the type of a config, but we will likely want to define a more explicit type for that and use it everywhere.

Publicize the library

Announce the existence of the library in various ways:

Maybe some or all of:

Having the SDK listed in the official docs will help a lot as well, but if we tell people about this SDK it might draw some more interest in ConfigCat as well.

Set up CI

Add a TravisCI configuration for automatically building and running tests against whichever Elixir versions we want to support.

Failed tests on Elixir 1.11.x

Describe the bug

13 tests are failed on Elixir 1.11.x. (All tests are working on Elixir 1.10.x)

To reproduce

  • Install Elixir 1.11.x dev environment
  • Install dependencies
    mix local.rebar --force
    mix local.hex --force
    mix deps.get
  • Run tests
    mix coveralls.json
  • 13 tests are failed:
    108 tests, 13 failures

Expected behavior

All tests are passed on all Elixir versions

SDK version

v1.0.1

Language/Framework version

Erlang/OTP 23 [erts-11.2] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe] [dtrace]
IEx 1.11.4 (compiled with Erlang/OTP 23)

Platform

Mac OS X 10.15.7

RFC: New architecture to support desired features

Background

In order to support a number of desired features that would make the SDK more extensible and closer in structure to the other language SDK's, I'm proposing a restructuring of the architecture as outlined below.

Here are the features we'd like to support:

  • #10 - Allow a custom cache implementation
  • #14 - Extract config logic to a ConfigFetcher module
  • #15 - Split cache policies into separate files (if possible)
  • #30 - Improve public interface

I've based this "desired future" largely on the current Ruby SDK as that's the one I'm most familiar with.

Current State

  • Almost all of the logic lives in the ConfigCat GenServer.

  • The state of the GenServer includes both client settings and the currently-cached config.

  • FetchPolicies are simple structs and the FetchPolicy module provides some helper functions that pattern match based on the type of the policy.

Something that's not clear from the tests is how this library would be used within an application, and I think this lack of clarity is what spawned #30.

Once we add a sample application (#5) and documentation (#4) this will become more clear, but here's a rough summary:

  • The current tests make it seem like we have to call ConfigCat.start_link every time we want to request a value; however, that's not the intended use for a client application.

  • Instead, a client application adds ConfigCat to its supervision tree as appropriate using a child spec such as {ConfigCat, [sdk_key()]}.

  • The client can then call ConfigCat.get_value(key, default, user) as appropriate.

  • By default, the public API in ConfigCat will talk to the running GenServer process using its default name (the ConfigCat module name).

  • It is possible to provide a different name for the GenServer (as is done in the tests), and to have the public API talk to a different GenServer via an optional client: pid argument. These extra arguments are there mostly for the tests, or for a more complicated use case where we want to use multiple different ConfigCat configurations in the same application. In normal circumstances, the client application doesn't need to provide those arguments.

Design Considerations

  • Since Elixir is not an object-oriented language like Ruby, we can't simply initialize a class with some instance variables that we can then call functions on.

  • The current ConfigCat module has too many responsibilities.

  • The current architecture does not easily support a pluggable cache implementation.

  • Other language clients split their cache policies into separate files/classes.

  • In Elixir, persistent state is normally managed by GenServers as are asynchronous periodic processes (like the auto-polling cache policy).

Proposal

  • Split ConfigFetcher out to its own (non-GenServer) module.

  • Define an Elixir behaviour for a ConfigCache.

  • Rename FetchPolicy to CachePolicy to match the other language SDKs; other changes as outlined below.

  • Divide the rest of the application into a Supervisor with a family of child GenServers:

    • A Client GenServer will manage any global application state (similar to the inst vars in the Ruby Client class). This state will include the configured cache policy (renamed from FetchPolicy) and anything else necessary.

    • A CachePolicy GenServer: one of CachePolicy.ManualPolling, CachePolicy.AutoPolling, or CachePolicy.LazyLoading. Whichever of these GenServers is running will keep a reference to the cache and config fetcher as well as its own settings. The AutoPolling GenServer will also manage the polling loop.

    • (optional) A Cache GenServer: we'll provide a Cache.InMemory GenServer as a default and add it to our supervision tree. This GenServer will manage the current configuration as its state. More on pluggable cache implementations below.

  • We might need to find a way to eliminate duplication between the CachePolicy GenServers.

  • The way the calling application specifies the cache policy might change; internally, we'll turn that specification into a child_spec that we can add to our supervision tree.

  • The calling application can provide a custom cache implementation as either cache_module: SomeModule or cache_spec: some_child_spec. In the former case, we'll call functions on that module; in the latter, we'll add the cache to our supervision tree (and then call functions on the module). This will allow callers to provide a cache (like Redis) that is already supervised by their application, or a simpler cache (like our InMemoryCache) that needs to be started and supervised.

  • The public API stays largely as it is today: the calling application adds ConfigCat to its supervision tree and then calls public API functions like ConfigCat.get_value(key, default, user). This goes against what #30 is asking for, but I'm hoping that the explanation above shows that this is already a very usable (and Elixir-like) API.

This is a more complex architecture than what we have today. However, it also divides up responsibilities into smaller, more easily-testable (and pluggable) pieces. It is also structured much more like the other language SDKs.

Roadmap

Here's how I think we can get to this new architecture incrementally:

  • Extract a ConfigFetcher module to manage the communication with the ConfigCat servers. (#40)

  • Add a Supervisor above the current ConfigCat GenServer. (#38)

  • Extract a ConfigCache behaviour and InMemory implementation of that GenServer.

  • Convert FetchPolicy into a family of CachePolicy GenServers.

Improve logging when there are multiple instances

When a calling application is using more than one instance of ConfigCat, there is not an easy way to distinguish the log messages between them.

It would be helpful if we included the custom name provided by the user as part of the log message. This might be a bit tricky, as the name is not necessarily available everywhere we call the logger.

It looks like this is also an issue with the Ruby implementation, so maybe it's not worth worrying about, but I thought I'd capture the issue anyway.

Can we help?

We (the e-commerce team at InfluxData) wrote a ConfigCat client for Elixir a few months ago. It's currently embedded within our one of our (closed-source) apps, but I was literally just about to create a new repo to extract and open source our work when I found this repo.

I don't see any code here yet, but we're wondering how far along you are with development. Can we combine forces to make a single, official Elixir client for ConfigCat?

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.