Introducing Elixir: Self-healing applications at ZOMG scale

Introducing Elixir: Self-healing applications at ZOMG scale

Elixir is a new functional language built on the Erlang VM. We’ll tour the Elixir language and it’s most important frameworks to discover what makes Elixir a great choice for building systems at ZOMG scale and how those systems stay up for so long with Wolverine level self-healing.

Elixir’s secret is in its Erlang heritage. When was the last time your telephone network went down? Never, right? That’s because most of the telephone network is written in Erlang. How does WhatsApp manage to support 1.2 billion users and deliver 42 billion messages a day with only around 50 engineers? Yes, Erlang.

Elixir takes the power and stability offered by Erlang and makes it more approachable, with a Ruby inspired syntax, simpler abstractions and some powerful tools which will enable you to build concurrent, fault-tolerant and distributed systems. We'll find out how this functional language is the most Object Oriented of all languages and how microservice architecture is built right in.

Challenge yourself to break out of your comfort zone and look at how other languages solve problems. You’ll become a better developer if you do.

196ab25f16dcfd37518a41ceb15e0da0?s=128

Andy Pike

June 10, 2017
Tweet

Transcript

  1. 5.
  2. 6.
  3. 7.
  4. 9.
  5. 13.
  6. 16.

    $ iex iex(1)> $ iex hello.exs iex(1)> Hello.world() "Hello World"

    iex(2)> h String.upcase Converts all characters in the given string to uppercase. ## Examples iex> String.upcase("abcd") "ABCD"
  7. 18.

    $ iex iex(1)> 1 + 1 2 iex(2)> 2 *

    3 6 iex(3)> 1.2 - 0.2 1.0 iex(4)> 1..10 1..10 iex(5)> "Hello World" "Hello World" iex(6)> :andy :andy
  8. 19.

    iex(1)> { :ok, 42, "next" } { :ok, 42, "next"

    } iex(2)> [1, 2, 3] [1, 2, 3] iex(3)> [1, 2, 3] ++ [4, 5, 6] [1, 2, 3, 4, 5, 6] iex(4)> info = %{ :name => "Andy", :town => "Woking" } %{name: "Andy", town: "Woking"} iex(5)> info[:name] "Andy" iex(6)> info.name "Andy"
  9. 20.

    person.exs defmodule Person do
 defstruct name: "", age: 0, email:

    "" end $ iex person.exs iex(1)> p1 = %Person{ name: "Andy", age: 25 } %Person{age: 25, email: "", name: "Andy"} iex(2)> p1.name "Andy" iex(3)> p2 = %Person{ p1 | email: "me@here.com" } %Person{age: 25, email: "me@here.com", name: "Andy"} iex(4)> p1.email ""
  10. 22.

    iex(1)> a = 1 1 iex(2)> a + 2 3

    iex(3)> a = 2 2 iex(4)> 2 = a 2 iex(5)> 3 = a ** (MatchError) no match of right hand side value: 2 iex(6)> ^a = 3 ** (MatchError) no match of right hand side value: 3
  11. 23.

    iex(1)> list = [1, 2, 3] [1, 2, 3] iex(2)>

    [x, y, z] = list [1, 2, 3] iex(3)> y 2 iex(4)> [1, b, c] = list [1, 2, 3] iex(5)> b 2 iex(6)> [3, t, 5] = list ** (MatchError) no match of right hand side value: [1, 2, 3]
  12. 24.

    iex(1)> success = { :ok, 42 } { :ok, 42

    } iex(2)> failure = { :error, "Oops" } { :error, "Oops" } iex(3)> { :ok, result } = success { :ok, 42 } iex(4)> result 42 iex(5)> { :ok, result } = failure ** (MatchError) no match of right hand side value: {:error, "Oops"} iex(6)> { _, result } = failure { :error, "Oops" }
  13. 25.

    iex(1)> list = [1, 2, 3] [1, 2, 3] iex(2)>

    [head|tail] = list [1, 2, 3] iex(3)> head 1 iex(4)> tail [2, 3] iex(5)> [head_2|tail_2] = tail [2, 3] iex(6)> head_2 2 iex(7)> tail_2 [3]
  14. 26.
  15. 27.

    defmodule Hello do def world do "Hello World" end def

    world(name) do "Hello #{name}" end end iex(1)> Hello.world "Hello World" iex(2)> Hello.world("Andy") "Hello Andy"
  16. 28.

    defmodule Hello do def world, do: greeting("World") def world(name), do:

    greeting("Hi", name) defp greeting(salutation \\ "Hello", name) do "#{salutation} #{name}" end end iex(1)> Hello.world "Hello World" iex(2)> Hello.world("Andy") "Hi Andy" iex(3)> Hello.greeting("Andy") ** (UndefinedFunctionError) function Hello.greeting/1 is undefined or private
  17. 29.

    defmodule Maths do def arithmetic(:add, a, b), do: a +

    b def arithmetic(:subtract, a, b), do: a - b end iex(1)> Maths.arithmetic(:add, 1, 2) 3 iex(2)> Maths.arithmetic(:subtract, 10, 2) 8 iex(2)> Maths.arithmetic(:multiply, 3, 3) ** (FunctionClauseError) no function clause matching…
  18. 30.

    defmodule MyList do def count([]), do: 0 def count([_ |

    tail]) do 1 + count(tail) end end iex(1)> MyList.count([]) 0 iex(2)> MyList.count([1, 2, 3]) 3
  19. 31.

    defmodule MyList do def count(list, total \\ 0) def count([],

    total), do: total def count([_ | tail], total) do count(tail, total + 1) end end iex(1)> MyList.count([]) 0 iex(2)> MyList.count([1, 2, 3]) 3
  20. 32.

    defmodule Guard do def what_is(x) when is_atom(x) do IO.puts "#{x}

    is an atom" end def what_is(x) when is_number(x) and x > 0 do IO.puts "#{x} is a positive number" end def what_is(_), do: IO.puts "Unknown" end iex(1)> Guard.what_is(10) 10 is a positive number iex(2)> Guard.what_is("Andy") Unknown
  21. 33.

    iex(1)> String.downcase("ANDY") "andy" iex(2)> String.ends_with?("Andy", "y") true iex(3)> Enum.map([1, 2,

    3], fn(x) -> x * x end) [1, 4, 9] iex(4)> Enum.map([1, 2, 3], &(&1 * &1)) [1, 4, 9] iex(5)> Map.has_key?(%{ name: "Andy" }, :name) true
  22. 35.

    text = " Hi Andy " text = String.downcase(text) text

    = String.strip(text) text = String.reverse(text) => "ydna ih" String.reverse(String.strip(String.downcase(text))) => "ydna ih"
  23. 36.

    text = " Hi Andy " text |> String.downcase |>

    String.strip |> String.reverse => "ydna ih"
  24. 37.
  25. 38.

    defmodule MyString do @doc ~S""" Converts a string to uppercase

    ## Examples iex> MyString.upcase("andy") "ANDY" """ def upcase(string) do String.upcase(string) end end
  26. 39.

    defmodule MyStringTest do use ExUnit.Case doctest MyString end $ mix

    test 1 test, 0 failures iex(1)> h MyString.upcase Converts a string to uppercase ## Examples iex> MyString.upcase("andy") "ANDY"
  27. 41.
  28. 42.

    iex(1)> pid = spawn(fn -> IO.puts "Hello World" end) Hello

    World #PID<0.82.0> iex(2)> Process.alive?(pid) false
  29. 43.

    defmodule Chat do def greet do IO.puts "Hello World" end

    end iex(1)> pid = spawn(Chat, :greet, []) Hello World #PID<0.82.0>
  30. 46.

    defmodule ChatWorker do def loop do receive do { :greet,

    name } -> IO.puts "Hi #{name}!" loop() end end end iex(1)> pid = spawn(ChatWorker, :loop, []) #PID<0.118.0> iex(2)> Process.alive?(pid) true
  31. 47.

    defmodule ChatWorker do def loop do receive do { :greet,

    name } -> IO.puts "Hi #{name}!" loop() end end end iex(3)> send pid, { :greet, "Andy" } Hi Andy! iex(4)> Process.alive?(pid) true
  32. 48.

    defmodule NothingWorker do def loop do receive do { :exit

    } -> IO.puts "Exiting…" _ -> loop() end end end iex(1)> pid = spawn(NothingWorker, :loop, []) iex(2)> send pid, { :covfefe } iex(3)> Process.alive?(pid) true
  33. 49.

    defmodule NothingWorker do def loop do receive do { :exit

    } -> IO.puts "Exiting…" _ -> loop() end end end iex(4)> send pid, { :exit } Exiting… iex(3)> Process.alive?(pid) false
  34. 50.
  35. 51.

    defmodule Counter do def init(count \\ 0) do loop(count) end

    def loop(count) do receive do { :add, number } -> count = count + number loop(count) { :current, sender } -> send(sender, { :ok, count }) loop(count) end end end
  36. 52.

    iex(1)> pid = spawn(Counter, :init, []) #PID<0.88.0> iex(2)> send(pid, {:add,

    1}) {:add, 1} iex(3)> send(pid, {:add, 2}) {:add, 2} iex(4)> send(pid, {:current, self()}) {:current, #PID<0.80.0>} iex(5)> flush() {:ok, 3} :ok
  37. 55.

    OTP

  38. 57.

    defmodule OTPCounter do use GenServer # Public API def start_link

    do GenServer.start_link(__MODULE__, :ok, []) end # Callbacks def init(:ok) do {:ok, 0} end end initial state
  39. 58.

    defmodule OTPCounter do # snip… # Public API def current(pid)

    do GenServer.call(pid, :current) end # Callbacks def handle_call(:current, from_pid, state) do {:reply, state, state} end end
  40. 59.

    defmodule OTPCounter do # snip… # Client API def add(pid,

    number) do GenServer.cast(pid, {:add, number}) end # Callbacks def handle_cast({:add, number}, state) do {:noreply, state + number} end end
  41. 60.

    # Public API def stop(pid) do GenServer.cast(pid, :stop) end #

    Callbacks def handle_cast(:stop, state) do {:stop, :normal, state} end def terminate(reason, state) do IO.puts "Server stopping with total #{state}" :ok end
  42. 61.

    iex(1)> {:ok, pid} = OTPCounter.start_link {:ok, #PID<0.94.0>} iex(2)> OTPCounter.current(pid) 0

    iex(3)> OTPCounter.add(pid, 1) :ok iex(4)> OTPCounter.add(pid, 1) :ok iex(5)> OTPCounter.current(pid) 2 iex(6)> OTPCounter.stop(pid) Server stopping with total 2 iex(7)> Process.alive?(pid) false
  43. 62.
  44. 64.

    Process 1 Process 2 Process 3 Process 4 Run Queue

    Current Process Until empty mailbox or 2000 reductions
  45. 71.
  46. 72.

    iex> self() #PID<0.80.0> iex> import Supervisor.Spec Supervisor.Spec iex> c =

    [worker(OTPCounter, [], [])] … iex> {_, sup} = Supervisor.start_link(c, strategy: :one_for_one) {:ok, #PID<0.90.0>} iex> {_, [_, counter]} = Process.info(sup, :links) {:links, [#PID<0.80.0>, #PID<0.91.0>]}
  47. 73.

    iex> OTPCounter.add(counter, 1) :ok iex> OTPCounter.current(counter) 1 iex> OTPCounter.crash(counter) [error]

    GenServer #PID<0.91.0> terminating… iex> {_, [_, counter]} = Process.info(sup, :links) {:links, [#PID<0.80.0>, #PID<0.95.0>]} iex> OTPCounter.current(counter) 0
  48. 76.

    Node :foo Core 1 Core n . . . Core

    1 Core n . . . Node :bar msg
  49. 77.

    $ iex --name foo@192.168.0.100 --cookie s3cr3t iex(foo@192.168.0.100)1> Node.connect(:"bar@192.168.0.101") true iex(foo@192.168.0.100)2>

    Node.list [:"bar@192.168.0.101"] iex(foo@192.168.0.100)3> c "chat_worker.exs" [ChatWorker] $ iex --name bar@192.168.0.101 --cookie s3cr3t iex(bar@192.168.0.101)1> Node.list [:"foo@192.168.0.100"] iex(bar@192.168.0.101)2> ChatWorker.loop ** (UndefinedFunctionError) function ChatWorker.loop/0 is undefined
  50. 78.

    $ iex --name foo@192.168.0.100 --cookie s3cr3t iex(foo@192.168.0.100)1> Node.connect(:"bar@192.168.0.101") true iex(foo@192.168.0.100)2>

    Node.list [:"bar@192.168.0.101"] iex(foo@192.168.0.100)3> c "chat_worker.exs" [ChatWorker] iex(bar@192.168.0.101)3> pid = Node.spawn_link :"foo@192.168.0.100", ChatWorker, :loop, [] #PID<9137.108.0> iex(bar@192.168.0.101)4> send(pid, {:greet, "Andy"}) Hi Andy!
  51. 79.
  52. 81.
  53. 82.

    Framework Throughput (req/s) Avg Latency (ms) Phoenix 31,417 3.52 Express

    9,477 10.56 Sinatra 8,334 7.46 Rails 3,452 17.96 https://github.com/mroth/phoenix-showdown/blob/master/RESULTS_v3.md
  54. 84.
  55. 86.
  56. 88.
  57. 91.

    "I thought of objects being like biological cells or individual

    computers on a network, only able to communicate with messages." - Alan Kay
  58. 92.
  59. 93.
  60. 95.