# SplitClient
Elixir client to use for Split.io feature flag service https://www.split.io/
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `split_client` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:split_client, "~> 0.5.1"}
]
end
```
## Configuration
### Evaluator Configuration
The [Split.io Evaluator](https://help.split.io/hc/en-us/articles/360020037072-Split-Evaluator) sits in your infrastructure between the Split.io servers.
It provides a REST API for accessing Split.io data and is meant to be used in cases where Split.io has not created an SDK for a specific language like Elixir.
In order for your application to access Split.io feature flag data you'll need to configure
how to access your instance of [Split.io Evaluator](https://help.split.io/hc/en-us/articles/360020037072-Split-Evaluator). You'll need to configure the `evaluator_auth_token` and the
`evaluator_url`.
In production you'll need to have your config access these environment variables. You could
do it like this in `config/prod.exs`:
```elixir
config :split_client, evaluator_auth_token: System.fetch_env!("SPLIT_EVALUATOR_AUTH_TOKEN")
config :split_client, evaluator_url: System.fetch_env!("SPLIT_EVALUATOR_URL")
```
### Local Feature Development
When you're developing features locally you don't want to toggle split treatments
in the Split.io web interface. The web interface changes things globally across
your infrastructure and isn't meant for testing changes on your local machine.
Instead you'll make changes in a local YAML file. Add this to your `config/dev.exs`
```elixir
config :split_client, data_source: SplitClient.DataSourceYAML
```
By default the `SplitClient` will expect this YAML file to live in the root of
your project and be called `split.yml`. You can change this in `config/dev.exs` with
```elixir
config :split_client, split_file: "my/file/path.yml"
```
The YAML file should follow a format similar to this:
```yml
# - feature_name:
# treatment: "treatment_applied_to_this_entry"
# keys: ["single_key_or_list", "other_key_same_treatment"]
# traffic_type: "customer"
# config: "{\"desc\" : \"this applies only to ON treatment\"}"
#
# Note that the "treatment" key is mandatory, but the "keys", "traffic_type" and
# "config" are optional. If "keys" are omitted they will be a fallback for when a passed key
# doesn't match
# Note that the config is JSON
# Note that `keys` can be a single key or a list of keys
# Note the attributes and bucketing_keys are ignored when using YAML stub
- my_feature:
treatment: "on"
keys: "mock_user_id"
traffic_type: "customer"
config: "{\"desc\" : \"this applies only to ON treatment\"}"
- some_other_feature:
treatment: "off"
traffic_type: "customer"
- my_feature:
treatment: "off"
traffic_type: "customer"
```
### Caching and Treatment Updates
Out of the box the SplitClient will automatically cache treatments with a 1 minute expiration.
You can change this by setting the `:cache_ttl` to a number of milliseconds of your choosing
```elixir
config :split_client, :cache_ttl, :timer.seconds(1)
```
### Test Configuration
Normally you want your application to share a global cache of Split.io
data. This isn't the case when you are running tests. In the testing
environment you want relevant tests to have their own isolated set of
Split data so that each test can be run concurrently. To ensure this
you can swap out the regular Treatments server for a Sandbox in
`config/test.exs`
```elixir
config :split_client, :treatments_server, SplitClient.Sandbox
```
## Split.io Glossary
In Split.io nomenclature a "Split" is equivalent to a "Feature Flag"
### Function Arguments
* `key` or `matching_key` - Some kind of unique identifier to identify the user, customer, account, etc.
A non-match results in default/control treatment
* `split_name` - The name of the Split to get the treatment for (e.g. `"shiny_feature"`)
* `traffic_type` - The Split.io Traffic Type the splits are associated with (e.g. `"user"`, `"customer"`, `"account"`, etc)
### Function Options
* `:attributes` - A `Map` of custom attributes to target on
(e.g. `%{plan_type: "premium", paying_customer: true}`)
See also: [Target Customer Attributes](https://help.split.io/hc/en-us/articles/360020793231-Target-with-custom-attributes)
* `:bucketing_key` - Which part of the distribution to look for a treatment out of 100% (e.g. `"20-39"`)
See also: [Treatment Order](https://help.split.io/hc/en-us/articles/360030117011-Why-setting-the-order-of-treatments-matters)
## Testing
Normally you want your application to share a global cache of Split.io
data. This isn't the case when you are running tests. In the testing
environment you want relevant tests to have their own isolated set of
Split data so that each test can be run concurrently. A little bit of setup in
relevant test files will be required.
```elixir
import SplitClient.Testing
setup :setup_split_client
```
First, you'll want to import the `SplitClient.Testing` module and execute `setup_split_client/1` before
every test that uses Split. This will give the test its own Split environment that won't be affected by any
other tests running in parallel.
Second, you'll want to create your Split definitions. This can be the same for all tests in the file
or vary per test. These definitions follow the same rules as the ones in
[Local Feature Development](README.md#local-feature-development)
```elixir
setup do
create_splits([
%{
"my_feature" => %{
treatment: "on",
keys: "mock_user_id",
traffic_type: "customer"
}
},
%{
"my_feature" => %{
treatment: "off",
traffic_type: "customer"
}
}
])
:ok
end
```
`create_splits/1` takes list of maps. Each map should have
one key for the split_name which has as its value a map
with the treatment information.
### YAML File Default
As mentioned in [Local Feature Development](README.md#local-feature-development) the `SplitClient`
uses a yaml file for local development. The `SplitClient.Sandbox` also expects a yaml file to exist
and uses it for default evaluation when a test or process hasn't been been configured with its own
isolated Split data.
Tests that are setting up their own sandbox will still use the yaml file as the default rules.
Calls to `create_splits/1` will override the yaml file for each Split specified in the arguments.
By default the `SplitClient.Sandbox` will expect the yaml file to live in the root of
your project and be called `split.yml`. You can change this in `config/test.exs` with
```elixir
config :split_client, split_file: "my/file/path.yml"
```
### Multi-process Collaboration
#### Explicit Allowance
You may have a process in your application that is using Split.io data
that is outside of the test process. In this case you'll need to
explicitally associate the test process with the outside process so that
the isolated Split data can be accessed. Otherwise it will default to your
yaml file. You can use `SplitClient.Sandbox.allow/2` for this.
```elixir
test "test a thing" do
task =
Task.async(fn ->
assert feature_a("mock_user_id") == "New Hotness"
end)
Sandbox.allow(self(), task.pid)
Task.await(task)
end
```
#### Async False
Sometimes there are unfortunate circumstances where you do not own the process
accessing your Split.io data or the process is otherwise unnamed and unaccessible.
In these circumstances you'll be relegated to making your test `async: false` to
ensure that the test is predictable and only accesses Split data that it's supposed to
## Contributing
If you are making improvements to this package there will be some setup involved.
### Accessing a Split.io Evaluator Instance
You'll need to add environment variables of `SPLIT_EVALUATOR_AUTH_TOKEN` and `SPLIT_EVALUATOR_URL` if you would like to test against a real Evaluator instance.
You'll also need to make the Evaluator your datasource in the dev environment.
Update to `config/dev.exs` to include:
```elixir
config :split_client, :data_source, SplitClient.Boundary.Evaluator
```
### Documentation
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/split_client](https://hexdocs.pm/split_client).