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

Real Time Web Application with Elixir and Phoenix

kanmo
July 10, 2016

Real Time Web Application with Elixir and Phoenix

Developers.IO 2016 in Sapporoの発表資料です

kanmo

July 10, 2016
Tweet

More Decks by kanmo

Other Decks in Technology

Transcript

  1. self introduction me |> name # Akihide Kan |> job

    # Software Engineer |> work_at # Classmethod, Inc. |> works # Ruby |> twitter # @kanmo
  2. Agenda • Elixir • Realtime Web • WebSocket • Phoenix

    • Channel • Sample Application • Demo
  3. Features of Elixir • Pattern Matching • |> (Pipe Operator)

    • Immutable Data • Actor Model • OTP • Let it Crash
  4. Pattern Matching %{:name => company} = %{:name => “Classmethod”} IO.puts(company)

    # => Classmethod ࠨลͷ.BQͷLFZOBNF ӈลͷ.BQͷLFZOBNF
  5. Pattern Matching %{:name => company} = %{:name => “Classmethod”} IO.puts(company)

    # => Classmethod ࠨลͷ.BQͷLFZOBNF ӈลͷ.BQͷLFZOBNF Ұகͨ͠৔߹ ʹม਺DPNQBOZ ʹz$MBTTNFUIPEΛଋറ
  6. Pattern Matching + Recursion defmodule MyList do def size([head|tail]), do:

    1 + size(tail) def size([]), do: 0 end MyList.size([“Elixir”, “Ruby”, “Java”]) # => 3ʢཁૉ਺͕ฦ٫͞ΕΔʣ
  7. Pattern Matching + Recursion defmodule MyList do def size([head|tail]), do:

    1 + size(tail) def size([]), do: 0 end MyList.size([“Elixir”, “Ruby”, “Java”]) # => 3ʢཁૉ਺͕ฦ٫͞ΕΔʣ IFBEʹϦετͷઌ಄ ཁૉz&MJYJSz͕ଋറ
  8. Pattern Matching + Recursion defmodule MyList do def size([head|tail]), do:

    1 + size(tail) def size([]), do: 0 end MyList.size([“Elixir”, “Ruby”, “Java”]) # => 3ʢཁૉ਺͕ฦ٫͞ΕΔʣ IFBEʹϦετͷઌ಄ ཁૉz&MJYJSz͕ଋറ UBJMʹ࢒Γͷཁૉ <z3VCZz z+BWBz>͕ଋറ
  9. Pattern Matching + Recursion defmodule MyList do def size([head|tail]), do:

    1 + size(tail) def size([]), do: 0 end MyList.size([“Elixir”, “Ruby”, “Java”]) # => 3ʢཁૉ਺͕ฦ٫͞ΕΔʣ IFBEʹϦετͷઌ಄ ཁૉz&MJYJSz͕ଋറ UBJMʹ࢒Γͷཁૉ <z3VCZz z+BWBz>͕ଋറ ΛՃࢉͯ͠ ࠶ؼݺͼग़͠
  10. Pattern Matching + Recursion defmodule MyList do def size([head|tail]), do:

    1 + size(tail) def size([]), do: 0 end MyList.size([“Elixir”, “Ruby”, “Java”]) # => 3ʢཁૉ਺͕ฦ٫͞ΕΔʣ IFBEʹϦετͷઌ಄ ཁૉz&MJYJSz͕ଋറ UBJMʹ࢒Γͷཁૉ <z3VCZz z+BWBz>͕ଋറ ΛՃࢉͯ͠ ࠶ؼݺͼग़͠ ཁૉ͕ۭʹͳͬͨΒऴྃ
  11. |> Pipe Operator 1..10 |> Enum.map(fn(x) -> x * x

    end) |> Enum.filter(fn(x) -> x < 40 end) |> IO.inspect # => [1,4,9,16,25,36] ؔ਺ͷฦ٫஋Λ࣍ͷؔ਺ͷ ୈҰҾ਺ʹ౉͢
  12. |> Pipe Operator defmodule Sample do def fetch_entry() do HTTPoison.get!(“http://b.hatena.ne.jp/entrylist/json?

    sort=count&url=http://dev.classmethod.jp”) |> decode_response |> extract_entry |> sort_descending_and_format end end
  13. |> Pipe Operator defmodule Sample do def fetch_entry() do HTTPoison.get!(“http://b.hatena.ne.jp/entrylist/json?

    sort=count&url=http://dev.classmethod.jp”) |> decode_response |> extract_entry |> sort_descending_and_format end end ͸ͯͿ"1*Λୟ͘
  14. |> Pipe Operator defmodule Sample do def fetch_entry() do HTTPoison.get!(“http://b.hatena.ne.jp/entrylist/json?

    sort=count&url=http://dev.classmethod.jp”) |> decode_response |> extract_entry |> sort_descending_and_format end end ͸ͯͿ"1*Λୟ͘ औಘ݁ՌΛ+40/ʹσίʔυ
  15. |> Pipe Operator defmodule Sample do def fetch_entry() do HTTPoison.get!(“http://b.hatena.ne.jp/entrylist/json?

    sort=count&url=http://dev.classmethod.jp”) |> decode_response |> extract_entry |> sort_descending_and_format end end ͸ͯͿ"1*Λୟ͘ औಘ݁ՌΛ+40/ʹσίʔυ λΠτϧͱϒΫϚ਺Λऔಘ
  16. |> Pipe Operator defmodule Sample do def fetch_entry() do HTTPoison.get!(“http://b.hatena.ne.jp/entrylist/json?

    sort=count&url=http://dev.classmethod.jp”) |> decode_response |> extract_entry |> sort_descending_and_format end end ͸ͯͿ"1*Λୟ͘ औಘ݁ՌΛ+40/ʹσίʔυ λΠτϧͱϒΫϚ਺Λऔಘ ࠷ޙʹιʔτͱจࣈྻ੔ܗ
  17. ίωΫγϣϯ։࢝ͷϦΫΤετ GET /resource HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade

    Sec-WebSocket-Version: 13 Sec-WebSocket-Key: kH/XISD+rj+uGkkQHBv/Dw== Ϩεϙϯε HTTP/1.1 101 OK Connection: Upgrade Upgrade: websocket sec-websocket-accept: ZCk6jJdyY474YOdCQNnFwjItvV0=
  18. ίωΫγϣϯ։࢝ͷϦΫΤετ GET /resource HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade

    Sec-WebSocket-Version: 13 Sec-WebSocket-Key: kH/XISD+rj+uGkkQHBv/Dw== Ϩεϙϯε HTTP/1.1 101 OK Connection: Upgrade Upgrade: websocket sec-websocket-accept: ZCk6jJdyY474YOdCQNnFwjItvV0= 6QHSBEFϔομͱ $POOFDUJPOϔομɻ )551͔ΒͷΞοϓάϨʔυΛҙຯ͢Δ
  19. ίωΫγϣϯ։࢝ͷϦΫΤετ GET /resource HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade

    Sec-WebSocket-Version: 13 Sec-WebSocket-Key: kH/XISD+rj+uGkkQHBv/Dw== Ϩεϙϯε HTTP/1.1 101 OK Connection: Upgrade Upgrade: websocket sec-websocket-accept: ZCk6jJdyY474YOdCQNnFwjItvV0= 6QHSBEFϔομͱ $POOFDUJPOϔομɻ )551͔ΒͷΞοϓάϨʔυΛҙຯ͢Δ 4UBUVT ͸TXJUDIJOHQSPUPDPMT ͷҙຯɻ
  20. ֤ݴޠͷFWͱͷൺֱ ʢࢀߟ·Ͱʹʣhttps://gist.github.com/omnibs/e5e72b31e6bd25caf39a 'SBNFXPSL 5ISPVHIQVU SFRT -BUFODZ NT $POTJTUFODZ МNT 1IPFOJY

        1MBZ    &YQSFTT $MVTUFS    3BJMT   
  21. Request Pipeline Connection |> endpoint |> router |> pipeline |>

    controller ϦΫΤετ͕ಧ͘ ϦΫΤετΛৼΓ෼͚Δ
  22. Request Pipeline Connection |> endpoint |> router |> pipeline |>

    controller ϦΫΤετ͕ಧ͘ ϦΫΤετΛৼΓ෼͚Δ ڞ௨ॲཧΛϦΫΤε τʹରͯ͠ߦ͏
  23. Request Pipeline Connection |> endpoint |> router |> pipeline |>

    controller ϦΫΤετ͕ಧ͘ ϦΫΤετΛৼΓ෼͚Δ ڞ௨ॲཧΛϦΫΤε τʹରͯ͠ߦ͏ "DUJPOʹରԠ͢Δϩ δοΫΛ࣮ߦ
  24. endpoint.ex defmodule Hello.Endpoint do use Phoenix.Endpoint, otp_app: :hello plug Plug.Static,

    ... plug Plug.RequestId plug Plug.Logger plug Plug.Parsers, ... plug Plug.MethodOverride plug Plug.Head plug Plug.Session, ... plug Hello.Router end
  25. endpoint.ex defmodule Hello.Endpoint do use Phoenix.Endpoint, otp_app: :hello plug Plug.Static,

    ... plug Plug.RequestId plug Plug.Logger plug Plug.Parsers, ... plug Plug.MethodOverride plug Plug.Head plug Plug.Session, ... plug Hello.Router end ॲཧ͢Δ1MVHΛࢦఆ
  26. router.ex defmodule Hello.Router do use Hello.Web, :router pipeline :browser do

    plug :accepts, [“html”] plug :fetch_session (লུ) end scope “/“ Hello do pipe_through :browser get “/“, PageController, :index end end
  27. router.ex defmodule Hello.Router do use Hello.Web, :router pipeline :browser do

    plug :accepts, [“html”] plug :fetch_session (লུ) end scope “/“ Hello do pipe_through :browser get “/“, PageController, :index end end ॲཧ͢Δ1MVH ؔ਺ Λࢦఆ
  28. router.ex defmodule Hello.Router do use Hello.Web, :router pipeline :browser do

    plug :accepts, [“html”] plug :fetch_session (লུ) end scope “/“ Hello do pipe_through :browser get “/“, PageController, :index end end ॲཧ͢Δ1MVH ؔ਺ Λࢦఆ ࢦఆͨ͠1MVHͷू·ΓΛ͜ͷϧʔ τͷϦΫΤετʹׂΓ౰ͯΔ
  29. ࠶ىಈͷ༗ແΛઃఆ defmodule Hello.ApiClient.Supervisor do use Supervisor def start_link() do Supervisor.start_link(__MODULE__,

    [], name: __MODULE__) end def init(_opts) do children = [worker(Hello.ApiClient, [], restart: :temporary)] supervise children, strategy, :simple_one_for_one end end
  30. ࠶ىಈͷ༗ແΛઃఆ defmodule Hello.ApiClient.Supervisor do use Supervisor def start_link() do Supervisor.start_link(__MODULE__,

    [], name: __MODULE__) end def init(_opts) do children = [worker(Hello.ApiClient, [], restart: :temporary)] supervise children, strategy, :simple_one_for_one end end ࠓճͷ৔߹͸ Τϥʔ͕ൃੜͯ͠΋࠶ىಈ͠ ͳ͍Α͏ʹઃఆ
  31. ࠶ىಈͷ༗ແΛઃఆ defmodule Hello.ApiClient.Supervisor do use Supervisor def start_link() do Supervisor.start_link(__MODULE__,

    [], name: __MODULE__) end def init(_opts) do children = [worker(Hello.ApiClient, [], restart: :temporary)] supervise children, strategy, :simple_one_for_one end end ࠓճͷ৔߹͸ Τϥʔ͕ൃੜͯ͠΋࠶ىಈ͠ ͳ͍Α͏ʹઃఆ ͜͜͸ࢠϓϩηεͷ؂ࢹ ͷઃఆ
  32. Timeoutઃఆ def await(childeren, opts) do timeout = opts[:timeout] || 5000

    timer = Process.send_after(self(), :timedout, timeout) .. end def await([head|tail], acc, timeout) do .. receive do :timeout -> kill(pid, monitor_ref) await(tail, acc, 0) end
  33. Timeoutઃఆ def await(childeren, opts) do timeout = opts[:timeout] || 5000

    timer = Process.send_after(self(), :timedout, timeout) .. end def await([head|tail], acc, timeout) do .. receive do :timeout -> kill(pid, monitor_ref) await(tail, acc, 0) end ࢠϓϩηεͷUJNFPVUͷ ઃఆ
  34. Timeoutઃఆ def await(childeren, opts) do timeout = opts[:timeout] || 5000

    timer = Process.send_after(self(), :timedout, timeout) .. end def await([head|tail], acc, timeout) do .. receive do :timeout -> kill(pid, monitor_ref) await(tail, acc, 0) end ࢠϓϩηεͷUJNFPVUͷ ઃఆ UJNFPVU͕ൃੜ ͨ͠ΒͦͷࢠϓϩηεΛLJMMͯ͠ ॲཧΛܧଓ
  35. Messages • topic: τϐοΫ͔τϐοΫ:αϒτϐοΫͷϖΞͷ໊લ ۭؒʢ”messages” or “messages:123) • event: จࣈྻͷΠϕϯτ໊ʢ”join”ʣ

    • payload: jsonܗࣜͷจࣈྻϝοηʔδ • ref: ड৴ͨ͠ΠϕϯτͷԠ౴ʹ࢖͏ϢχʔΫͳจࣈྻ
  36. # lib/hello/endpoint.ex defmodule Hello.Endpoint do use Phoenix.Endopoint socket “/socket”, Hello.UserSocket

    . . . end # web/channels/user_socket.ex defmodule Hello.UserSocket do use Phoenix.Socket channel “rooms:*”, Hello.RoomChannel . . . end ιέοτΛϚ΢ϯτ
  37. # lib/hello/endpoint.ex defmodule Hello.Endpoint do use Phoenix.Endopoint socket “/socket”, Hello.UserSocket

    . . . end # web/channels/user_socket.ex defmodule Hello.UserSocket do use Phoenix.Socket channel “rooms:*”, Hello.RoomChannel . . . end ιέοτΛΤϯυϙΠϯτʹ Ϛ΢ϯτ ιέοτΛϚ΢ϯτ
  38. # lib/hello/endpoint.ex defmodule Hello.Endpoint do use Phoenix.Endopoint socket “/socket”, Hello.UserSocket

    . . . end # web/channels/user_socket.ex defmodule Hello.UserSocket do use Phoenix.Socket channel “rooms:*”, Hello.RoomChannel . . . end ιέοτΛΤϯυϙΠϯτʹ Ϛ΢ϯτ ΫϥΠΞϯτ͕zSPPNTz͔Β࢝ ·ΔτϐοΫͷϝοηʔδΛૹΔͱɺ 3PPN$IBOOFM΁ૹΒΕΔ ιέοτΛϚ΢ϯτ
  39. # web/channels/room_channel.ex defmodule Hello.RoomChannel do use Phoenix.Channel def join(“rooms:lobby”, auth_msg,

    socket) do {:ok, socket} end def end(“rooms:” <> _private_room_id, _auth_msg, socket) do {:error, %{reason: “unauthorized”}} end end τϐοΫʹࢀՃ
  40. # web/channels/room_channel.ex defmodule Hello.RoomChannel do use Phoenix.Channel def join(“rooms:lobby”, auth_msg,

    socket) do {:ok, socket} end def end(“rooms:” <> _private_room_id, _auth_msg, socket) do {:error, %{reason: “unauthorized”}} end end lSPPNTMPCCZzτϐοΫ ʹࢀՃ͢Δ τϐοΫʹࢀՃ
  41. # web/static/js/chat.js import {Socket} from “phoenix” let socket = new

    Socket(“/socket”) socket.connect let channel = socket.channel(“rooms:lobby”, {}) channel.join() .receive(“ok”, resp => { console.log(“Welcome to Chat!” }) ΫϥΠΞϯτ͔ΒτϐοΫʹࢀՃ
  42. # web/static/js/chat.js import {Socket} from “phoenix” let socket = new

    Socket(“/socket”) socket.connect let channel = socket.channel(“rooms:lobby”, {}) channel.join() .receive(“ok”, resp => { console.log(“Welcome to Chat!” }) ΫϥΠΞϯτ͔ΒτϐοΫʹࢀՃ ιέοτΛੜ੒ͯ͠ɺαʔ όʹ઀ଓ
  43. # web/static/js/chat.js import {Socket} from “phoenix” let socket = new

    Socket(“/socket”) socket.connect let channel = socket.channel(“rooms:lobby”, {}) channel.join() .receive(“ok”, resp => { console.log(“Welcome to Chat!” }) ΫϥΠΞϯτ͔Β DIBOOFMʹ઀ଓ ΫϥΠΞϯτ͔ΒτϐοΫʹࢀՃ ιέοτΛੜ੒ͯ͠ɺαʔ όʹ઀ଓ
  44. . . . chatInput.on("keypress", event => { if(event.keyCode === 13){

    channel.push(new_msg", {body: chatInput.val()}) chatInput.val("") } }) ΫϥΠΞϯτ͔ΒϝοηʔδΛૹ৴
  45. . . . chatInput.on("keypress", event => { if(event.keyCode === 13){

    channel.push(new_msg", {body: chatInput.val()}) chatInput.val("") } }) ઀ଓ͍ͯ͠ΔDIBOOFMʹ ϝοηʔδΛૹ৴ ΫϥΠΞϯτ͔ΒϝοηʔδΛૹ৴
  46. αʔόͰϝοηʔδड৴ αʔό͔ΒશΫϥΠΞϯτ΁broadcast # web/channels/room_channel.ex defmodule Toy.RoomChannel do use Phoenix.Channel .

    . . def handle_in("new_msg", %{"body" => body}, socket) do broadcast! socket, "new_msg", %{body: body} {:noreply, socket} end def handle_out("new_msg", payload, socket) do push socket, "new_msg", payload {:noreply, socket} end end
  47. αʔόͰϝοηʔδड৴ αʔό͔ΒશΫϥΠΞϯτ΁broadcast # web/channels/room_channel.ex defmodule Toy.RoomChannel do use Phoenix.Channel .

    . . def handle_in("new_msg", %{"body" => body}, socket) do broadcast! socket, "new_msg", %{body: body} {:noreply, socket} end def handle_out("new_msg", payload, socket) do push socket, "new_msg", payload {:noreply, socket} end end ΫϥΠΞϯτ͔Βͷ ϝοηʔδΛड৴
  48. αʔόͰϝοηʔδड৴ αʔό͔ΒશΫϥΠΞϯτ΁broadcast # web/channels/room_channel.ex defmodule Toy.RoomChannel do use Phoenix.Channel .

    . . def handle_in("new_msg", %{"body" => body}, socket) do broadcast! socket, "new_msg", %{body: body} {:noreply, socket} end def handle_out("new_msg", payload, socket) do push socket, "new_msg", payload {:noreply, socket} end end ΫϥΠΞϯτ͔Βͷ ϝοηʔδΛड৴ τϐοΫʹࢀՃ͠ ͍ͯΔશΫϥΠΞϯτʹ CSPBEDBTU
  49. . . . channel.on("new_msg", payload => { messagesContainer.append(`<br/>[${Date()}] ${payload.body}`) })

    αʔό͔Βͷϝοηʔδ Λड৴ɺը໘ʹදࣔ αʔό͔ΒͷϝοηʔδΛड৴