Skip to content

Fast adapters with clear syntax and build-in safety.

License

Notifications You must be signed in to change notification settings

IanLuites/adapter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Adapter

Hex.pm Build Status Coverage Status Hex.pm

Fast adapters with clear syntax and build-in safety.

Why use Adapter?

  • Fast (as fast as hardcoded defdelegates)
  • Easy (define a behaviour and it takes care of everything else)
  • Safe (will error if the implementation does not match the behaviour)
  • Clean (clearly separated marked behaviour/delegate versus functions)
  • Flexible (change implementation/adapter at runtime)

In addition to these basic qualities it is:

  • Compatible and tested with releases (distillery, mix release)
  • Documentation compatible (each stub copies the documentation of the @callback)
  • Spec / Dialyzer (each stub has a spec matching the @callback)
  • IDE (intelligent code completion / intellisense) compatible [stubs]

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:

  • :default (none), a default implementation to link to at first compile.

  • :error (:module_name_not_configured), an atom that is returned in an error tuple when the adapter has not been configured. Can be set to :raise to raise instead of returning an error tuple.

  • :log (:info), the log level of the configuration message. The following levels are allowed: :debug, :info, and :notice. To disable logging of configuration set log: false.

  • :mode (:compile), determines the implementation type of the adapter pattern. The following modes are supported:

    • :compile, the stubs are replaced each time by recompiling the module. This gives hardcoded performance while still allowing changes of adapter at runtime.
    • :compile_env, the macro hardcodes the adapter at compile time. This works by using Application.compile_env. (or Application.get_env below Elixir 1.11.) It mirrors the standard adapter pattern using module attributes and defdelegates. It is fast, but requires the adapter to be set at compile and can no longer be changed at runtime like startup.
    • :get_env, the macro generates a Application.get_env pattern. Looking up the set adapter for each call, allowing for easy runtime switching. This is slower in use than a :compile and :compile_env, but the fastest to re-configure and simpler than :compile when it comes to the underlying mechanic.
  • :random (true), wraps the default implementation in an Enum.random([...]) to trick dialyzer. Dialyzer might error out, because it detects the hardcoded [error] values before the adapter is configured. To avoid this causing issues the hardcoded value is wrapped in random. This forces dialyzer to respect the spec instead of the implementation.

  • :validate (true), whether to perform configuration validation. This will verify a given implementation actually implements the complete behaviour. It will error out and refuse to configure if there are functions missing or have a wrong arity. Setting this to false will skip validation, making configuration slightly faster and allow setting incomplete implementations.

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

  • Improve documentation
  • Travis builds

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.

About

Fast adapters with clear syntax and build-in safety.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages