$30 off During Our Annual Pro Sale. View Details »

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

  2. About me Tymon Tobolski • GitHub: teamon • Twitter: @iteamon

    • 8+ years with ruby • Elixir+Dev+Ops @ Recruitee.com
  3. http:/ /github-awards.com/ !

  4. Part I - The success story Part II - What

    is elixir? Part III - Elixir Crash Course
  5. mid-August 2016

  6. HR panel Careers pages Landing pages

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

    (rails) recruitee (rails)
  8. Start with fallback

  9. Use the same PostgreSQL database

  10. Extract microservices

  11. Use docker & rancher

  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
  13. Part I - The success story Part II - What

    is elixir? Part III - Elixir Crash Course
  14. What is elixir and why should I care?

  15. Elixir is a dynamic, functional language designed for building scalable

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

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

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

    and maintainable applications. — elixir-lang.org
  19. None
  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
  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)
  22. None
  23. None
  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
  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
  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
  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
  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
  29. But that's just good old HTTP...

  30. Phoenix - Channels Phoenix Phoenix Phoenix Browser iOS Android

  31. Phoenix - Channels defmodule Recruitee.Endpoint do use Phoenix.Endpoint, otp_app: :recruitee

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

    Recruitee.OfferChannel # ... end
  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
  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
  35. Phoenix - Front-end (.js) • $ mix phoenix.new --no-brunch #

    package.json { # ... "dependencies": { "phoenix": "file:deps/phoenix" }, # whatever }
  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 => { ... })
  37. None
  38. Ecto • multiple databases support (with focus on PostgreSQL) •

    SQL migrations • query DSL • changesets & validations • you might not need it
  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
  40. Part I - The success story Part II - What

    is elixir? Part III - Elixir Crash Course
  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}
  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)
  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)
  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)
  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)
  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
  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
  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
  49. Anonymous functions # define anonymous function f = fn a,b

    -> a + b end # call anonymous function f.(1,2)
  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]
  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 ), "." )
  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, ".")
  53. Pipe Operator |> # Just use pipe "jon snow" |>

    String.split |> Enum.map(&String.capitalize/1) |> Enum.join(".")
  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
  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
  56. Processes PID 1 PID 2 message message state, mailbox state,

    mailbox
  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
  58. Processes PID 1 IEx.main PID 2 Counter.loop :inc :inc :dec

    {:get, pid} {:value, 1}
  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
  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
  61. Processes in real world data = File.read("path/to/file") post = Repo.get(Post,

    123) response = Tesla.get("http://google.com")
  62. Recruitee API Phoenix app stats • 14.000 LOC • Over

    2000 × |> • 4 × receive • 2 × send
  63. Supervision trees Supervisor Supervisor Worker Worker Worker Worker Worker

  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
  65. Thanks! Questions?