Adapter

Hex.pmBuild StatusCoverage StatusHex.pm

Fast adapters with clear syntax and build-in safety.

Why use Adapter?

In addition to these basic qualities it is:

Quick Setup

def deps do
  [
    {:adapter, "~> 1.0"}
  ]
end
defmodule SessionRepo do
  use Adapter

  # Define the adapter behavior
  behaviour do
    @doc ~S"""
    Lookup a sessions based on token.
    """
    @callback get(token :: binary) :: {:ok, Session.t | nil} | {:error, atom}
  end

  # Add module functions outside the behaviour definition
  # These can use the behaviour's callbacks like they exist as functions.
  @spec get!(binary) :: Session.t | nil | no_return
  def get!(token) do
    case get(token) do
      {:ok, result} -> result
      {:error, reason} -> raise "SessionRepo: #{reason}"
    end
  end
end

# PostgreSQL implementation
defmodule SessionRepo.PostgreSQL do
  @behaviour SessionRepo

  @impl SessionRepo
  def get(token), do: ...
end

# Redis implementation
defmodule SessionRepo.Redis do
  @behaviour SessionRepo

  @impl SessionRepo
  def get(token), do: ...
end

# Now configure
SessionRepo.configure(SessionRepo.PostgreSQL)

# Runtime switching possible
SessionRepo.configure(SessionRepo.Redis)

Configuration

Adapters come with the following configuration options:

When using :compile_env or :get_env the implementation will default to using :adapter as app and the module name as key when doing configuration lookups.

To define a custom config location pass app: :my_app, key: :my_repo.

Guide

First define the module that can use different adapters. The behavior of the adapter is defined with @callback like normal, but this time wrapped in a behavio[u]r macro.

defmodule SessionRepo do
  use Adapter

  behaviour do
    @doc ~S"""
    Lookup a sessions based on token.
    """
    @callback get(token :: binary) :: {:ok, Session.t | nil} | {:error, atom}
  end

  @spec get!(binary) :: Session.t | nil | no_return
  def get!(token) do
    case get(token) do
      {:ok, result} -> result
      {:error, reason} -> raise "SessionRepo: #{reason}"
    end
  end
end

How does it work?

The functionality is quite simple.

Inside the behavio[u]r block each @callback is tracked and documentation and spec recorded.

After the behavio[u]r block each recorded callback will generate a stub. Each stub will be given the recorded documentation and spec. This allows functions in the module to call the functions from the defined behavio[u]r.

On configuration either the application config updated to reflect the change or in :compile mode the module is purged and recompiled with the new adapter.

Roadmap

To 1.0.0

Changelog

1.0.0-rc0 (2020-11-02)

Initial release.

License

MIT License

Copyright (c) 2020 Ian Luites

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.