SlideShare a Scribd company logo
Nos premiers mois
avec Elixir
Joan Zapata
@joanzap
Raphaël Lustin
@raphilustin
TrustBK
TrustBK
Cible les PME
From scratch, pas de dette technique
Repense la relation banquier-client
Confiance, technologie, éthique
On recrute !
trustbk.com/jobs
Nos premiers mois
• Découvrir le langage et son écosystème

• Trouver nos bonnes pratiques

• Apprendre à tester et à livrer en production
Découvrir le langage
Scénario 1
Gestion d’erreur
Scénario 1
GET /api/accounts/1
authorization: Bearer 12345678
GET /api/accounts/1
authorization: Bearer WRONG_TOKEN
401
200
Controller
Controller
Controller
Controller
Plug
SERVEUR
defmodule BackendWeb.Router do 

scope "/api", BackendWeb do 

get "/accounts/:id", AccountsController, :get_one 

end 

end
defmodule BackendWeb.Router do 









scope "/api", BackendWeb do 

get "/accounts/:id", AccountsController, :get_one 

end 

end
defmodule BackendWeb.Router do 

pipeline :authenticate do 

plug Backend.Plug.Authenticate 

end 



scope "/api", BackendWeb do 

get "/accounts/:id", AccountsController, :get_one 

end 

end
defmodule BackendWeb.Router do 

pipeline :authenticate do 

plug Backend.Plug.Authenticate 

end 



scope "/api", BackendWeb do
pipe_through :authenticate

get "/accounts/:id", AccountsController, :get_one 

end 

end
Backend.Plug.Authenticate
defmodule Backend.Plug.Authenticate do 

@behaviour Plug 



def init(opts), do: opts 

def call(conn, _opts  %{}) 

end
def call(conn, _opts  %{}) do



end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

{:ok, token} = get_token(authorization_headers) 

end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

{:ok, token} = get_token(authorization_headers) 

{:ok, session: session, user: user} = Auth.authenticate(token) 

end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

{:ok, token} = get_token(authorization_headers) 

{:ok, session: session, user: user} = Auth.authenticate(token) 

end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

{:ok, token} = get_token(authorization_headers) 

{:ok, session: session, user: user} = Auth.authenticate(token) 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session)
end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

{:ok, token} = get_token(authorization_headers) 

{:ok, session: session, user: user} = Auth.authenticate(token) 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session)
end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

case get_token(authorization_headers) do 

{:ok, token} -> 

{:ok, session: session, user: user} = Auth.authenticate(token) 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

{:error, error} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

case get_token(authorization_headers) do 

{:ok, token} -> 

{:ok, session: session, user: user} = Auth.authenticate(token) 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

{:error, error} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

case get_token(authorization_headers) do 

{:ok, token} -> 

{:ok, session: session, user: user} = Auth.authenticate(token) 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

{:error, error} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

case get_token(authorization_headers) do 

{:ok, token} -> 

{:ok, session: session, user: user} = Auth.authenticate(token) 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

{:error, error} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

case get_token(authorization_headers) do 

{:ok, token} -> 

case Auth.authenticate(token) do 

{:ok, session: session, user: user} -> 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

{:error, error} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

{:error, error} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

case get_token(authorization_headers) do 

{:ok, token} -> 

case Auth.authenticate(token) do 

{:ok, session: session, user: user} -> 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

{:error, error} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

{:error, error} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

end
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

case get_token(authorization_headers) do 

{:ok, token} -> 

case Auth.authenticate(token) do 

{:ok, session: session, user: user} -> 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

{:error, error} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

{:error, error} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

end
Please fix.
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

case get_token(authorization_headers) do 

{:ok, token} -> 

case Auth.authenticate(token) do 

{:ok, session: session, user: user} -> 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

{:error, error} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

{:error, error} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

end
|>
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

{:ok, token} = get_token(authorization_headers) 

{:ok, session: session, user: user} = Auth.authenticate(token) 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session)
end
def call(conn, _opts  %{}) do 

{:ok, session: session, user: user} = 

conn 

|> get_req_header("authorization") 

|> get_token() 

|> Auth.authenticate() 



conn 

|> assign(:current_user, user) 

|> assign(:current_session, session)

end
def call(conn, _opts  %{}) do 

conn 

|> get_req_header("authorization") 

|> get_token() 

|> Auth.authenticate() 

|> handle_authentication(conn) 

end
def call(conn, _opts  %{}) do 

conn 

|> get_req_header("authorization") 

|> get_token() 

|> Auth.authenticate() 

|> handle_authentication(conn) 

end 



def handle_authentication({:ok, session: session, user: user}, conn) do 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

end
def call(conn, _opts  %{}) do 

conn 

|> get_req_header("authorization") 

|> get_token() 

|> Auth.authenticate() 

|> handle_authentication(conn) 

end 



def handle_authentication({:ok, session: session, user: user}, conn) do 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

end 



def handle_authentication({:error, error}, conn) do 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end
def call(conn, _opts  %{}) do 

conn 

|> get_req_header("authorization") 

|> get_token() 

|> Auth.authenticate() 

|> handle_authentication(conn) 

end 



def handle_authentication({:ok, session: session, user: user}, conn) do 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

end 



def handle_authentication({:error, error}, conn) do 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end
def call(conn, _opts  %{}) do 

conn 

|> get_req_header("authorization") 

|> get_token() 

|> Auth.authenticate() 

|> handle_authentication(conn) 

end 



def handle_authentication({:ok, session: session, user: user}, conn) do 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

end 



def handle_authentication({:error, error}, conn) do 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end
{:ok, token}
@spec authenticate(String.t) :: …X
def call(conn, _opts  %{}) do 

conn 

|> get_req_header("authorization") 

|> get_token() 

|> Auth.authenticate() 

|> handle_authentication(conn) 

end 



def handle_authentication({:ok, session: session, user: user}, conn) do 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

end 



def handle_authentication({:error, error}, conn) do 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end
@spec authenticate({:ok, String.t} | {:error, atom}) :: …
{:ok, token}
@spec authenticate(String.t) :: …X
def call(conn, _opts  %{}) do 

conn 

|> get_req_header("authorization") 

|> get_token() 

|> Auth.authenticate() 

|> handle_authentication(conn) 

end 



def handle_authentication({:ok, session: session, user: user}, conn) do 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

end 



def handle_authentication({:error, error}, conn) do 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end
{:ok, token}
@spec authenticate(String.t) :: …X
@spec authenticate({:ok, String.t} | {:error, atom}) :: …Please fix.
😕
😯
def call(conn, _opts  %{}) do 

authorization_headers = get_req_header(conn, "authorization") 

{:ok, token} = get_token(authorization_headers) 

{:ok, session: session, user: user} = Auth.authenticate(token) 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session)
end
def call(conn, _opts  %{}) do 

with authorization_headers <- get_req_header(conn, "authorization"), 

{:ok, token} <- get_token(authorization_headers), 

{:ok, session: session, user: user} <- Auth.authenticate(token) 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session)
end
def call(conn, _opts  %{}) do 

with authorization_headers <- get_req_header(conn, "authorization"), 

{:ok, token} <- get_token(authorization_headers), 

{:ok, session: session, user: user} <- Auth.authenticate(token) 

do
conn 

|> assign(:current_user, user) 

|> assign(:current_session, session)
end
end
def call(conn, _opts  %{}) do 

with authorization_headers <- get_req_header(conn, "authorization"), 

{:ok, token} <- get_token(authorization_headers), 

{:ok, session: session, user: user} <- Auth.authenticate(token) 

do
conn 

|> assign(:current_user, user) 

|> assign(:current_session, session)

else
pattern -> instructions
pattern -> instructions
pattern -> instructions
end
end
def call(conn, _opts  %{}) do 

with authorization_headers <- get_req_header(conn, "authorization"), 

{:ok, token} <- get_token(authorization_headers), 

{:ok, session: session, user: user} <- Auth.authenticate(token) 

do
conn 

|> assign(:current_user, user) 

|> assign(:current_session, session)

else
end
end
def call(conn, _opts  %{}) do 

with authorization_headers <- get_req_header(conn, "authorization"), 

{:ok, token} <- get_token(authorization_headers), 

{:ok, session: session, user: user} <- Auth.authenticate(token) 

do 

conn 

|> assign(:current_user, user) 

|> assign(:current_session, session) 

else {:error, _reason} -> 

conn 

|> put_status(:unauthorized) 

|> render(ErrorView, "401.json", %{}) 

|> halt() 

end 

end
👍
Scénario 2
Pagination
Scénario 2
GET ?page=0
[{…}, {…}, {…}, {…}]
Serveur Prestataire
Scénario 2
GET ?page=0
[{…}, {…}, {…}, {…}]
Serveur Prestataire
GET ?page=1
[{…}, {…}]
Scénario 2
GET ?page=0
[{…}, {…}, {…}, {…}]
Serveur Prestataire
GET ?page=1
[{…}, {…}]
GET ?page=2
[]
defmodule Client 

def resources(page) do 

... 

end 

end
[%{…}, %{…}, %{…}]
defmodule Client 

def resources(page) do 

... 

end 

end
def get_resources do 

end
def get_resources do 

page = 0 

acc = []
end
def get_resources do 

page = 0 

acc = []


do 

result = Client.resources(page)
page = page + 1

acc = acc ++ result 

while(result != []) 



acc 

end 

Récursivité
def get_resources do 

end
def get_resources do
Client.resources(0)

end
def get_resources do
do_get_resources(0, Client.resources(0))

end
def get_resources do 

do_get_resources(0, Client.resources(0)) 

end 



defp do_get_resources(_page, []), 

do: :ok
def get_resources do 

do_get_resources(0, Client.resources(0)) 

end 



defp do_get_resources(_page, []), 

do: :ok

defp do_get_resources(page, resources), 

do: do_get_resources(
page + 1,
Client.resources(page + 1)
)
def get_resources do 

do_get_resources(0, Client.resources(0)) 

end 



defp do_get_resources(_page, []), 

do: :ok

defp do_get_resources(page, resources), 

do: do_get_resources(
page + 1,
Client.resources(page + 1)
)
def get_resources do 

do_get_resources([], 0, Client.resources(0)) 

end 



defp do_get_resources(acc, _page, []), 

do: acc

defp do_get_resources(acc, page, resources), 

do: do_get_resources(
acc + resources,
page + 1,
Client.resources(page + 1)
)
def get_resources do 

do_get_resources([], 0, Client.resources(0)) 

end 



defp do_get_resources(acc, _page, []), 

do: acc

defp do_get_resources(acc, page, resources), 

do: do_get_resources(
acc + resources,
page + 1,
Client.resources(page + 1)
)
Utilisation
get_resources() 

|> Enum.each(&process_resource/1)
get_resources() 

|> Enum.each(&process_resource/1)
get_resources() 

|> Enum.each(&process_resource/1)
get_resources() 

|> Stream.each(&process_resource/1)
|> Stream.run()
get_resources() 

|> Stream.each(&process_resource/1)
|> Stream.run()
get_resources() 

|> Stream.each(&process_resource/1)
|> Stream.run()
get_resources() 

|> Stream.each(&process_resource/1)
|> Stream.run()
def get_resources do

end
def get_resources do 

Stream.unfold()

end
unfold(initial_acc, (acc -> {element, acc} | nil))
def get_resources do 

Stream.unfold()

end
def get_resources do 

Stream.unfold(0, fn page -> 

end) 

end
def get_resources do 

Stream.unfold(0, fn page ->

resources = Client.resources(page) 

{resources, page + 1} 

end) 

end
def get_resources do 

Stream.unfold(0, fn page -> 

case Client.resources(page) do 

[] -> nil 

resources -> {resources, page + 1} 

end 

end) 

end
get_resources |> Enum.to_list()
[

[ %{id: 1, …}, %{id: 2, …}, %{id: 3, …} ], 

[ %{id: 4, …}, %{id: 5, …}, %{id: 6, …} ], 

[ %{id: 7, …}, %{id: 8, …}, %{id: 9, …} ] 

]
iex>
def get_resources do 

Stream.unfold(0, fn page -> 

case Client.resources(page) do 

[] -> nil 

resources -> {resources, page + 1} 

end 

end)

end
def get_resources do 

Stream.unfold(0, fn page -> 

case Client.resources(page) do 

[] -> nil 

resources -> {resources, page + 1} 

end 

end)
|> Stream.flat_map(& &1) 

end
[

%{id: 1, …}, %{id: 2, …}, %{id: 3, …},

%{id: 4, …}, %{id: 5, …}, %{id: 6, …}, 

%{id: 7, …}, %{id: 8, …}, %{id: 9, …}

]
get_resources |> Enum.to_list()iex>
👍def get_resources do 

Stream.unfold(0, fn page -> 

case Client.resources(page) do 

[] -> nil 

resources -> {resources, page + 1} 

end 

end)
|> Stream.flat_map(& &1) 

end
Intégration
et livraison continues
Retour d’expérience
.gitlab-ci.yml
image: elixir:1.5.1
services:
- postgres:9.6
variables:
MIX_ENV: test
stages:
- build
- test
- package
- deploy
before_script:
- mix local.hex --force
- mix local.rebar --force
- mix deps.get --only test
cache:
paths:
- "_build"
- "deps"
.gitlab-ci.yml
image: elixir:1.5.1
services:
- postgres:9.6
variables:
MIX_ENV: test
stages:
- build
- test
- package
- deploy
before_script:
- mix local.hex --force
- mix local.rebar --force
- mix deps.get --only test
cache:
paths:
- "_build"
- "deps"
.gitlab-ci.yml
image: elixir:1.5.1
services:
- postgres:9.6
variables:
MIX_ENV: test
stages:
- build
- test
- package
- deploy
before_script:
- mix local.hex --force
- mix local.rebar --force
- mix deps.get --only test
cache:
paths:
- "_build"
- "deps"
.gitlab-ci.yml
image: elixir:1.5.1
services:
- postgres:9.6
variables:
MIX_ENV: test
stages:
- build
- test
- package
- deploy
before_script:
- mix local.hex --force
- mix local.rebar --force
- mix deps.get --only test
cache:
paths:
- "_build"
- "deps"
.gitlab-ci.yml
build:
stage: build
script:
- mix compile
doc:
stage: build
script:
- mix docs
.gitlab-ci.yml
.gitlab-ci.yml
lint:
stage: test
script:
- mix credo
- mix dialyzer --halt-exit-status
migrations:
stage: test
script:
- mix ecto.reset
- mix ecto.rollback --all
test:
stage: test
script:
- mix ecto.reset
- mix test
.gitlab-ci.yml
lint:
stage: test
script:
- mix credo
- mix dialyzer --halt-exit-status
migrations:
stage: test
script:
- mix ecto.reset
- mix ecto.rollback --all
test:
stage: test
script:
- mix ecto.reset
- mix test
.gitlab-ci.yml
lint:
stage: test
script:
- mix credo
- mix dialyzer --halt-exit-status
migrations:
stage: test
script:
- mix ecto.reset
- mix ecto.rollback --all
test:
stage: test
script:
- mix ecto.reset
- mix test
.gitlab-ci.yml
lint:
stage: test
script:
- mix credo
- mix dialyzer --halt-exit-status
migrations:
stage: test
script:
- mix ecto.reset
- mix ecto.rollback --all
test:
stage: test
script:
- mix ecto.reset
- mix test
.gitlab-ci.yml
package:
stage: package
script:
- bin/package.sh
artifacts:
expire_in: 1 month
paths:
- artifact/
.gitlab-ci.yml
package:
stage: package
script:
- bin/package.sh
artifacts:
expire_in: 1 month
paths:
- artifact/
.gitlab-ci.yml
deploy:integration:
stage: deploy
environment:
name: $CI_BUILD_REF_NAME
url: https://$CI_BUILD_REF_NAME.integration
only:
- master@trustbk/backend
script:
- bin/deploy.sh
.gitlab-ci.yml
deploy:integration:
stage: deploy
environment:
name: $CI_BUILD_REF_NAME
url: https://$CI_BUILD_REF_NAME.integration
only:
- master@trustbk/backend
script:
- bin/deploy.sh
.gitlab-ci.yml
deploy:integration:
stage: deploy
environment:
name: $CI_BUILD_REF_NAME
url: https://$CI_BUILD_REF_NAME.integration
only:
- master@trustbk/backend
script:
- bin/deploy.sh
bin/package.sh
bin/package.sh
distillery
A pure Elixir implementation of

release packaging functionality for the Erlang VM.
rel/config.exs
Path.join(["rel", "plugins", "*.exs"])
|> Path.wildcard()
|> Enum.map(&Code.eval_file(&1))
use Mix.Releases.Config,
default_release: :backend,
default_environment: Mix.env()
environment :prod do
set include_erts: true
set include_src: false
set include_system_libs: true
end
release :backend do
set version: current_version(:backend)
set applications: [
:backend,
…
]
end
bin/package.sh
$ mix release
==> Assembling release..
==> Building release backend:0.0.1 using environment prod
==> Including ERTS 9.0.4 from /usr/local/Cellar/erlang/20.0/lib/erlang/erts-9.0.4
==> Packaging release..
==> Release successfully built!
You can run it in one of the following ways:
Interactive: _build/prod/rel/backend/bin/backend console
Foreground: _build/prod/rel/backend/bin/backend foreground
Daemon: _build/prod/rel/backend/bin/backend start
bin/package.sh
_build/prod/rel/backend/releases/0.0.1/backend.tar.gz
bin/package.sh
$ tree -L 2 _build/prod/rel/backend
_build/prod/rel/backend
├── bin
│   ├── backend
│   ├── backend.bat
│   ├── backend_loader.sh
│   ├── release_utils.escript
│   └── start_clean.boot
├── erts-9.0
│   ├── bin
│   ├── include
│   ├── lib
│   └── src
├── lib
│   ├── backend-0.0.1
│   └── […dependencies…]
├── releases
│   ├── 0.0.1
│   ├── RELEASES
│   └── start_erl.data
└── var
├── WARNING_README
├── log
├── sys.config
└── vm.args
bin/backend start
bin/backend stop
bin/package.sh
#!/bin/bash -ex
MIX_ENV=prod mix release —warnings-as-errors
[…preparing a tarball from _build/prod/rel/backend]
bin/package.sh
#!/bin/bash -ex
MIX_ENV=prod mix release —warnings-as-errors
[…preparing a tarball from _build/prod/rel/backend]
bin/package.sh
lib/backend/release_tasks.ex
defmodule Elixir.Backend.ReleaseTasks do
alias Backend.Repo
alias Ecto.Migrator
@start_apps [:ecto, :postgrex]
def migrate do
:ok = Application.load(:backend)
Enum.each(@start_apps, &Application.ensure_all_started/1)
{:ok, _} = Repo.start_link(pool_size: 1)
path = Application.app_dir(:backend, "priv/repo/migrations")
Migrator.run(Repo, path, :up, all: true)
Enum.each(@start_apps, &Application.stop/1)
:init.stop()
end
end
lib/backend/release_tasks.ex
defmodule Elixir.Backend.ReleaseTasks do
alias Backend.Repo
alias Ecto.Migrator
@start_apps [:ecto, :postgrex]
def migrate do
:ok = Application.load(:backend)
Enum.each(@start_apps, &Application.ensure_all_started/1)
{:ok, _} = Repo.start_link(pool_size: 1)
path = Application.app_dir(:backend, "priv/repo/migrations")
Migrator.run(Repo, path, :up, all: true)
Enum.each(@start_apps, &Application.stop/1)
:init.stop()
end
end
lib/backend/release_tasks.ex
defmodule Elixir.Backend.ReleaseTasks do
alias Backend.Repo
alias Ecto.Migrator
@start_apps [:ecto, :postgrex]
def migrate do
:ok = Application.load(:backend)
Enum.each(@start_apps, &Application.ensure_all_started/1)
{:ok, _} = Repo.start_link(pool_size: 1)
path = Application.app_dir(:backend, "priv/repo/migrations")
Migrator.run(Repo, path, :up, all: true)
Enum.each(@start_apps, &Application.stop/1)
:init.stop()
end
end
rel/commands/migrate.sh
#!/bin/sh
bin/backend command Elixir.Backend.ReleaseTasks migrate
rel/config.exs
release :backend do
set version: current_version(:backend)
set applications: [
:backend,
…
]
set commands: [
"migrate": "rel/commands/migrate.sh"
]
end
bin/deploy.sh
config/prod.exs
use Mix.Config
config :backend, Backend.Repo,
adapter: Ecto.Adapters.Postgres,
username: System.get_env("TBK_BACKEND_DB_USER"),
password: System.get_env("TBK_BACKEND_DB_PASSWORD"),
database: System.get_env("TBK_BACKEND_DB_DATABASE"),
hostname: System.get_env("TBK_BACKEND_DB_HOSTNAME"),
pool_size: 10
Evaluated at compile-time!
config/prod.exs
use Mix.Config
config :backend, Backend.Repo,
adapter: Ecto.Adapters.Postgres,
username: "${TBK_BACKEND_DB_USER}",
password: "${TBK_BACKEND_DB_PASSWORD}",
database: "${TBK_BACKEND_DB_DATABASE}",
hostname: "${TBK_BACKEND_DB_HOSTNAME}",
pool_size: 10
Evaluated at runtime by Erlang VM with REPLACE_OS_VARS=true
bin/deploy.sh
[…copy tarball to server by SSH]
[…expand]
vcour/bin/backend stop
ln -sfT versions/20170921102241_8f41acc1 vcour
vcour/bin/backend migrate
vcour/bin/backend start
• Idempotent

• Stateless

• Zero-downtime deployment
bin/deploy.sh
Questions
Joan Zapata
@joanzap
Raphaël Lustin
@raphilustin

More Related Content

What's hot

CakePHP workshop
CakePHP workshopCakePHP workshop
CakePHP workshop
Walther Lalk
 
jQuery: Events, Animation, Ajax
jQuery: Events, Animation, AjaxjQuery: Events, Animation, Ajax
jQuery: Events, Animation, Ajax
Constantin Titarenko
 
Security in laravel
Security in laravelSecurity in laravel
Security in laravel
Sayed Ahmed
 
And the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack SupportAnd the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack Support
Ben Scofield
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
Konstantin Kudryashov
 
Zero to SOLID
Zero to SOLIDZero to SOLID
Zero to SOLID
Vic Metcalfe
 
Advanced jQuery
Advanced jQueryAdvanced jQuery
Advanced jQuery
sergioafp
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
Konstantin Kudryashov
 
Do more with less code in a serverless world
Do more with less code in a serverless worldDo more with less code in a serverless world
Do more with less code in a serverless world
jeromevdl
 
Guard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful Security
Ryan Weaver
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form component
Samuel ROZE
 
Building @Anywhere (for TXJS)
Building @Anywhere (for TXJS)Building @Anywhere (for TXJS)
Building @Anywhere (for TXJS)danwrong
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
Samuel ROZE
 
Micro app-framework
Micro app-frameworkMicro app-framework
Micro app-framework
Michael Dawson
 
Matters of State
Matters of StateMatters of State
Matters of State
Kris Wallsmith
 
sfDay Cologne - Sonata Admin Bundle
sfDay Cologne - Sonata Admin BundlesfDay Cologne - Sonata Admin Bundle
sfDay Cologne - Sonata Admin Bundleth0masr
 
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreSymfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Ryan Weaver
 
Web::Machine - Simpl{e,y} HTTP
Web::Machine - Simpl{e,y} HTTPWeb::Machine - Simpl{e,y} HTTP
Web::Machine - Simpl{e,y} HTTP
Michael Francis
 
Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…
Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…
Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…D
 
Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...
Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...
Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...D
 

What's hot (20)

CakePHP workshop
CakePHP workshopCakePHP workshop
CakePHP workshop
 
jQuery: Events, Animation, Ajax
jQuery: Events, Animation, AjaxjQuery: Events, Animation, Ajax
jQuery: Events, Animation, Ajax
 
Security in laravel
Security in laravelSecurity in laravel
Security in laravel
 
And the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack SupportAnd the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack Support
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
 
Zero to SOLID
Zero to SOLIDZero to SOLID
Zero to SOLID
 
Advanced jQuery
Advanced jQueryAdvanced jQuery
Advanced jQuery
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
 
Do more with less code in a serverless world
Do more with less code in a serverless worldDo more with less code in a serverless world
Do more with less code in a serverless world
 
Guard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful Security
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form component
 
Building @Anywhere (for TXJS)
Building @Anywhere (for TXJS)Building @Anywhere (for TXJS)
Building @Anywhere (for TXJS)
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Micro app-framework
Micro app-frameworkMicro app-framework
Micro app-framework
 
Matters of State
Matters of StateMatters of State
Matters of State
 
sfDay Cologne - Sonata Admin Bundle
sfDay Cologne - Sonata Admin BundlesfDay Cologne - Sonata Admin Bundle
sfDay Cologne - Sonata Admin Bundle
 
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreSymfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
 
Web::Machine - Simpl{e,y} HTTP
Web::Machine - Simpl{e,y} HTTPWeb::Machine - Simpl{e,y} HTTP
Web::Machine - Simpl{e,y} HTTP
 
Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…
Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…
Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…
 
Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...
Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...
Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...
 

Similar to Elixir Paris Meetup

JavaScript Promises
JavaScript PromisesJavaScript Promises
JavaScript Promises
Tomasz Bak
 
What's new in Rails 4
What's new in Rails 4What's new in Rails 4
What's new in Rails 4
Fabio Akita
 
More to RoC weibo
More to RoC weiboMore to RoC weibo
More to RoC weibo
shaokun
 
Roll Your Own API Management Platform with nginx and Lua
Roll Your Own API Management Platform with nginx and LuaRoll Your Own API Management Platform with nginx and Lua
Roll Your Own API Management Platform with nginx and Lua
Jon Moore
 
"Auth for React.js APP", Nikita Galkin
"Auth for React.js APP", Nikita Galkin"Auth for React.js APP", Nikita Galkin
"Auth for React.js APP", Nikita Galkin
Fwdays
 
Passwords suck, but centralized proprietary services are not the answer
Passwords suck, but centralized proprietary services are not the answerPasswords suck, but centralized proprietary services are not the answer
Passwords suck, but centralized proprietary services are not the answer
Francois Marier
 
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Francois Marier
 
Bonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node jsBonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node js
Francois Zaninotto
 
Payments On Rails
Payments On RailsPayments On Rails
Payments On Rails
E-xact Transactions
 
Phoenix for laravel developers
Phoenix for laravel developersPhoenix for laravel developers
Phoenix for laravel developers
Luiz Messias
 
The web beyond "usernames & passwords"
The web beyond "usernames & passwords"The web beyond "usernames & passwords"
The web beyond "usernames & passwords"
Francois Marier
 
Take My Logs. Please!
Take My Logs. Please!Take My Logs. Please!
Take My Logs. Please!
Mike Brittain
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsMichael Peacock
 
Persona: in your browsers, killing your passwords
Persona: in your browsers, killing your passwordsPersona: in your browsers, killing your passwords
Persona: in your browsers, killing your passwords
Francois Marier
 
Flask patterns
Flask patternsFlask patterns
Flask patternsit-people
 
Tools for Making Machine Learning more Reactive
Tools for Making Machine Learning more ReactiveTools for Making Machine Learning more Reactive
Tools for Making Machine Learning more Reactive
Jeff Smith
 
Angular js security
Angular js securityAngular js security
Angular js security
Jose Manuel Ortega Candel
 
Centralize your Business Logic with Pipelines in Elixir
Centralize your Business Logic with Pipelines in ElixirCentralize your Business Logic with Pipelines in Elixir
Centralize your Business Logic with Pipelines in Elixir
Michael Viveros
 
RubyBarCamp “Полезные gems и plugins”
RubyBarCamp “Полезные gems и plugins”RubyBarCamp “Полезные gems и plugins”
RubyBarCamp “Полезные gems и plugins”
apostlion
 

Similar to Elixir Paris Meetup (20)

JavaScript Promises
JavaScript PromisesJavaScript Promises
JavaScript Promises
 
What's new in Rails 4
What's new in Rails 4What's new in Rails 4
What's new in Rails 4
 
More to RoC weibo
More to RoC weiboMore to RoC weibo
More to RoC weibo
 
Roll Your Own API Management Platform with nginx and Lua
Roll Your Own API Management Platform with nginx and LuaRoll Your Own API Management Platform with nginx and Lua
Roll Your Own API Management Platform with nginx and Lua
 
"Auth for React.js APP", Nikita Galkin
"Auth for React.js APP", Nikita Galkin"Auth for React.js APP", Nikita Galkin
"Auth for React.js APP", Nikita Galkin
 
Passwords suck, but centralized proprietary services are not the answer
Passwords suck, but centralized proprietary services are not the answerPasswords suck, but centralized proprietary services are not the answer
Passwords suck, but centralized proprietary services are not the answer
 
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
 
Bonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node jsBonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node js
 
Payments On Rails
Payments On RailsPayments On Rails
Payments On Rails
 
Phoenix for laravel developers
Phoenix for laravel developersPhoenix for laravel developers
Phoenix for laravel developers
 
The web beyond "usernames & passwords"
The web beyond "usernames & passwords"The web beyond "usernames & passwords"
The web beyond "usernames & passwords"
 
Take My Logs. Please!
Take My Logs. Please!Take My Logs. Please!
Take My Logs. Please!
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
 
Django (Web Konferencia 2009)
Django (Web Konferencia 2009)Django (Web Konferencia 2009)
Django (Web Konferencia 2009)
 
Persona: in your browsers, killing your passwords
Persona: in your browsers, killing your passwordsPersona: in your browsers, killing your passwords
Persona: in your browsers, killing your passwords
 
Flask patterns
Flask patternsFlask patterns
Flask patterns
 
Tools for Making Machine Learning more Reactive
Tools for Making Machine Learning more ReactiveTools for Making Machine Learning more Reactive
Tools for Making Machine Learning more Reactive
 
Angular js security
Angular js securityAngular js security
Angular js security
 
Centralize your Business Logic with Pipelines in Elixir
Centralize your Business Logic with Pipelines in ElixirCentralize your Business Logic with Pipelines in Elixir
Centralize your Business Logic with Pipelines in Elixir
 
RubyBarCamp “Полезные gems и plugins”
RubyBarCamp “Полезные gems и plugins”RubyBarCamp “Полезные gems и plugins”
RubyBarCamp “Полезные gems и plugins”
 

Recently uploaded

Hierarchical Digital Twin of a Naval Power System
Hierarchical Digital Twin of a Naval Power SystemHierarchical Digital Twin of a Naval Power System
Hierarchical Digital Twin of a Naval Power System
Kerry Sado
 
weather web application report.pdf
weather web application report.pdfweather web application report.pdf
weather web application report.pdf
Pratik Pawar
 
HYDROPOWER - Hydroelectric power generation
HYDROPOWER - Hydroelectric power generationHYDROPOWER - Hydroelectric power generation
HYDROPOWER - Hydroelectric power generation
Robbie Edward Sayers
 
一比一原版(UofT毕业证)多伦多大学毕业证成绩单如何办理
一比一原版(UofT毕业证)多伦多大学毕业证成绩单如何办理一比一原版(UofT毕业证)多伦多大学毕业证成绩单如何办理
一比一原版(UofT毕业证)多伦多大学毕业证成绩单如何办理
ydteq
 
Student information management system project report ii.pdf
Student information management system project report ii.pdfStudent information management system project report ii.pdf
Student information management system project report ii.pdf
Kamal Acharya
 
MCQ Soil mechanics questions (Soil shear strength).pdf
MCQ Soil mechanics questions (Soil shear strength).pdfMCQ Soil mechanics questions (Soil shear strength).pdf
MCQ Soil mechanics questions (Soil shear strength).pdf
Osamah Alsalih
 
CME397 Surface Engineering- Professional Elective
CME397 Surface Engineering- Professional ElectiveCME397 Surface Engineering- Professional Elective
CME397 Surface Engineering- Professional Elective
karthi keyan
 
Gen AI Study Jams _ For the GDSC Leads in India.pdf
Gen AI Study Jams _ For the GDSC Leads in India.pdfGen AI Study Jams _ For the GDSC Leads in India.pdf
Gen AI Study Jams _ For the GDSC Leads in India.pdf
gdsczhcet
 
Investor-Presentation-Q1FY2024 investor presentation document.pptx
Investor-Presentation-Q1FY2024 investor presentation document.pptxInvestor-Presentation-Q1FY2024 investor presentation document.pptx
Investor-Presentation-Q1FY2024 investor presentation document.pptx
AmarGB2
 
Nuclear Power Economics and Structuring 2024
Nuclear Power Economics and Structuring 2024Nuclear Power Economics and Structuring 2024
Nuclear Power Economics and Structuring 2024
Massimo Talia
 
English lab ppt no titlespecENG PPTt.pdf
English lab ppt no titlespecENG PPTt.pdfEnglish lab ppt no titlespecENG PPTt.pdf
English lab ppt no titlespecENG PPTt.pdf
BrazilAccount1
 
NO1 Uk best vashikaran specialist in delhi vashikaran baba near me online vas...
NO1 Uk best vashikaran specialist in delhi vashikaran baba near me online vas...NO1 Uk best vashikaran specialist in delhi vashikaran baba near me online vas...
NO1 Uk best vashikaran specialist in delhi vashikaran baba near me online vas...
Amil Baba Dawood bangali
 
J.Yang, ICLR 2024, MLILAB, KAIST AI.pdf
J.Yang,  ICLR 2024, MLILAB, KAIST AI.pdfJ.Yang,  ICLR 2024, MLILAB, KAIST AI.pdf
J.Yang, ICLR 2024, MLILAB, KAIST AI.pdf
MLILAB
 
Fundamentals of Electric Drives and its applications.pptx
Fundamentals of Electric Drives and its applications.pptxFundamentals of Electric Drives and its applications.pptx
Fundamentals of Electric Drives and its applications.pptx
manasideore6
 
Water Industry Process Automation and Control Monthly - May 2024.pdf
Water Industry Process Automation and Control Monthly - May 2024.pdfWater Industry Process Automation and Control Monthly - May 2024.pdf
Water Industry Process Automation and Control Monthly - May 2024.pdf
Water Industry Process Automation & Control
 
power quality voltage fluctuation UNIT - I.pptx
power quality voltage fluctuation UNIT - I.pptxpower quality voltage fluctuation UNIT - I.pptx
power quality voltage fluctuation UNIT - I.pptx
ViniHema
 
Architectural Portfolio Sean Lockwood
Architectural Portfolio Sean LockwoodArchitectural Portfolio Sean Lockwood
Architectural Portfolio Sean Lockwood
seandesed
 
Standard Reomte Control Interface - Neometrix
Standard Reomte Control Interface - NeometrixStandard Reomte Control Interface - Neometrix
Standard Reomte Control Interface - Neometrix
Neometrix_Engineering_Pvt_Ltd
 
Governing Equations for Fundamental Aerodynamics_Anderson2010.pdf
Governing Equations for Fundamental Aerodynamics_Anderson2010.pdfGoverning Equations for Fundamental Aerodynamics_Anderson2010.pdf
Governing Equations for Fundamental Aerodynamics_Anderson2010.pdf
WENKENLI1
 
block diagram and signal flow graph representation
block diagram and signal flow graph representationblock diagram and signal flow graph representation
block diagram and signal flow graph representation
Divya Somashekar
 

Recently uploaded (20)

Hierarchical Digital Twin of a Naval Power System
Hierarchical Digital Twin of a Naval Power SystemHierarchical Digital Twin of a Naval Power System
Hierarchical Digital Twin of a Naval Power System
 
weather web application report.pdf
weather web application report.pdfweather web application report.pdf
weather web application report.pdf
 
HYDROPOWER - Hydroelectric power generation
HYDROPOWER - Hydroelectric power generationHYDROPOWER - Hydroelectric power generation
HYDROPOWER - Hydroelectric power generation
 
一比一原版(UofT毕业证)多伦多大学毕业证成绩单如何办理
一比一原版(UofT毕业证)多伦多大学毕业证成绩单如何办理一比一原版(UofT毕业证)多伦多大学毕业证成绩单如何办理
一比一原版(UofT毕业证)多伦多大学毕业证成绩单如何办理
 
Student information management system project report ii.pdf
Student information management system project report ii.pdfStudent information management system project report ii.pdf
Student information management system project report ii.pdf
 
MCQ Soil mechanics questions (Soil shear strength).pdf
MCQ Soil mechanics questions (Soil shear strength).pdfMCQ Soil mechanics questions (Soil shear strength).pdf
MCQ Soil mechanics questions (Soil shear strength).pdf
 
CME397 Surface Engineering- Professional Elective
CME397 Surface Engineering- Professional ElectiveCME397 Surface Engineering- Professional Elective
CME397 Surface Engineering- Professional Elective
 
Gen AI Study Jams _ For the GDSC Leads in India.pdf
Gen AI Study Jams _ For the GDSC Leads in India.pdfGen AI Study Jams _ For the GDSC Leads in India.pdf
Gen AI Study Jams _ For the GDSC Leads in India.pdf
 
Investor-Presentation-Q1FY2024 investor presentation document.pptx
Investor-Presentation-Q1FY2024 investor presentation document.pptxInvestor-Presentation-Q1FY2024 investor presentation document.pptx
Investor-Presentation-Q1FY2024 investor presentation document.pptx
 
Nuclear Power Economics and Structuring 2024
Nuclear Power Economics and Structuring 2024Nuclear Power Economics and Structuring 2024
Nuclear Power Economics and Structuring 2024
 
English lab ppt no titlespecENG PPTt.pdf
English lab ppt no titlespecENG PPTt.pdfEnglish lab ppt no titlespecENG PPTt.pdf
English lab ppt no titlespecENG PPTt.pdf
 
NO1 Uk best vashikaran specialist in delhi vashikaran baba near me online vas...
NO1 Uk best vashikaran specialist in delhi vashikaran baba near me online vas...NO1 Uk best vashikaran specialist in delhi vashikaran baba near me online vas...
NO1 Uk best vashikaran specialist in delhi vashikaran baba near me online vas...
 
J.Yang, ICLR 2024, MLILAB, KAIST AI.pdf
J.Yang,  ICLR 2024, MLILAB, KAIST AI.pdfJ.Yang,  ICLR 2024, MLILAB, KAIST AI.pdf
J.Yang, ICLR 2024, MLILAB, KAIST AI.pdf
 
Fundamentals of Electric Drives and its applications.pptx
Fundamentals of Electric Drives and its applications.pptxFundamentals of Electric Drives and its applications.pptx
Fundamentals of Electric Drives and its applications.pptx
 
Water Industry Process Automation and Control Monthly - May 2024.pdf
Water Industry Process Automation and Control Monthly - May 2024.pdfWater Industry Process Automation and Control Monthly - May 2024.pdf
Water Industry Process Automation and Control Monthly - May 2024.pdf
 
power quality voltage fluctuation UNIT - I.pptx
power quality voltage fluctuation UNIT - I.pptxpower quality voltage fluctuation UNIT - I.pptx
power quality voltage fluctuation UNIT - I.pptx
 
Architectural Portfolio Sean Lockwood
Architectural Portfolio Sean LockwoodArchitectural Portfolio Sean Lockwood
Architectural Portfolio Sean Lockwood
 
Standard Reomte Control Interface - Neometrix
Standard Reomte Control Interface - NeometrixStandard Reomte Control Interface - Neometrix
Standard Reomte Control Interface - Neometrix
 
Governing Equations for Fundamental Aerodynamics_Anderson2010.pdf
Governing Equations for Fundamental Aerodynamics_Anderson2010.pdfGoverning Equations for Fundamental Aerodynamics_Anderson2010.pdf
Governing Equations for Fundamental Aerodynamics_Anderson2010.pdf
 
block diagram and signal flow graph representation
block diagram and signal flow graph representationblock diagram and signal flow graph representation
block diagram and signal flow graph representation
 

Elixir Paris Meetup

  • 1. Nos premiers mois avec Elixir Joan Zapata @joanzap Raphaël Lustin @raphilustin
  • 3. TrustBK Cible les PME From scratch, pas de dette technique Repense la relation banquier-client Confiance, technologie, éthique
  • 5. Nos premiers mois • Découvrir le langage et son écosystème • Trouver nos bonnes pratiques • Apprendre à tester et à livrer en production
  • 8. Scénario 1 GET /api/accounts/1 authorization: Bearer 12345678 GET /api/accounts/1 authorization: Bearer WRONG_TOKEN 401 200 Controller Controller Controller Controller Plug SERVEUR
  • 9. defmodule BackendWeb.Router do 
 scope "/api", BackendWeb do 
 get "/accounts/:id", AccountsController, :get_one 
 end 
 end
  • 10. defmodule BackendWeb.Router do 
 
 
 
 
 scope "/api", BackendWeb do 
 get "/accounts/:id", AccountsController, :get_one 
 end 
 end
  • 11. defmodule BackendWeb.Router do 
 pipeline :authenticate do 
 plug Backend.Plug.Authenticate 
 end 
 
 scope "/api", BackendWeb do 
 get "/accounts/:id", AccountsController, :get_one 
 end 
 end
  • 12. defmodule BackendWeb.Router do 
 pipeline :authenticate do 
 plug Backend.Plug.Authenticate 
 end 
 
 scope "/api", BackendWeb do pipe_through :authenticate
 get "/accounts/:id", AccountsController, :get_one 
 end 
 end
  • 14. defmodule Backend.Plug.Authenticate do 
 @behaviour Plug 
 
 def init(opts), do: opts 
 def call(conn, _opts %{}) 
 end
  • 15. def call(conn, _opts %{}) do
 
 end
  • 16. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 end
  • 17. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 {:ok, token} = get_token(authorization_headers) 
 end
  • 18. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 {:ok, token} = get_token(authorization_headers) 
 {:ok, session: session, user: user} = Auth.authenticate(token) 
 end
  • 19. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 {:ok, token} = get_token(authorization_headers) 
 {:ok, session: session, user: user} = Auth.authenticate(token) 
 end
  • 20. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 {:ok, token} = get_token(authorization_headers) 
 {:ok, session: session, user: user} = Auth.authenticate(token) 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) end
  • 21. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 {:ok, token} = get_token(authorization_headers) 
 {:ok, session: session, user: user} = Auth.authenticate(token) 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) end
  • 22. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 case get_token(authorization_headers) do 
 {:ok, token} -> 
 {:ok, session: session, user: user} = Auth.authenticate(token) 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 {:error, error} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 end
  • 23. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 case get_token(authorization_headers) do 
 {:ok, token} -> 
 {:ok, session: session, user: user} = Auth.authenticate(token) 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 {:error, error} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 end
  • 24. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 case get_token(authorization_headers) do 
 {:ok, token} -> 
 {:ok, session: session, user: user} = Auth.authenticate(token) 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 {:error, error} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 end
  • 25. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 case get_token(authorization_headers) do 
 {:ok, token} -> 
 {:ok, session: session, user: user} = Auth.authenticate(token) 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 {:error, error} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 end
  • 26. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 case get_token(authorization_headers) do 
 {:ok, token} -> 
 case Auth.authenticate(token) do 
 {:ok, session: session, user: user} -> 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 {:error, error} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 {:error, error} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 end
  • 27. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 case get_token(authorization_headers) do 
 {:ok, token} -> 
 case Auth.authenticate(token) do 
 {:ok, session: session, user: user} -> 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 {:error, error} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 {:error, error} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 end
  • 28. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 case get_token(authorization_headers) do 
 {:ok, token} -> 
 case Auth.authenticate(token) do 
 {:ok, session: session, user: user} -> 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 {:error, error} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 {:error, error} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 end
  • 29. Please fix. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 case get_token(authorization_headers) do 
 {:ok, token} -> 
 case Auth.authenticate(token) do 
 {:ok, session: session, user: user} -> 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 {:error, error} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 {:error, error} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 end
  • 30. |>
  • 31. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 {:ok, token} = get_token(authorization_headers) 
 {:ok, session: session, user: user} = Auth.authenticate(token) 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) end
  • 32. def call(conn, _opts %{}) do 
 {:ok, session: session, user: user} = 
 conn 
 |> get_req_header("authorization") 
 |> get_token() 
 |> Auth.authenticate() 
 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session)
 end
  • 33. def call(conn, _opts %{}) do 
 conn 
 |> get_req_header("authorization") 
 |> get_token() 
 |> Auth.authenticate() 
 |> handle_authentication(conn) 
 end
  • 34. def call(conn, _opts %{}) do 
 conn 
 |> get_req_header("authorization") 
 |> get_token() 
 |> Auth.authenticate() 
 |> handle_authentication(conn) 
 end 
 
 def handle_authentication({:ok, session: session, user: user}, conn) do 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 end
  • 35. def call(conn, _opts %{}) do 
 conn 
 |> get_req_header("authorization") 
 |> get_token() 
 |> Auth.authenticate() 
 |> handle_authentication(conn) 
 end 
 
 def handle_authentication({:ok, session: session, user: user}, conn) do 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 end 
 
 def handle_authentication({:error, error}, conn) do 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end
  • 36. def call(conn, _opts %{}) do 
 conn 
 |> get_req_header("authorization") 
 |> get_token() 
 |> Auth.authenticate() 
 |> handle_authentication(conn) 
 end 
 
 def handle_authentication({:ok, session: session, user: user}, conn) do 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 end 
 
 def handle_authentication({:error, error}, conn) do 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end
  • 37. def call(conn, _opts %{}) do 
 conn 
 |> get_req_header("authorization") 
 |> get_token() 
 |> Auth.authenticate() 
 |> handle_authentication(conn) 
 end 
 
 def handle_authentication({:ok, session: session, user: user}, conn) do 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 end 
 
 def handle_authentication({:error, error}, conn) do 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end {:ok, token} @spec authenticate(String.t) :: …X
  • 38. def call(conn, _opts %{}) do 
 conn 
 |> get_req_header("authorization") 
 |> get_token() 
 |> Auth.authenticate() 
 |> handle_authentication(conn) 
 end 
 
 def handle_authentication({:ok, session: session, user: user}, conn) do 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 end 
 
 def handle_authentication({:error, error}, conn) do 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end @spec authenticate({:ok, String.t} | {:error, atom}) :: … {:ok, token} @spec authenticate(String.t) :: …X
  • 39. def call(conn, _opts %{}) do 
 conn 
 |> get_req_header("authorization") 
 |> get_token() 
 |> Auth.authenticate() 
 |> handle_authentication(conn) 
 end 
 
 def handle_authentication({:ok, session: session, user: user}, conn) do 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 end 
 
 def handle_authentication({:error, error}, conn) do 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end {:ok, token} @spec authenticate(String.t) :: …X @spec authenticate({:ok, String.t} | {:error, atom}) :: …Please fix.
  • 40. 😕
  • 41. 😯
  • 42. def call(conn, _opts %{}) do 
 authorization_headers = get_req_header(conn, "authorization") 
 {:ok, token} = get_token(authorization_headers) 
 {:ok, session: session, user: user} = Auth.authenticate(token) 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) end
  • 43. def call(conn, _opts %{}) do 
 with authorization_headers <- get_req_header(conn, "authorization"), 
 {:ok, token} <- get_token(authorization_headers), 
 {:ok, session: session, user: user} <- Auth.authenticate(token) 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) end
  • 44. def call(conn, _opts %{}) do 
 with authorization_headers <- get_req_header(conn, "authorization"), 
 {:ok, token} <- get_token(authorization_headers), 
 {:ok, session: session, user: user} <- Auth.authenticate(token) 
 do conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) end end
  • 45. def call(conn, _opts %{}) do 
 with authorization_headers <- get_req_header(conn, "authorization"), 
 {:ok, token} <- get_token(authorization_headers), 
 {:ok, session: session, user: user} <- Auth.authenticate(token) 
 do conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session)
 else pattern -> instructions pattern -> instructions pattern -> instructions end end
  • 46. def call(conn, _opts %{}) do 
 with authorization_headers <- get_req_header(conn, "authorization"), 
 {:ok, token} <- get_token(authorization_headers), 
 {:ok, session: session, user: user} <- Auth.authenticate(token) 
 do conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session)
 else end end
  • 47. def call(conn, _opts %{}) do 
 with authorization_headers <- get_req_header(conn, "authorization"), 
 {:ok, token} <- get_token(authorization_headers), 
 {:ok, session: session, user: user} <- Auth.authenticate(token) 
 do 
 conn 
 |> assign(:current_user, user) 
 |> assign(:current_session, session) 
 else {:error, _reason} -> 
 conn 
 |> put_status(:unauthorized) 
 |> render(ErrorView, "401.json", %{}) 
 |> halt() 
 end 
 end 👍
  • 49. Scénario 2 GET ?page=0 [{…}, {…}, {…}, {…}] Serveur Prestataire
  • 50. Scénario 2 GET ?page=0 [{…}, {…}, {…}, {…}] Serveur Prestataire GET ?page=1 [{…}, {…}]
  • 51. Scénario 2 GET ?page=0 [{…}, {…}, {…}, {…}] Serveur Prestataire GET ?page=1 [{…}, {…}] GET ?page=2 []
  • 52. defmodule Client 
 def resources(page) do 
 ... 
 end 
 end
  • 53. [%{…}, %{…}, %{…}] defmodule Client 
 def resources(page) do 
 ... 
 end 
 end
  • 55. def get_resources do 
 page = 0 
 acc = [] end
  • 56. def get_resources do 
 page = 0 
 acc = [] 
 do 
 result = Client.resources(page) page = page + 1
 acc = acc ++ result 
 while(result != []) 
 
 acc 
 end 

  • 60. def get_resources do do_get_resources(0, Client.resources(0))
 end
  • 61. def get_resources do 
 do_get_resources(0, Client.resources(0)) 
 end 
 
 defp do_get_resources(_page, []), 
 do: :ok
  • 62. def get_resources do 
 do_get_resources(0, Client.resources(0)) 
 end 
 
 defp do_get_resources(_page, []), 
 do: :ok
 defp do_get_resources(page, resources), 
 do: do_get_resources( page + 1, Client.resources(page + 1) )
  • 63. def get_resources do 
 do_get_resources(0, Client.resources(0)) 
 end 
 
 defp do_get_resources(_page, []), 
 do: :ok
 defp do_get_resources(page, resources), 
 do: do_get_resources( page + 1, Client.resources(page + 1) )
  • 64. def get_resources do 
 do_get_resources([], 0, Client.resources(0)) 
 end 
 
 defp do_get_resources(acc, _page, []), 
 do: acc
 defp do_get_resources(acc, page, resources), 
 do: do_get_resources( acc + resources, page + 1, Client.resources(page + 1) )
  • 65. def get_resources do 
 do_get_resources([], 0, Client.resources(0)) 
 end 
 
 defp do_get_resources(acc, _page, []), 
 do: acc
 defp do_get_resources(acc, page, resources), 
 do: do_get_resources( acc + resources, page + 1, Client.resources(page + 1) )
  • 75. def get_resources do 
 Stream.unfold()
 end
  • 76. unfold(initial_acc, (acc -> {element, acc} | nil))
  • 77. def get_resources do 
 Stream.unfold()
 end
  • 78. def get_resources do 
 Stream.unfold(0, fn page -> 
 end) 
 end
  • 79. def get_resources do 
 Stream.unfold(0, fn page ->
 resources = Client.resources(page) 
 {resources, page + 1} 
 end) 
 end
  • 80. def get_resources do 
 Stream.unfold(0, fn page -> 
 case Client.resources(page) do 
 [] -> nil 
 resources -> {resources, page + 1} 
 end 
 end) 
 end
  • 81. get_resources |> Enum.to_list() [
 [ %{id: 1, …}, %{id: 2, …}, %{id: 3, …} ], 
 [ %{id: 4, …}, %{id: 5, …}, %{id: 6, …} ], 
 [ %{id: 7, …}, %{id: 8, …}, %{id: 9, …} ] 
 ] iex>
  • 82. def get_resources do 
 Stream.unfold(0, fn page -> 
 case Client.resources(page) do 
 [] -> nil 
 resources -> {resources, page + 1} 
 end 
 end)
 end
  • 83. def get_resources do 
 Stream.unfold(0, fn page -> 
 case Client.resources(page) do 
 [] -> nil 
 resources -> {resources, page + 1} 
 end 
 end) |> Stream.flat_map(& &1) 
 end
  • 84. [
 %{id: 1, …}, %{id: 2, …}, %{id: 3, …},
 %{id: 4, …}, %{id: 5, …}, %{id: 6, …}, 
 %{id: 7, …}, %{id: 8, …}, %{id: 9, …}
 ] get_resources |> Enum.to_list()iex>
  • 85. 👍def get_resources do 
 Stream.unfold(0, fn page -> 
 case Client.resources(page) do 
 [] -> nil 
 resources -> {resources, page + 1} 
 end 
 end) |> Stream.flat_map(& &1) 
 end
  • 87.
  • 88.
  • 89.
  • 90.
  • 92. image: elixir:1.5.1 services: - postgres:9.6 variables: MIX_ENV: test stages: - build - test - package - deploy before_script: - mix local.hex --force - mix local.rebar --force - mix deps.get --only test cache: paths: - "_build" - "deps" .gitlab-ci.yml
  • 93. image: elixir:1.5.1 services: - postgres:9.6 variables: MIX_ENV: test stages: - build - test - package - deploy before_script: - mix local.hex --force - mix local.rebar --force - mix deps.get --only test cache: paths: - "_build" - "deps" .gitlab-ci.yml
  • 94. image: elixir:1.5.1 services: - postgres:9.6 variables: MIX_ENV: test stages: - build - test - package - deploy before_script: - mix local.hex --force - mix local.rebar --force - mix deps.get --only test cache: paths: - "_build" - "deps" .gitlab-ci.yml
  • 95. image: elixir:1.5.1 services: - postgres:9.6 variables: MIX_ENV: test stages: - build - test - package - deploy before_script: - mix local.hex --force - mix local.rebar --force - mix deps.get --only test cache: paths: - "_build" - "deps" .gitlab-ci.yml
  • 96. build: stage: build script: - mix compile doc: stage: build script: - mix docs .gitlab-ci.yml
  • 97. .gitlab-ci.yml lint: stage: test script: - mix credo - mix dialyzer --halt-exit-status migrations: stage: test script: - mix ecto.reset - mix ecto.rollback --all test: stage: test script: - mix ecto.reset - mix test
  • 98. .gitlab-ci.yml lint: stage: test script: - mix credo - mix dialyzer --halt-exit-status migrations: stage: test script: - mix ecto.reset - mix ecto.rollback --all test: stage: test script: - mix ecto.reset - mix test
  • 99. .gitlab-ci.yml lint: stage: test script: - mix credo - mix dialyzer --halt-exit-status migrations: stage: test script: - mix ecto.reset - mix ecto.rollback --all test: stage: test script: - mix ecto.reset - mix test
  • 100. .gitlab-ci.yml lint: stage: test script: - mix credo - mix dialyzer --halt-exit-status migrations: stage: test script: - mix ecto.reset - mix ecto.rollback --all test: stage: test script: - mix ecto.reset - mix test
  • 103. .gitlab-ci.yml deploy:integration: stage: deploy environment: name: $CI_BUILD_REF_NAME url: https://$CI_BUILD_REF_NAME.integration only: - master@trustbk/backend script: - bin/deploy.sh
  • 104. .gitlab-ci.yml deploy:integration: stage: deploy environment: name: $CI_BUILD_REF_NAME url: https://$CI_BUILD_REF_NAME.integration only: - master@trustbk/backend script: - bin/deploy.sh
  • 105. .gitlab-ci.yml deploy:integration: stage: deploy environment: name: $CI_BUILD_REF_NAME url: https://$CI_BUILD_REF_NAME.integration only: - master@trustbk/backend script: - bin/deploy.sh
  • 107. bin/package.sh distillery A pure Elixir implementation of release packaging functionality for the Erlang VM.
  • 108. rel/config.exs Path.join(["rel", "plugins", "*.exs"]) |> Path.wildcard() |> Enum.map(&Code.eval_file(&1)) use Mix.Releases.Config, default_release: :backend, default_environment: Mix.env() environment :prod do set include_erts: true set include_src: false set include_system_libs: true end release :backend do set version: current_version(:backend) set applications: [ :backend, … ] end
  • 109. bin/package.sh $ mix release ==> Assembling release.. ==> Building release backend:0.0.1 using environment prod ==> Including ERTS 9.0.4 from /usr/local/Cellar/erlang/20.0/lib/erlang/erts-9.0.4 ==> Packaging release.. ==> Release successfully built! You can run it in one of the following ways: Interactive: _build/prod/rel/backend/bin/backend console Foreground: _build/prod/rel/backend/bin/backend foreground Daemon: _build/prod/rel/backend/bin/backend start
  • 111. bin/package.sh $ tree -L 2 _build/prod/rel/backend _build/prod/rel/backend ├── bin │   ├── backend │   ├── backend.bat │   ├── backend_loader.sh │   ├── release_utils.escript │   └── start_clean.boot ├── erts-9.0 │   ├── bin │   ├── include │   ├── lib │   └── src ├── lib │   ├── backend-0.0.1 │   └── […dependencies…] ├── releases │   ├── 0.0.1 │   ├── RELEASES │   └── start_erl.data └── var ├── WARNING_README ├── log ├── sys.config └── vm.args bin/backend start bin/backend stop
  • 112. bin/package.sh #!/bin/bash -ex MIX_ENV=prod mix release —warnings-as-errors […preparing a tarball from _build/prod/rel/backend]
  • 113. bin/package.sh #!/bin/bash -ex MIX_ENV=prod mix release —warnings-as-errors […preparing a tarball from _build/prod/rel/backend]
  • 115. lib/backend/release_tasks.ex defmodule Elixir.Backend.ReleaseTasks do alias Backend.Repo alias Ecto.Migrator @start_apps [:ecto, :postgrex] def migrate do :ok = Application.load(:backend) Enum.each(@start_apps, &Application.ensure_all_started/1) {:ok, _} = Repo.start_link(pool_size: 1) path = Application.app_dir(:backend, "priv/repo/migrations") Migrator.run(Repo, path, :up, all: true) Enum.each(@start_apps, &Application.stop/1) :init.stop() end end
  • 116. lib/backend/release_tasks.ex defmodule Elixir.Backend.ReleaseTasks do alias Backend.Repo alias Ecto.Migrator @start_apps [:ecto, :postgrex] def migrate do :ok = Application.load(:backend) Enum.each(@start_apps, &Application.ensure_all_started/1) {:ok, _} = Repo.start_link(pool_size: 1) path = Application.app_dir(:backend, "priv/repo/migrations") Migrator.run(Repo, path, :up, all: true) Enum.each(@start_apps, &Application.stop/1) :init.stop() end end
  • 117. lib/backend/release_tasks.ex defmodule Elixir.Backend.ReleaseTasks do alias Backend.Repo alias Ecto.Migrator @start_apps [:ecto, :postgrex] def migrate do :ok = Application.load(:backend) Enum.each(@start_apps, &Application.ensure_all_started/1) {:ok, _} = Repo.start_link(pool_size: 1) path = Application.app_dir(:backend, "priv/repo/migrations") Migrator.run(Repo, path, :up, all: true) Enum.each(@start_apps, &Application.stop/1) :init.stop() end end
  • 119. rel/config.exs release :backend do set version: current_version(:backend) set applications: [ :backend, … ] set commands: [ "migrate": "rel/commands/migrate.sh" ] end
  • 121. config/prod.exs use Mix.Config config :backend, Backend.Repo, adapter: Ecto.Adapters.Postgres, username: System.get_env("TBK_BACKEND_DB_USER"), password: System.get_env("TBK_BACKEND_DB_PASSWORD"), database: System.get_env("TBK_BACKEND_DB_DATABASE"), hostname: System.get_env("TBK_BACKEND_DB_HOSTNAME"), pool_size: 10 Evaluated at compile-time!
  • 122. config/prod.exs use Mix.Config config :backend, Backend.Repo, adapter: Ecto.Adapters.Postgres, username: "${TBK_BACKEND_DB_USER}", password: "${TBK_BACKEND_DB_PASSWORD}", database: "${TBK_BACKEND_DB_DATABASE}", hostname: "${TBK_BACKEND_DB_HOSTNAME}", pool_size: 10 Evaluated at runtime by Erlang VM with REPLACE_OS_VARS=true
  • 123. bin/deploy.sh […copy tarball to server by SSH] […expand] vcour/bin/backend stop ln -sfT versions/20170921102241_8f41acc1 vcour vcour/bin/backend migrate vcour/bin/backend start
  • 124. • Idempotent • Stateless • Zero-downtime deployment bin/deploy.sh
  • 125.