Multiverse

Deps StatusHex.pm DownloadsLatest VersionLicenseBuild StatusCoverage StatusEbert

This plug helps to manage multiple API versions based on request and response gateways. This is an awesome practice to hide your backward compatibility. It allows to have your code in a latest possible version, without duplicating controllers or models.

Compatibility Layers

Inspired by Stripe API. Read more at MOVE FAST, DON'T BREAK YOUR API or API versioning.

Goals

Adapters

Multiverse allows you to use a custom adapter which can, for eg.:

Default adapter works with ISO-8601 date from x-api-version header (configurable). For malformed versions it would log a warning and fallback to the current date.

Also, it allows to use channel name instead of date, where:

Channels allow you to plan version releases upfront and test them without affecting users, just set future date for a change and pass it explicitly or use edge channel to test latest application version.

Installation

The package (take look at hex.pm) can be installed as:

  1. Add multiverse to your list of dependencies in mix.exs:
  def deps do
    [{:multiverse, "~> 1.1.0"}]
  end
  1. Make sure that multiverse is available at runtime in your production:
  def application do
    [applications: [:multiverse]]
  end

How to use

  1. Insert this plug into your API pipeline (in your router.ex):
  pipeline :api do
    plug :accepts, ["json"]
    plug :put_secure_browser_headers

    plug Multiverse
  end
  1. Define module that handles change
  defmodule AccountTypeChange do
    @behaviour Multiverse.Change

    def handle_request(%Plug.Conn{} = conn) do
      # Mutate your request here
      IO.inspect "AccountTypeChange.handle_request applied to request"
      conn
    end

    def handle_response(%Plug.Conn{} = conn) do
      # Mutate your response here
      IO.inspect "AccountTypeChange.handle_response applied to response"
      conn
    end
  end
  1. Enable the change:
  pipeline :api do
    plug :accepts, ["json"]
    plug :put_secure_browser_headers

    plug Multiverse, gates: %{
      ~D[2016-07-21] => [AccountTypeChange]
    }
  end
  1. Send your API requests with X-API-Version header with version lower or equal to 2016-07-20.

Overriding version header

You can use any version headers by passing option to Multiverse:

  pipeline :api do
    plug :accepts, ["json"]
    plug :put_secure_browser_headers

    plug Multiverse,
      version_header: "x-my-version-header",
      gates: %{
        ~D[2016-07-21] => [AccountTypeChange]
      }
  end

Using custom adapters

You can use your own adapter which implements Multiverse.Adapter behaviour:

  pipeline :api do
    plug :accepts, ["json"]
    plug :put_secure_browser_headers

    plug Multiverse,
      adapter: MyApp.SmartMultiverseAdapter,
      gates: %{
        ~D[2016-07-21] => [AccountTypeChange]
      }
  end

Structuring your tests

  1. Split your tests into versions:
$ ls -l test/acceptance
total 0
drwxr-xr-x  2 andrew  staff  68 Aug  1 19:23 AccountTypeChange
drwxr-xr-x  2 andrew  staff  68 Aug  1 19:24 OlderChange
  1. Avoid touching request or response in old tests. Create API gates and matching folder in acceptance tests.

Other things you might want to do

  1. Store Multiverse configuration in config.ex:
  use Mix.Config

  config :multiverse, MyApp.Endpoint,
    gates: %{
      ~D[2016-07-21] => [AccountTypeChange]
    }
  plug Multiverse, endpoint: __MODULE__
  1. Generate API documentation from changes @moduledoc's.

  2. Other awesome stuff. Open an issue and tell me about it! :).

License

See LICENSE.md.