Pro Yearly is on sale from $80 to $50! »

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

9fae8c5d475fe322a3a74e53d56ee2a0?s=128

Tymon Tobolski

November 23, 2016
Tweet

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?