diff --git a/README.md b/README.md index d39d873..917aa07 100644 --- a/README.md +++ b/README.md @@ -27,20 +27,22 @@ Then you can start the Elixir Split Thin Client, either in your supervision tree ```elixir children = [ - {Split.Sockets.Supervisor, opts} + {Split, opts} ] ``` Or by starting it manually: ```elixir -Split.Sockets.Supervisor.start_link(opts) +Split.Supervisor.start_link(opts) ``` Where `opts` is a keyword list with the following options: -- `:socket_path` - The path to the splitd socket file. For example `/var/run/splitd.sock`. -- `:fallback_enabled` - A boolean that indicates wether we should return errors when RPC communication fails or falling back to a default value . Default is `false`. +- `:socket_path`: **REQUIRED** The path to the splitd socket file. For example `/var/run/splitd.sock`. +- `:fallback_enabled`: **OPTIONAL** A boolean that indicates wether we should return errors when RPC communication fails or falling back to a default value . Default is `false`. +- `:pool_size`: **OPTIONAL** The size of the pool of connections to the splitd daemon. Default is the number of online schedulers in the Erlang VM (See: https://www.erlang.org/doc/apps/erts/erl_cmd.html). +- `:connect_timeout`: **OPTIONAL** The timeout in milliseconds to connect to the splitd daemon. Default is `1000`. Once you have started Split, you are ready to start interacting with the Split.io splitd's daemon. diff --git a/lib/split.ex b/lib/split.ex index 4a430bb..28ae9ad 100644 --- a/lib/split.ex +++ b/lib/split.ex @@ -1,6 +1,54 @@ defmodule Split do @moduledoc """ - Documentation for `Split`. + The Split.io Elixir thin client. + + This module provides a simple API to interact with the Split.io service + via the [Split Daemon (splitd)](https://help.split.io/hc/en-us/articles/18305269686157-Split-Daemon-splitd). + + ## Adding Split to Your Supervision Tree + + The most basic approach is to add `Split` as a child of your application's + top-most supervisor, i.e. `lib/my_app/application.ex`. + + ```elixir + defmodule MyApp.Application do + use Application + + def start(_type, _args) do + children = [ + # ... other children ... + {Split, [socket_path: "/var/run/split.sock", fallback_enabled: true]} + ] + + opts = [strategy: :one_for_one, name: MyApp.Supervisor] + Supervisor.start_link(children, opts) + end + end + ``` + + You can also start `Split` dynamically by calling `Split.Supervisor.start_link/1`: + + ```elixir + Split.Supervisor.start_link(opts) + ``` + + ### Options + + `Split` takes a number of keyword arguments as options when starting. The following options are available: + + - `:socket_path`: **REQUIRED** The path to the splitd socket file. For example `/var/run/splitd.sock`. + - `:fallback_enabled`: **OPTIONAL** A boolean that indicates wether we should return errors when RPC communication fails or falling back to a default value . Default is `false`. + - `:pool_size`: **OPTIONAL** The size of the pool of connections to the splitd daemon. Default is the number of online schedulers in the Erlang VM (See: https://www.erlang.org/doc/apps/erts/erl_cmd.html). + - `:connect_timeout`: **OPTIONAL** The timeout in milliseconds to connect to the splitd daemon. Default is `1000`. + + + ## Using the API + + Once you have started Split, you are ready to start interacting with the Split.io splitd's daemon to access feature flags and configurations. + + ```elixir + Split.get_treatment("user_key", "feature_name") + ``` """ alias Split.Telemetry alias Split.Sockets.Pool @@ -19,6 +67,15 @@ defmodule Split do flag_sets: [String.t()] } + @typedoc "An option that can be provided when starting `Split`." + @type option :: + {:socket_path, String.t()} + | {:fallback_enabled, boolean()} + | {:pool_size, non_neg_integer()} + | {:connect_timeout, non_neg_integer()} + + @type options :: [option()] + defstruct [ :name, :traffic_type, @@ -30,6 +87,16 @@ defmodule Split do :flag_sets ] + @doc """ + Builds a child specification to use in a Supervisor. + + Normally not called directly by your code. Instead, it will be + called by your application's Supervisor once you add `Split` + to its supervision tree. + """ + @spec child_spec(options()) :: Supervisor.child_spec() + defdelegate child_spec(options), to: Split.Supervisor + @spec get_treatment(String.t(), String.t(), String.t() | nil, map() | nil) :: {:ok, Treatment.t()} | {:error, term()} def get_treatment(user_key, feature_name, bucketing_key \\ nil, attributes \\ %{}) do diff --git a/lib/rpc/encoder.ex b/lib/split/rpc/encoder.ex similarity index 100% rename from lib/rpc/encoder.ex rename to lib/split/rpc/encoder.ex diff --git a/lib/rpc/fallback.ex b/lib/split/rpc/fallback.ex similarity index 100% rename from lib/rpc/fallback.ex rename to lib/split/rpc/fallback.ex diff --git a/lib/rpc/message.ex b/lib/split/rpc/message.ex similarity index 100% rename from lib/rpc/message.ex rename to lib/split/rpc/message.ex diff --git a/lib/rpc/opcodes.ex b/lib/split/rpc/opcodes.ex similarity index 100% rename from lib/rpc/opcodes.ex rename to lib/split/rpc/opcodes.ex diff --git a/lib/rpc/response_parser.ex b/lib/split/rpc/response_parser.ex similarity index 100% rename from lib/rpc/response_parser.ex rename to lib/split/rpc/response_parser.ex diff --git a/lib/sockets/conn.ex b/lib/split/sockets/conn.ex similarity index 95% rename from lib/sockets/conn.ex rename to lib/split/sockets/conn.ex index 0473167..0d47da5 100644 --- a/lib/sockets/conn.ex +++ b/lib/split/sockets/conn.ex @@ -10,7 +10,7 @@ defmodule Split.Sockets.Conn do @type t :: %__MODULE__{ socket: port() | nil, socket_path: String.t(), - opts: map() + opts: keyword() } defstruct [ @@ -30,8 +30,8 @@ defmodule Split.Sockets.Conn do @default_connect_timeout 1000 @default_rcv_timeout 1000 - @spec new(String.t(), map()) :: t - def new(socket_path, opts \\ %{}) do + @spec new(String.t(), keyword()) :: t + def new(socket_path, opts \\ []) do %__MODULE__{ socket: nil, socket_path: socket_path, @@ -41,7 +41,7 @@ defmodule Split.Sockets.Conn do @spec connect(t) :: {:ok, t()} | {:error, t(), term()} def connect(%__MODULE__{socket: nil, socket_path: socket_path, opts: opts} = conn) do - connect_timeout = Map.get(conn.opts, :connect_timeout, @default_connect_timeout) + connect_timeout = Keyword.get(opts, :connect_timeout, @default_connect_timeout) Telemetry.span(:connect, %{socket_path: socket_path, pool_name: opts[:pool_name]}, fn -> case :gen_tcp.connect({:local, socket_path}, 0, @connect_opts, connect_timeout) do diff --git a/lib/sockets/pool.ex b/lib/split/sockets/pool.ex similarity index 88% rename from lib/sockets/pool.ex rename to lib/split/sockets/pool.ex index 41b08b6..a3bb3c8 100644 --- a/lib/sockets/pool.ex +++ b/lib/split/sockets/pool.ex @@ -17,17 +17,17 @@ defmodule Split.Sockets.Pool do end def start_link(opts) do - fallback_enabled = Map.get(opts, :fallback_enabled, false) + fallback_enabled = Keyword.get(opts, :fallback_enabled, false) :persistent_term.put(:splitd_fallback_enabled, fallback_enabled) - pool_name = Map.get(opts, :pool_name, __MODULE__) - pool_size = Map.get(opts, :pool_size, System.schedulers_online()) + pool_name = Keyword.get(opts, :pool_name, __MODULE__) + pool_size = Keyword.get(opts, :pool_size, System.schedulers_online()) opts = opts - |> Map.put_new(:fallback_enabled, fallback_enabled) - |> Map.put_new(:pool_size, pool_size) - |> Map.put_new(:pool_name, pool_name) + |> Keyword.put_new(:fallback_enabled, fallback_enabled) + |> Keyword.put_new(:pool_size, pool_size) + |> Keyword.put_new(:pool_name, pool_name) NimblePool.start_link( worker: {__MODULE__, opts}, @@ -101,7 +101,9 @@ defmodule Split.Sockets.Pool do end @impl NimblePool - def init_pool(%{socket_path: socket_path} = opts) do + def init_pool(opts) do + socket_path = Keyword.get(opts, :socket_path) + unless File.exists?(socket_path) do Logger.error(""" The Split Daemon (splitd) socket was not found at #{socket_path}. @@ -116,8 +118,8 @@ defmodule Split.Sockets.Pool do end @impl NimblePool - def init_worker({%{socket_path: socket_path} = opts, _metrics_ref} = pool_state) do - {:ok, Conn.new(socket_path, opts), pool_state} + def init_worker({opts, _metrics_ref} = pool_state) do + {:ok, Conn.new(Keyword.get(opts, :socket_path), opts), pool_state} end @impl NimblePool diff --git a/lib/sockets/pool_metrics.ex b/lib/split/sockets/pool_metrics.ex similarity index 100% rename from lib/sockets/pool_metrics.ex rename to lib/split/sockets/pool_metrics.ex diff --git a/lib/sockets/supervisor.ex b/lib/split/supervisor.ex similarity index 71% rename from lib/sockets/supervisor.ex rename to lib/split/supervisor.ex index bcdd56f..9dc20b4 100644 --- a/lib/sockets/supervisor.ex +++ b/lib/split/supervisor.ex @@ -1,4 +1,4 @@ -defmodule Split.Sockets.Supervisor do +defmodule Split.Supervisor do use GenServer alias Split.Sockets.Pool @@ -7,6 +7,7 @@ defmodule Split.Sockets.Supervisor do {:ok, init_arg} end + @spec start_link(keyword()) :: Supervisor.on_start() def start_link(opts) do child = {Pool, opts} Supervisor.start_link([child], strategy: :one_for_one) diff --git a/lib/telemetry.ex b/lib/split/telemetry.ex similarity index 100% rename from lib/telemetry.ex rename to lib/split/telemetry.ex diff --git a/test/sockets/pool_test.exs b/test/sockets/pool_test.exs index 1744862..f5182b3 100644 --- a/test/sockets/pool_test.exs +++ b/test/sockets/pool_test.exs @@ -2,7 +2,6 @@ defmodule Split.Sockets.PoolTest do use ExUnit.Case alias Split.RPC.Message - alias Split.Sockets.Supervisor alias Split.Sockets.Pool alias Split.Sockets.PoolMetrics @@ -18,9 +17,7 @@ defmodule Split.Sockets.PoolTest do Split.Test.MockSplitdServer.wait_until_listening(socket_path) - start_supervised!( - {Supervisor, %{socket_path: socket_path, pool_name: __MODULE__, pool_size: 10}} - ) + start_supervised!({Split, socket_path: socket_path, pool_name: __MODULE__, pool_size: 10}) :ok end diff --git a/test/split_test.exs b/test/split_test.exs index cf15a6b..4d6ebc9 100644 --- a/test/split_test.exs +++ b/test/split_test.exs @@ -2,7 +2,6 @@ defmodule SplitThinElixirTest do use ExUnit.Case alias Split.Impression - alias Split.Sockets.Supervisor alias Split.Treatment setup_all context do @@ -15,7 +14,7 @@ defmodule SplitThinElixirTest do Split.Test.MockSplitdServer.wait_until_listening(socket_path) - start_supervised!({Supervisor, %{socket_path: socket_path}}) + start_supervised!({Split, socket_path: socket_path}) :ok end