Slide 1

Slide 1 text

elixir Bydgoszcz Web Development Meetup 23.11.2016

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

http:/ /github-awards.com/ !

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

mid-August 2016

Slide 6

Slide 6 text

HR panel Careers pages Landing pages

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Start with fallback

Slide 9

Slide 9 text

Use the same PostgreSQL database

Slide 10

Slide 10 text

Extract microservices

Slide 11

Slide 11 text

Use docker & rancher

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

What is elixir and why should I care?

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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)

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

But that's just good old HTTP...

Slide 30

Slide 30 text

Phoenix - Channels Phoenix Phoenix Phoenix Browser iOS Android

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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 => { ... })

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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}

Slide 42

Slide 42 text

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)

Slide 43

Slide 43 text

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)

Slide 44

Slide 44 text

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)

Slide 45

Slide 45 text

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)

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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]

Slide 51

Slide 51 text

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 ), "." )

Slide 52

Slide 52 text

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, ".")

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

Supervision trees Supervisor Supervisor Worker Worker Worker Worker Worker

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Thanks! Questions?