lib/fly_multi_region.ex

defmodule FlyMultiRegion do
  @moduledoc ~S"""
  Documentation for `FlyMultiRegion`.

  For setting up read-replica databases refer to [Fly.io's documentation](https://fly.io/docs/getting-started/multi-region-databases/)

  ## Usage

  ### Installation

  The package can be installed by adding `fly_multi_region` to your list of dependencies in `mix.exs`:

  ```elixir
  def deps do
    [
      {:fly_multi_region, "~> 0.0.1"}
    ]
  end
  ```

  ### Configuration

  If you're following the Fly.io guides then your database config will happen at runtime. Add the following to the `runtime.exs` file:

  ```elixir
  config :fly_multi_region,
    region: System.get_env("FLY_REGION"),
    regions: ["nrt", "ord"],
    url: System.get_env("DATABASE_URL"),
    opts: [
      socket_options: [:inet6],
      pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
    ]
  ```

  Parameters:

  - region: generated by Fly.io by default
  - regions: a list of replicas in your cluster
  - url: database url
  - opts: configuration options for Ecto

  ### Supervision

  Add FlyMultiRegion to your Application's supervision tree

  As mentioned in [Ecto's documentation for replica databases](https://hexdocs.pm/ecto/replicas-and-dynamic-repositories.html) it's necessary to add any Repos to your supervision tree. With that in mind, make sure to add `FlyMultiRegion` to your Application module (application.ex)

  ```elixir
  def start(_type, _args) do
      children =
        [
          ...,
          FlyMultiRegion
        ]

      ...
      Supervisor.start_link(children, opts)
    end
  ```

  ### Repo

  Add the following to your projects main Repo module:

  ```elixir
    use FlyMultiRegion.Repo
  ```
  """
  use Supervisor

  alias __MODULE__.Repo

  @doc false
  def start_link(opts) do
    Supervisor.start_link(__MODULE__, :ok, opts)
  end

  @impl true
  def init(_opts) do
    Supervisor.init(replicas(), strategy: :one_for_one)
  end

  @doc false
  defp replicas do
    Enum.map(regions(), fn region ->
      {_, module, _, _} = Repo.new(region)
      {module, region: region}
    end)
  end

  @doc false
  def regions, do: Application.get_env(:fly_multi_region, :regions) || []

  @doc """
  Returns replica Repo module name
  """
  def repo_module(region) do
    Module.concat(FlyMultiRegion.Repo, String.capitalize(region))
  end
end