elixir-toniq / vapor

Runtime configuration system for Elixir
MIT License
592 stars 36 forks source link
configuration elixir

Vapor

Elixir CI Module Version Hex Docs Total Download License Last Updated

Loads dynamic configuration at runtime.

Sponsors

This project is sponsored by these fine folks:

smartlogic

If you're interested in sponsoring you can do so here: https://github.com/sponsors/keathley

Why Vapor?

Dynamically configuring elixir apps can be hard. There are major differences between configuring applications with mix and configuring applications in a release. Vapor wants to make all of that easy by providing an alternative to mix config for runtime configs. Specifically Vapor can:

Example

defmodule VaporExample.Application do
  use Application
  alias Vapor.Provider.{File, Env}

  def start(_type, _args) do
    providers = [
      %Env{bindings: [db_url: "DB_URL", db_name: "DB_NAME", port: "PORT"]},
      %File{path: "config.toml", bindings: [kafka_brokers: "kafka.brokers"]},
    ]

    # If values could not be found we raise an exception and halt the boot
    # process
    config = Vapor.load!(providers)

    children = [
       {VaporExampleWeb.Endpoint, port: config.port},
       {VaporExample.Repo, [db_url: config.db_url, db_name: config.db_name]},
       {VaporExample.Kafka, brokers: config.kafka_brokers},
    ]

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

Precedence

Vapor merges the configuration based on the order that the providers are specified.

providers = [
  %Dotenv{},
  %File{path: "$HOME/.vapor/config.json", bindings: []},
  %Env{bindings: []},
]

Env will have the highest precedence, followed by File, and finally Dotenv.

Reading config files

Config files can be read from a number of different file types including JSON, TOML, and YAML. Vapor determines which file format to use based on the file extension.

Options on bindings

Bindings for %Env{} and %File{} providers support a number of options:

providers = [
  %Env{
    bindings: [
      {:db_name, "DB_NAME"},
      {:db_port, "DB_PORT", default: 4369, map: &String.to_integer/1},
    ]
  }
]

Adding configuration plans to modules

Vapor provides a Vapor.Plan behaviour. This allows modules to describe a provider or set of providers.

defmodule VaporExample.Kafka do
  @behaviour Vapor.Plan

  @impl Vapor.Plan
  def config_plan do
    %Vapor.Provider.Env{
      bindings: [
        {:brokers, "KAFKA_BROKERS"},
        {:group_id, "KAFKA_CONSUMER_GROUP_ID"},
      ]
    }
  end
end

config = Vapor.load!(VaporExample.Kafka)

Planner DSL

While using the structs directly is a perfectly reasonable option, it can often be verbose. Vapor provides a DSL for specifying configuration plans using less lines of code.

defmodule VaporExample.Config do
  use Vapor.Planner

  dotenv()

  config :db, env([
    {:url, "DB_URL"},
    {:name, "DB_NAME"},
    {:pool_size, "DB_POOL_SIZE", default: 10, map: &String.to_integer/1},
  ])

  config :web, env([
    {:port, "PORT", map: &String.to_integer/1},
  ])

  config :kafka, VaporExample.Kafka
end

defmodule VaporExample.Application do
  use Application

  def start(_type, _args) do
    config = Vapor.load!(VaporExample.Config)

    children = [
       {VaporExampleWeb.Endpoint, config.web},
       {VaporExample.Repo, config.db},
       {VaporExample.Kafka, config.kafka},
    ]

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

Custom Providers

There are several built in providers

If you need to create a new provider you can do so with the included Vapor.Provider protocol.

defmodule MyApp.DatabaseProvider do
  defstruct [id: nil]

  defimpl Vapor.Provider do
    def load(db_provider) do
    end
  end
end

Why does this exist?

While its possible to use Elixir's release configuration for some use cases, release configuration has some issues:

Vapor is designed to solve these problems.

Installing

Add vapor to your mix dependencies:

def deps do
  [
    {:vapor, "~> 0.10"},
  ]
end

Resources from the community

Configuring your Elixir Application at Runtime with Vapor

Copyright and License

Copyright (c) 2020 Christopher Keathley

This library is released under the MIT License. See the LICENSE.md file.