Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Elixir Paris Meetup

Avatar for Joan Joan
September 21, 2017

Elixir Paris Meetup

Avatar for Joan

Joan

September 21, 2017
Tweet

Other Decks in Programming

Transcript

  1. TrustBK Cible les PME From scratch, pas de dette technique

    Repense la relation banquier-client Confiance, technologie, éthique
  2. Nos premiers mois • Découvrir le langage et son écosystème

    • Trouver nos bonnes pratiques • Apprendre à tester et à livrer en production
  3. 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
  4. defmodule BackendWeb.Router do 
 scope "/api", BackendWeb do 
 get

    "/accounts/:id", AccountsController, :get_one 
 end 
 end
  5. defmodule BackendWeb.Router do 
 
 
 
 
 scope "/api",

    BackendWeb do 
 get "/accounts/:id", AccountsController, :get_one 
 end 
 end
  6. defmodule BackendWeb.Router do 
 pipeline :authenticate do 
 plug Backend.Plug.Authenticate

    
 end 
 
 scope "/api", BackendWeb do 
 get "/accounts/:id", AccountsController, :get_one 
 end 
 end
  7. 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
  8. def call(conn, _opts \\ %{}) do 
 authorization_headers = get_req_header(conn,

    "authorization") 
 {:ok, token} = get_token(authorization_headers) 
 end
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. |>

  22. 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
  23. 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
  24. def call(conn, _opts \\ %{}) do 
 conn 
 |>

    get_req_header("authorization") 
 |> get_token() 
 |> Auth.authenticate() 
 |> handle_authentication(conn) 
 end
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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.
  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 
 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. def get_resources do 
 page = 0 
 acc =

    [] 
 do 
 result = Client.resources(page) page = page + 1
 acc = acc ++ result 
 while(result != []) 
 
 acc 
 end 

  38. 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) )
  39. 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) )
  40. 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) )
  41. 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) )
  42. def get_resources do 
 Stream.unfold(0, fn page ->
 resources =

    Client.resources(page) 
 {resources, page + 1} 
 end) 
 end
  43. def get_resources do 
 Stream.unfold(0, fn page -> 
 case

    Client.resources(page) do 
 [] -> nil 
 resources -> {resources, page + 1} 
 end 
 end) 
 end
  44. get_resources |> Enum.to_list() [
 [ %{id: 1, …}, %{id: 2,

    …}, %{id: 3, …} ], 
 [ %{id: 4, …}, %{id: 5, …}, %{id: 6, …} ], 
 [ %{id: 7, …}, %{id: 8, …}, %{id: 9, …} ] 
 ] iex>
  45. def get_resources do 
 Stream.unfold(0, fn page -> 
 case

    Client.resources(page) do 
 [] -> nil 
 resources -> {resources, page + 1} 
 end 
 end)
 end
  46. 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
  47. [
 %{id: 1, …}, %{id: 2, …}, %{id: 3, …},


    %{id: 4, …}, %{id: 5, …}, %{id: 6, …}, 
 %{id: 7, …}, %{id: 8, …}, %{id: 9, …}
 ] get_resources |> Enum.to_list() iex>
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. .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
  54. .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
  55. .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
  56. .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
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. rel/config.exs release :backend do set version: current_version(:backend) set applications: [

    :backend, … ] set commands: [ "migrate": "rel/commands/migrate.sh" ] end
  64. 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!
  65. 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
  66. 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