# Versioning
[![Build Status](https://travis-ci.org/nsweeting/versioning.svg?branch=master)](https://travis-ci.org/nsweeting/versioning)
[![Versioning Version](https://img.shields.io/hexpm/v/versioning.svg)](https://hex.pm/packages/versioning)
Versioning provides a way for API's to remain backward compatible without the headache.
This is done through use of a "versioning schema" that translates data through a series
of steps to the target version. This technique is well described in the article [APIs as infrastructure: future-proofing Stripe with versioning](https://stripe.com/blog/api-versioning).
The basic rule is each API version in the schema must only ever concern itself with creating a
set of change modules associated with the version ahead of it. This contract ensures
that we can continue to translate data to legacy versions without enormous effort.
## Installation
The package can be installed by adding `versioning` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:versioning, "~> 0.1.0"}
]
end
```
## Documentation
See [HexDocs](https://hexdocs.pm/versioning) for additional documentation.
## Example
Lets say we have an `Article` struct that contains the boolean field `:active`. As time goes by, we recognize that there may be more kinds of statuses that our `Article`'s may have.
To keep up with the times, we add the enum field `:status`. One of the values could be `"active"` - among many others.
### Versioning Struct
A the heart of our versioning is the `Versioning` struct. A `Versioning` sruct contains the following fields:
- `:target` - The version that we want our data to be changed into.
- `:type` - The type of data we are working with. If we are working with structs, this will typically be the struct name, eg: `Article`
- `:data` - The underlying data that we want to change. For structs, like our `Article`, be aware that we typically have our data as a bare map since it is easier to transform.
- `:changed` - A boolean representing whether a change operation has occured.
- `:assigns` - A map of arbitrary data we can use to store additonal information in.
Let's create a versioning of an `Article` struct.
```elixir
# Fetch an article
arcticle = get_article(id)
versioning = Versioning.new("2019-01-01", article)
#Versioning<target: "2019-01-01", type: Article, changed: false>
```
We now have a versioning of our `Article`
### Versioning Change
Lets create of first "versioning change". This change module will accept a `Versioning` struct, and must return a `Versioning` struct.
```elixir
defmodule MyAPI.Versioning.ArticleStatus do
use Versioning.Change
def change(versioning) do
{status, data} = Map.pop(versioning.data, :status)
case status do
"active" -> put_active(versioning, data, true)
"archived" -> put_active(versioning, data, true)
_ -> put_active(versioning, data, false)
end
end
defp put_active(versioning, data, active) do
%{versioning | data: Map.put(data, :active, active)}
end
end
```
As you can see, our change module accepts the versioning, removes the new `:status` value, translates the status to our `:active` requirements, and updates the versioning data - returning a modified versioning.
### Versioning Schema
With our first change module in place, its time to tie it all together with our "versioning schema". The schema provides a DSL to describe and route our versioning.
```elixir
defmodule MyAPI.Versioning do
use Versioning.Schema
version "2019-01-01" do
change Article do
MyAPI.Versioning.ArticleStatus
end
change User do
[
MyAPI.Versioning.UserChange1,
MyAPI.Versioning.UserChange2
]
end
end
version "2018-12-01" do
change Article do
[
MyAPI.Versioning.OtherArticleChange,
MyAPI.Versioning.SomeOtherArticleChange,
]
end
change Payment do
[
MyAPI.Versioning.PaymentChange1,
MyAPI.Versioning.PaymentChange2,
MyAPI.Versioning.PaymentChange3
]
end
end
end
```
The schema above shows we currently support 2 versions. Our latest version `"2019-01-01"` is where our new article change is held. The schema DSL describes a flow, whereby the "top" version represents the most recent, and each subsequent version is one step older.
### Running our Versioning
With our versioning in place, we can now translate our `Article` struct to the requirements of our users "pinned" API version.
```elixir
#For the sake of example, lets say the user is pinned at the older "2018-12-01" version.
version = get_api_version(user)
article = get_article(id)
versioning = Versioning.new(version, article)
MyAPI.Versioning.run(versioning)
#Versioning<target: "2018-12-01", type: Article, changed: true>
```
Calling `run/1` on our schema with a versioning struct will execute our schema. It will "walk" through each version, running any changes held within it that match the change `:type` on our versioning struct.
Once a matching version is found, it will run the changes within, but will stop execution afterwards.
We can then access the underlying data through our `versioning.data`.
For the example above, this would mean our `Article` struct would have been run through the following change modules in the order:
1. `MyAPI.Versioning.ArticleStatus`
2. `MyAPI.Versioning.OtherArticleChange`
3. `MyAPI.Versioning.SomeOtherArticleChange`