Slide 1

Slide 1 text

IDIOMATIC ELIXIR © Erlang Solu,ons Ltd, 2016 1

Slide 2

Slide 2 text

Hello! © Erlang Solu,ons Ltd, 2016 2

Slide 3

Slide 3 text

Claudio Ortolina Head of Elixir at Erlang Solu!ons Ltd. @cloud8421 claudio.ortolina@erlang-solu!ons.com © Erlang Solu,ons Ltd, 2016 3

Slide 4

Slide 4 text

IDIOMATIC ELIXIR © Erlang Solu,ons Ltd, 2016 4

Slide 5

Slide 5 text

IDIOMATIC ELIXIR © Erlang Solu,ons Ltd, 2016 5

Slide 6

Slide 6 text

FLEXIBLE ELIXIR © Erlang Solu,ons Ltd, 2016 6

Slide 7

Slide 7 text

DEFINITION 1 flexible adjec!ve - /ˈflek.sə.bəl/ • Able to change or be changed easily according to the situa!on • Able to bend or to be bent easily without breaking 1 h!p:/ /dic"onary.cambridge.org/dic"onary/english/flexible © Erlang Solu,ons Ltd, 2016 7

Slide 8

Slide 8 text

WARNING: CODE AHEAD © Erlang Solu,ons Ltd, 2016 8

Slide 9

Slide 9 text

1. HANDLING VOLATILE DATA © Erlang Solu,ons Ltd, 2016 9

Slide 10

Slide 10 text

CACHE STORE defmodule CacheStore do @type key :: term @type value :: term @type reason :: term @spec get(key) :: {:ok, value} | {:error, reason} @spec set(key, value) :: :ok @spec start_link() :: {:ok, pid} | {:error, reason} end © Erlang Solu,ons Ltd, 2016 10

Slide 11

Slide 11 text

v1: AGENT © Erlang Solu,ons Ltd, 2016 11

Slide 12

Slide 12 text

defmodule MyApp do def start(_type, _args) do children = [ worker(CacheStore, []) ] ... end end defmodule CacheStore do def start_link() do initial = fn() -> %{} end Agent.start_link(initial, name: __MODULE__) end end © Erlang Solu,ons Ltd, 2016 12

Slide 13

Slide 13 text

defmodule CacheStore do def set(key, val) do Agent.cast(__MODULE__, fn(store) -> Dict.put(store, key, val) end) end def get(key) do Agent.get(__MODULE__, fn(store) -> case Dict.get(store, key) do :error -> {:error, :not_found} value -> {:ok, value} end end) end end © Erlang Solu,ons Ltd, 2016 13

Slide 14

Slide 14 text

! SIMPLE © Erlang Solu,ons Ltd, 2016 14

Slide 15

Slide 15 text

! STRICTLY CONSISTENT © Erlang Solu,ons Ltd, 2016 15

Slide 16

Slide 16 text

! ONE OPERATION AT A TIME © Erlang Solu,ons Ltd, 2016 16

Slide 17

Slide 17 text

! IF AGENT CRASHES, DATA IS LOST © Erlang Solu,ons Ltd, 2016 17

Slide 18

Slide 18 text

v2: GenServer + ETS © Erlang Solu,ons Ltd, 2016 18

Slide 19

Slide 19 text

GenServer • Generic server behaviour • Used to model client-server rela!onship • Server holds data and api to access/mutate it • Used to implement Agent © Erlang Solu,ons Ltd, 2016 19

Slide 20

Slide 20 text

ETS • Erlang Term Storage • Built-in • Large, in-memory data storage • Constant !me access © Erlang Solu,ons Ltd, 2016 20

Slide 21

Slide 21 text

defmodule CacheStore do use GenServer def start_link do GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do :ets.new(__MODULE__, [:named_table, :bag, :public, read_concurrency: true]) {:ok, []} end end © Erlang Solu,ons Ltd, 2016 21

Slide 22

Slide 22 text

defmodule CacheStore do ... def set(key, value) do GenServer.cast(__MODULE__, {:set, key, value}) end def handle_cast({:set, key, value}, state) do true = :ets.insert(__MODULE__, {key, value}) {:noreply, state} end end © Erlang Solu,ons Ltd, 2016 22

Slide 23

Slide 23 text

defmodule CacheStore do ... def get(key) do case :ets.lookup(__MODULE__, key) do [] -> {:error, :not_found} [{^key, result}] -> {:ok, result} end end def handle_cast({:set, key, value}, state) do true = :ets.insert(__MODULE__, {key, value}) {:noreply, state} end end © Erlang Solu,ons Ltd, 2016 23

Slide 24

Slide 24 text

! CONCURRENT WRITES AND READS © Erlang Solu,ons Ltd, 2016 24

Slide 25

Slide 25 text

! EVENTUALLY CONSISTENT © Erlang Solu,ons Ltd, 2016 25

Slide 26

Slide 26 text

! IF SERVER CRASHES, DATA IS GONE © Erlang Solu,ons Ltd, 2016 26

Slide 27

Slide 27 text

v3: GenServer + ETS + App © Erlang Solu,ons Ltd, 2016 27

Slide 28

Slide 28 text

defmodule CacheStore do def create_cache_table do :ets.new(__MODULE__, [:named_table, :duplicate_bag, :public, read_concurrency: true]) end end © Erlang Solu,ons Ltd, 2016 28

Slide 29

Slide 29 text

defmodule MyApp do def start(_type, _args) do # The app process owns the table now CacheStore.create_cache_table children = [ worker(CacheStore, []) ] ... end end © Erlang Solu,ons Ltd, 2016 29

Slide 30

Slide 30 text

! ONLY AN APP CRASH CAN AFFECT THE CACHED DATA © Erlang Solu,ons Ltd, 2016 30

Slide 31

Slide 31 text

2. LONG RUNNING PROCESSES © Erlang Solu,ons Ltd, 2016 31

Slide 32

Slide 32 text

GAME PROCESS • Game session in a process • Data is self-contained • Exposes expensive opera!ons © Erlang Solu,ons Ltd, 2016 32

Slide 33

Slide 33 text

v1: GenServer © Erlang Solu,ons Ltd, 2016 33

Slide 34

Slide 34 text

defmodule Game do use GenServer defmodule State do defstruct score: 0, players: [], ... end def player_avatars(pid) do GenServer.call(pid, :player_avatars) end def handle_call(:player_avatars, _from, state) do ids = Enum.map(state.players, fn(p) -> p.id end) {:ok, avatars} = slow_fetch_urls(ids) {:reply, avatars, state} end end © Erlang Solu,ons Ltd, 2016 34

Slide 35

Slide 35 text

! IT WORKS © Erlang Solu,ons Ltd, 2016 35

Slide 36

Slide 36 text

! WHILE FETCHING, THE GAME IS BLOCKED © Erlang Solu,ons Ltd, 2016 36

Slide 37

Slide 37 text

! IF FETCH FAILS, THE GAME DIES © Erlang Solu,ons Ltd, 2016 37

Slide 38

Slide 38 text

2. GenServer.reply © Erlang Solu,ons Ltd, 2016 38

Slide 39

Slide 39 text

defmodule Game do ... def player_avatars(pid) do GenServer.call(pid, :player_avatars) end def handle_call(:player_avatars, from, state) do spawn(fn() -> ids = Enum.map(state.players, fn(p) -> p.id end) {:ok, avatars} = slow_fetch_urls(ids) GenServer.reply(from, avatars) end) {:noreply, state} end end © Erlang Solu,ons Ltd, 2016 39

Slide 40

Slide 40 text

! FETCH HAPPENS IN DIFFERENT PROCESS © Erlang Solu,ons Ltd, 2016 40

Slide 41

Slide 41 text

! WHEN FETCH FAILS, GAME IS UNAFFECTED © Erlang Solu,ons Ltd, 2016 41

Slide 42

Slide 42 text

! SAME GUARANTEES FOR THE CALLER © Erlang Solu,ons Ltd, 2016 42

Slide 43

Slide 43 text

! EVENTUAL CONSISTENCY © Erlang Solu,ons Ltd, 2016 43

Slide 44

Slide 44 text

3. FUNCTIONAL CHAINS © Erlang Solu,ons Ltd, 2016 44

Slide 45

Slide 45 text

DIGEST USER INPUT • Incoming data • Whitelist, validate, store • Each step may fail • Computa!on must return a value © Erlang Solu,ons Ltd, 2016 45

Slide 46

Slide 46 text

v1: PURE PIPELINE © Erlang Solu,ons Ltd, 2016 46

Slide 47

Slide 47 text

defmodule CreateUser do @allowed_params ~w(email username) def run(params) do params |> whitelist |> validate |> store end def whitelist(params) do Map.take(params, @allowed_params) end end © Erlang Solu,ons Ltd, 2016 47

Slide 48

Slide 48 text

defmodule MyApp.UserController do def create(params) do case CreateUser.run(params) do {:ok, _user} -> resp(conn, 201, :created) _error -> resp(conn, 406, :malformed) end end end © Erlang Solu,ons Ltd, 2016 48

Slide 49

Slide 49 text

! WORKS GREAT FOR GOLDEN PATH © Erlang Solu,ons Ltd, 2016 49

Slide 50

Slide 50 text

! FAILS WITH ARGUMENT ERRORS © Erlang Solu,ons Ltd, 2016 50

Slide 51

Slide 51 text

v2: TRY..RESCUE © Erlang Solu,ons Ltd, 2016 51

Slide 52

Slide 52 text

defmodule CreateUser do @allowed_params ~w(email username) def run(params) do try do params |> whitelist |> validate |> store rescue error -> error end end def whitelist(params) do Map.take(params, @allowed_params) end end © Erlang Solu,ons Ltd, 2016 52

Slide 53

Slide 53 text

! TRY..RESCUE HIDES ERRORS © Erlang Solu,ons Ltd, 2016 53

Slide 54

Slide 54 text

! ALL FAILURES ARE TREATED THE SAME © Erlang Solu,ons Ltd, 2016 54

Slide 55

Slide 55 text

v3: WITH MACRO © Erlang Solu,ons Ltd, 2016 55

Slide 56

Slide 56 text

defmodule CreateUser do @allowed_params ~w(email username) def run(params) do with {:ok, whitelisted} <- whitelist(params), {:ok, valid} <- validate(whitelisted) do store(valid) else error -> error end end def whitelist(params) do {:ok, Map.take(params, @allowed_params)} end end © Erlang Solu,ons Ltd, 2016 56

Slide 57

Slide 57 text

! CLEARLY EXPRESS WHAT CAN FAIL © Erlang Solu,ons Ltd, 2016 57

Slide 58

Slide 58 text

! CONTROL OVER FAILURE CASES © Erlang Solu,ons Ltd, 2016 58

Slide 59

Slide 59 text

! EXCEPTIONS DIFFERENT FROM ERRORS © Erlang Solu,ons Ltd, 2016 59

Slide 60

Slide 60 text

! NEED TO "TAG" RETURN VALUES © Erlang Solu,ons Ltd, 2016 60

Slide 61

Slide 61 text

FLEXIBLE ELIXIR © Erlang Solu,ons Ltd, 2016 61

Slide 62

Slide 62 text

RICH STANDARD LIBRARY © Erlang Solu,ons Ltd, 2016 62

Slide 63

Slide 63 text

CHOOSE WHO OWNS DATA © Erlang Solu,ons Ltd, 2016 63

Slide 64

Slide 64 text

CHOOSE WHO RUNS FUNCTIONS © Erlang Solu,ons Ltd, 2016 64

Slide 65

Slide 65 text

CHOOSE FAILURE SEMANTICS © Erlang Solu,ons Ltd, 2016 65

Slide 66

Slide 66 text

THANKS! © Erlang Solu,ons Ltd, 2016 66

Slide 67

Slide 67 text

Claudio Ortolina Head of Elixir at Erlang Solu!ons Ltd. @cloud8421 claudio.ortolina@erlang-solu!ons.com © Erlang Solu,ons Ltd, 2016 67