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

Elixir - Bydgoszcz Web Development Meetup

Elixir - Bydgoszcz Web Development Meetup

Part 1 - The success story
Part 2 - What is elixir?
Part 3 - Elixir Crash Course

Tymon Tobolski

November 23, 2016
Tweet

More Decks by Tymon Tobolski

Other Decks in Programming

Transcript

  1. elixir
    Bydgoszcz Web Development Meetup
    23.11.2016

    View Slide

  2. About me
    Tymon Tobolski
    • GitHub: teamon
    • Twitter: @iteamon
    • 8+ years with ruby
    • Elixir+Dev+Ops @ Recruitee.com

    View Slide

  3. http:/
    /github-awards.com/ !

    View Slide

  4. Part I - The success story
    Part II - What is elixir?
    Part III - Elixir Crash Course

    View Slide

  5. mid-August 2016

    View Slide

  6. HR panel Careers pages
    Landing pages

    View Slide

  7. The big split
    careers (rails)
    api (phoenix)
    frontend (webpack/ng2)
    legacy (rails)
    recruitee (rails)

    View Slide

  8. Start with
    fallback

    View Slide

  9. Use the same
    PostgreSQL
    database

    View Slide

  10. Extract
    microservices

    View Slide

  11. Use docker & rancher

    View Slide

  12. mid-November 2016
    It's alive!
    • 3 elixir devs
    • 14.000 elixir LOC
    • new infrastructure based on docker & rancher
    • 3 potential open-source libs

    View Slide

  13. Part I - The success story
    Part II - What is elixir?
    Part III - Elixir Crash Course

    View Slide

  14. What is elixir
    and why should I care?

    View Slide

  15. Elixir is a dynamic,
    functional language
    designed for building
    scalable and maintainable
    applications.
    — elixir-lang.org

    View Slide

  16. Elixir is a dynamic,
    functional language
    designed for building
    scalable and maintainable
    applications.
    — elixir-lang.org

    View Slide

  17. Elixir is a dynamic,
    functional language
    designed for building
    scalable and maintainable
    applications.
    — elixir-lang.org

    View Slide

  18. Elixir is a dynamic,
    functional language
    designed for building
    scalable and maintainable
    applications.
    — elixir-lang.org

    View Slide

  19. View Slide

  20. Ingredients
    Basic semantics Erlang (functional, impure, MFA)
    Concurrency model Erlang (processes, message passing)
    Fault-tolerance Erlang (supervision trees, monitors)
    Pattern matching Erlang
    Ecosystem Erlang (OTP, BEAM)
    Syntax Ruby (def, do, end)
    Literals Ruby (:atom, str =~ ~r/regex/)
    Community Ruby
    Macros Clojure
    Doc-strings Python
    Pipe operator F#
    Lessons learned Rails
    Unicode ("UTF-8 !")
    Sugar & Magic

    View Slide

  21. How to get Elixir
    brew install elixir # macOS
    sudo port install elixir # macOS
    pacman -S elixir # Arch
    emerge --ask dev-lang/elixir # Gentoo
    guix package -i elixir # GNU Guix
    yum install elixir # Fedora
    pkg install elixir # FreeBSD
    sudo apt-get install elixir # Ubuntu / Debian
    cinst elixir # Windows (Chocolatey)
    docker run -it --rm elixir # Anywhere (Docker)

    View Slide

  22. View Slide

  23. View Slide

  24. Phoenix - Plug middlewares
    defmodule Recruitee.Plug.ETag do
    def init(opts), do: opts
    def call(conn, _opts \\ []) do
    Plug.Conn.put_resp_header(conn, "etag", etag(conn))
    end
    end

    View Slide

  25. Phoenix - Plug middlewares
    defmodule Recruitee.Endpoint do
    use Phoenix.Endpoint, otp_app: :recruitee
    plug Plug.Static, at: "/", gzip: false, ...
    plug Plug.RequestId
    plug Plug.Logger
    plug Plug.MethodOverride
    plug Plug.Head
    plug Plug.Session, ...
    plug Recruitee.Router
    end

    View Slide

  26. Phoenix - Routing
    defmodule Recruitee.Router do
    use Recruitee.Web, :router
    pipeline :api do
    plug :accepts, ["json"]
    plug Recruitee.Plug.ETag
    end
    scope "/", Recruitee do
    pipe_through :api
    resources "/offers", OfferController do
    resources "/attachments", AttachmentController, only: [:index]
    end
    patch "/candidates/:id", CandidateController, :update, assigns: %{scopes: ~w(manage_candidate)}
    delete "/candidates/:id", CandidateController, :delete, assigns: %{scopes: ~w(manage_candidate)}
    end
    forward "/", Recruitee.Plug.Forward
    end

    View Slide

  27. Phoenix - Controllers
    defmodule Recruitee.CandidateController do
    use Recruitee.Controller
    def show(conn, %{"id" => id}) do
    candidate = Queries.Candidate.get!(id)
    render(conn, "show.json", candidate: candidate)
    end
    end

    View Slide

  28. Phoenix - Views
    defmodule Recruitee.CandidateView do
    use Recruitee.View
    def render("show.json", %{candidate: candidate}) do
    candidate
    |> Map.take([:id, :name, :emails, :phones, :rating, :referrer])
    |> Map.merge(timestamps(candidate))
    |> Map.merge(placements(candidate))
    end
    defp placements(candidate), do: # ...
    end

    View Slide

  29. But that's just good old HTTP...

    View Slide

  30. Phoenix - Channels
    Phoenix Phoenix Phoenix
    Browser iOS Android

    View Slide

  31. Phoenix - Channels
    defmodule Recruitee.Endpoint do
    use Phoenix.Endpoint, otp_app: :recruitee
    socket "/socket", Recruitee.UserSocket
    # ...
    end

    View Slide

  32. Phoenix - Channels
    defmodule Recruitee.UserSocket do
    use Phoenix.Socket
    channel "offer:*", Recruitee.OfferChannel
    # ...
    end

    View Slide

  33. Phoenix - Channels
    defmodule Recruitee.OfferChannel do
    use Phoenix.Channel
    def join("offer:" <> offer_id, _params, socket) do
    if authorized?(socket, offer_id) do
    {:ok, socket}
    else
    {:error, %{reason: "unauthorized"}}
    end
    end
    end

    View Slide

  34. Phoenix - Channels
    defmodule Recruitee.CandidateController do
    use Recruitee.Controller
    def create(conn, params) do
    candidate = Actions.Candidate.create(params)
    Recruitee.Endpoint.broadcast! "offer:" <> offer_id, "new_candidate", candidate
    # ...
    end
    end

    View Slide

  35. Phoenix - Front-end (.js)
    • $ mix phoenix.new --no-brunch
    # package.json
    {
    # ...
    "dependencies": {
    "phoenix": "file:deps/phoenix"
    },
    # whatever
    }

    View Slide

  36. Phoenix - Front-end (.js)
    import {Socket} from "phoenix"
    let socket = new Socket("/socket", {params: {token: "..."}})
    socket.connect()
    let channel = socket.channel("offer:123", {})
    channel.join()
    .receive("ok", resp => { console.log("Joined successfully", resp) })
    .receive("error", resp => { console.log("Unable to join", resp) })
    channel.on("new_candidate", payload => { ... })

    View Slide

  37. View Slide

  38. Ecto
    • multiple databases support (with focus on PostgreSQL)
    • SQL migrations
    • query DSL
    • changesets & validations
    • you might not need it

    View Slide

  39. Elixir in bullet points
    • functional, immutable
    • concurrent & parallel, without callbacks
    • distributed
    • fault-tolerant ("let it crash")
    • hot code swap
    • modern syntax & features
    • very good documentation
    • helpful community

    View Slide

  40. Part I - The success story
    Part II - What is elixir?
    Part III - Elixir Crash Course

    View Slide

  41. Types
    # Basic # Number # Atom # Tuple
    nil 123 :foo_bar {:ok, 200}
    true 3.1415 :"with space" {:hi, "x", 1.0}
    false 1_000_000
    # List # Map # String # Other
    [] %{} "UTF-8 !" ~r/(\d+)/ui
    [1,2,3] %{key: value} "c = #{a+b}" (1..100)
    '123' %{:key => value} <<49,50,51>> #PID<0.107.0>
    ~w(a b c) %{"key" => val}

    View Slide

  42. Operators
    # Comparision # Lists
    1 == 1 (true) [1] ++ [2] ([1,2])
    1 > 2 (false) [1,2,2,3] -- [2,3] ([1,2])
    # Math # String concatenation
    1 + 2 (3) "hello" <> "world" ("helloworld")
    2 - 1 (1)
    4 / 2 (2.0) # Regex match
    div(4,2) (2) "hello" =~ ~r/ell/ (true)

    View Slide

  43. Note on &&, ||, and, or
    • very similar to ruby
    • all operators are short-circut
    • and & or requires Boolean as first argument
    • and & or can be used in guard clauses
    • everything except nil and false is true-ish (but can't be used in
    and, or)

    View Slide

  44. Modules & Functions
    # lib/tesla/adapters/hackney.ex
    defmodule Tesla.Adapters.Hackney do
    def process(env, opts \\ []) do
    format(env.request) # call same module function
    end
    defp format(request) do
    IO.inspect(request) # call other module function
    end
    defp oneline_add(a,b), do: a + b
    end
    # call `process` function from `Tesla.Adapters.Hackney` module
    Tesla.Adapters.Hackney.process(env, opts)

    View Slide

  45. Pattern Matching
    1. a = 1
    2. 1 = a
    3. 2 = a # ** (MatchError) no match of right hand side value: 1
    4. {a,b} = {1,2} # a=1, b=2
    5. {a,2} = {1,2} # a=1
    6. {a,3} = {1,2} # ** (MatchError) ...
    7. {a,_} = {1,2} # a=1
    8. [head | tail] = [1,2,3] # head=1, tail=[1,2]
    9. [a,b,c | _] = [1,2,3] # a=1, b=2, c=3
    10. %{name: name} = %{name: "Jon", age: 10} # name="Jon"
    11. {:ok, result} = some_function(args)

    View Slide

  46. Pattern Matching Examples - multiple clauses
    defmodule Recruitee.Search.Queries.Query do
    # ...
    def query_match(_field, nil), do: %{}
    def query_match(_field, ""), do: %{}
    def query_match(field, value) do
    %{"match" => %{field => value}}
    end
    # ...
    end

    View Slide

  47. Pattern Matching Examples - guards
    defmodule Recruitee.FileKind do
    # ...
    def kind(ext) when ext in [".doc", ".pdf"], do: :document
    def kind(ext) when ext in [".png", ".jpg"], do: :image
    def kind(_), do: :other
    # ...
    end

    View Slide

  48. Pattern Matching Examples - map, nested match
    defmodule Recruitee.ResumeParser do
    # ...
    defp extract_name_from_filename(%{name: nil}), do: # ...
    defp extract_name_from_filename(env), do: env
    defp extract_photo(%{images: [{name, data}|_]}), do: # ...
    defp extract_photo(env), do: env
    defp set_cv_email(%{cv_data: %{email: email}}) when not is_nil(email), do: # ...
    # ...
    end

    View Slide

  49. Anonymous functions
    # define anonymous function
    f = fn a,b -> a + b end
    # call anonymous function
    f.(1,2)

    View Slide

  50. Enum
    # anonymous function
    Enum.map([1,2,3], fn x -> x + 5 end) # => [6,7,8]
    # anonymous function - shorthand syntax
    Enum.reduce([1,2,3], 0, &(&1 + &2)) # => 6
    # reference Module.Function/Arity
    Enum.map(["one", "two", "three"], &String.length/1) #=> [3,3,5]

    View Slide

  51. Pipe Operator |>
    # Standard notation
    Enum.join(Enum.map(String.split("jon snow"), &String.capitalize/1), ".")
    # Multi-line notation
    Enum.join(
    Enum.map(
    String.split("jon snow"),
    &String.capitalize/1
    ),
    "."
    )

    View Slide

  52. Pipe Operator |>
    # Split into multiple lines using variables
    a0 = "jon snow"
    a1 = String.split(a0)
    a2 = Enum.map(a1, &String.capitalize/1)
    a3 = Enum.join(a2, ".")

    View Slide

  53. Pipe Operator |>
    # Just use pipe
    "jon snow"
    |> String.split
    |> Enum.map(&String.capitalize/1)
    |> Enum.join(".")

    View Slide

  54. Pipe Example - Ecto Queries
    defmodule Recruitee.Queries.Candidate do
    def not_viewed_qualified_by_offer_count(account) do
    account
    |> Queries.Offer.visible
    |> Queries.Offer.active
    |> join(:inner, [o], p in assoc(o, :placements))
    |> join(:inner, [_,p], c in assoc(p, :candidate))
    |> where([_,p], is_nil(p.disqualified_at))
    |> where([_,_,c], c.viewed in [nil, false])
    |> group_by([o], o.id)
    |> select([o], {o.id, count(o.id)})
    |> Repo.all
    |> Enum.into(%{})
    end
    end

    View Slide

  55. Pipe Example - Custom Processing
    defmodule Recruitee.Actions.Candidate do
    def create(account, params, offer_ids \\ []) do
    account
    |> new_candidate
    |> put_change(:source, "manual")
    |> Candidate.create_changeset(params)
    |> set_cv_processing_status
    |> set_initials_photo(:create)
    |> finalize_create(account, offer_ids)
    end
    end

    View Slide

  56. Processes
    PID 1 PID 2
    message
    message
    state, mailbox state, mailbox

    View Slide

  57. spawn, send, receive
    my_pid = self() # => #PID<0.85.0>
    child1_pid = spawn_link(fn -> send(my_pid, {:hello, 1}) end)
    child2_pid = spawn_link(SomeModule, :some_fun, ["ar", "gs"])
    receive do
    {:hello, num} -> IO.puts(num)
    after
    1000 -> IO.puts("timeout")
    end

    View Slide

  58. Processes
    PID 1
    IEx.main
    PID 2
    Counter.loop
    :inc
    :inc
    :dec
    {:get, pid}
    {:value, 1}

    View Slide

  59. spawn, send, receive
    defmodule Counter do
    def start_link(value \\ 0) do
    spawn_link(__MODULE__, :loop, [value])
    end
    def loop(value) do
    receive do
    :inc -> loop(value + 1)
    :dec -> loop(value - 1)
    {:get, pid} ->
    send(pid, {:value, value})
    loop(value)
    end
    end
    end

    View Slide

  60. spawn, send, receive
    pid = Counter.start_link # loop(0)
    send pid, :inc # loop(1)
    send pid, :inc # loop(2)
    send pid, :dec # loop(1)
    send pid, {:get, self} # send, loop(1)
    result = receive do
    {:value, v} -> v
    end # => 1

    View Slide

  61. Processes in real world
    data = File.read("path/to/file")
    post = Repo.get(Post, 123)
    response = Tesla.get("http://google.com")

    View Slide

  62. Recruitee API Phoenix app stats
    • 14.000 LOC
    • Over 2000 × |>
    • 4 × receive
    • 2 × send

    View Slide

  63. Supervision trees
    Supervisor
    Supervisor Worker Worker
    Worker Worker Worker

    View Slide

  64. Tools
    Feature | Ruby/Rails | Elixir/Phoenix
    ---------------------------------------------------------------------
    Package repo | rubygems.org | hex.pm
    Online docs | rubydoc.info | hexdocs.pm
    Deps config | Gemfile | mix.exs
    Install deps | bundle install | mix deps.get
    Install global | gem install rails | mix archive.install http://...
    Start server | rails server | mix phoenix.server
    Run task | rake my:task | mix my.task
    REPL | irb, rails console | iex, iex -S mix

    View Slide

  65. Thanks!
    Questions?

    View Slide