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

Extending OTP with custom behaviours

Extending OTP with custom behaviours

Michał Muskała

December 03, 2016
Tweet

More Decks by Michał Muskała

Other Decks in Programming

Transcript

  1. EXTENDING OTP WITH CUSTOM BEHAVIOURS Looking beyond a gen_server

  2. MICHAŁ MUSKAŁA http://michal.muskala.eu/ https://github.com/michalmuskala/ @michalmuskala

  3. None
  4. None
  5. None
  6. THE MASTERPLAN • The GenServer • OTP special processes •

    OTP behaviours • Custom behaviours
  7. THE EXPERIMENT

  8. None
  9. WHO USED ELIXIR?

  10. WHO USED SPAWN & RECEIVE FOR PMAP?

  11. WHO WROTE A GENSERVER IMPLEMENTATION?

  12. WHO WROTE A BEHAVIOUR?

  13. BEHAVIOUR

  14. OTP BEHAVIOURS • application • gen_server • gen_fsm • gen_statem

    • gen_event • supervisor
  15. None
  16. http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-contracts/ “Furthermore, we have already defined three implementations of the

    Twitter API, so we better make it all explicit. In Elixir we do so by defining a behaviour with callback functions (…)”
  17. TWO USE CASES FOR BEHAVIOURS message driven data driven behaviour

    spectrum
  18. GENSERVER IS EMERGENT

  19. None
  20. defmodule Calculator do def start do spawn(__MODULE__, :loop, [[]]) end

    def add(pid, x, y) do send(pid, {:add, self(), x, y}) receive do {:result, x} -> x end end def loop(state) do receive do {:add, from, x, y} -> send(from, {:result, x + y}) end loop(state) end end
  21. MESSAGE HANDLER PROCESS • waits for a message • parses

    the message • handles the message • sends a reply
  22. defmodule Calculator do def start do spawn(__MODULE__, :loop, [[]]) end

    def add(pid, x, y) do send(pid, {:add, self(), x, y}) receive do {:result, x} -> x end end def loop(state) do receive do {:add, from, x, y} -> send(from, {:result, x + y}) end loop(state) end end
  23. MESSAGE HANDLER PROCESS • waits for a message • parses

    the message • handles the message • sends a reply
  24. defmodule Calculator do use GenServer def start do GenServer.start_link(__MODULE__, [])

    end def add(pid, x, y) do GenServer.call(pid, {:add, x, y}) end def handle_call({:add, x, y}, from, state) do {:reply, x + y, state} end end
  25. GEN_SERVER • init/1 • handle_cast/2 • handle_call/3 • handle_info/2 •

    terminate/2 • code_change/3
  26. GEN_SERVER • init/1 • handle_cast/2 • handle_call/3 • handle_info/2 •

    terminate/2 • code_change/3 • format_status/2
  27. FORMAT_STATUS • :sys.get_status/2 • abnormal termination and logging

  28. GenServer Callback module start_link call multi_call cast abcast init handle_cast

    handle_call handle_info terminate
  29. HOW IS GEN_SERVER IMPLEMENTED? HOW CAN WE DO SOMETHING SIMILAR?

  30. None
  31. OTP SPECIAL PROCESSES • process fits into a supervision tree

    • support for the :sys debug utilities • process system messages • OTP design principles
 http://erlang.org/doc/design_principles/des_princ.html
  32. None
  33. SPECIAL PROCESS IMPLEMENTATION • start with :proc_lib.start_link/3 • initialise debug

    with :sys.debug_options/1 • respond to start_link with :proc_lib.init_ack/2 • use :sys.handle_system_msg/6 for {:system, from, request} • implement system_continue/3 and system_terminate/4 callbacks
  34. None
  35. GEN_SERVER TO THE RESCUE!

  36. GEN_SERVER FOR EVERYTHING? • pooling • database connection • web

    server • chat room • data streams
  37. None
  38. CONNECTION • init/1, handle_call/3, handle_cast/2, handle_info/2, teminate/2, code_change/3 • connect/2

    • disconnect/2 • :backoff, :connect, :disconnect,
  39. DBCONNECTION • connect/1, disconnect/2, ping/1 • out of process state:

    checkout/1, checkin/1 • transaction: handle_begin/2, handle_commit/2, handle_rollback/2 • queries: handle_prepare/3, handle_execute/4, handle_close/3 • coursors: handle_declare/4, handle_first/4, handle_next/4, handle_deallocate/4
  40. ECTO.ADAPTER • ensure_all_started/2, child_spec/3 • loaders/2, dumpers/2, autogenerate/1 • prepare/2,

    execute/6, insert_all/7, insert/6, update/6, delete/4 • transaction/3, in_transaction?/1, rollback/2 • extensions for storage, migrations, dumps
  41. None
  42. RANCH_TRANSPORT • acceptor: listen/1, accept/2, accept_ack/2 • connect/3, connect/4 •

    communication: recv/3, send/2, sendfile/2, sendfile/4, sendfile/5 • config: setopts/2, controlling_process/2, • read settings: name/0, secure/0, messages/0, peername/1, sockname/1 • shutdown/2, close/1
  43. COWBOY_MIDDLEWARE AND PLUG

  44. None
  45. COWBOY_MIDDLEWARE AND PLUG • cowboy: execute/2 • plug: call/2

  46. PHOENIX.CHANNEL • join/2 • handle_in/3 • handle_out/3 (*) • handle_info/2

    • terminate/2 • code_change/3
  47. HACKNEY_POOL_HANDLER • start/0 • checkout/4 • checkin/2

  48. GENSTAGE • init/1 • handle_demand/2 • handle_subscribe/4, handle_cancel/3 • handle_events/3

    • handle_call/3, handle_cast/2, handle_info/2, teminate/2, code_change/3
  49. THERE ARE TWO WAYS TO DO BEHAVIOURS

  50. None
  51. MAC ROS

  52. defmacro __using__(_) do quote do use GenServer def handle_call({:foo, args},

    _, state) do {reply, state} = some_callback(args, state) Some.more_logic(reply, state) {:reply, reply, state} end defp some_callback(args, state), do: {:ok, state} defoverridable [some_callback: 2] end end
  53. use GenServer @callback init(term) :: {:ok, state :: term} |

    ... @callback some_callback(term) :: {reply :: term, state :: term} def start_link(module, args, opts) do GenServer.start_link(__MODULE__, {module, args, opts}, opts) end def init({mod, args, opts}) do case mod.init(args) do {:ok, int} -> %{mod: mod, int: int} other -> other end end
  54. def handle_call({:foo, arg}, _from, %{mod: mod, int: int} = state)

    do {reply, int} = mod.some_callback(arg, int) {:reply, reply, %{state | internal: int}} end def format_status(:normal, [pdict, %{mod: mod, int: int}]) do [{:data, [{'State', int}]}] end def format_status(:terminate, [pdict, %{int: int}]) do int end
  55. BEHAVIOURS ARE POWERFUL ABSTRACTIONS

  56. THEY ARE GREAT OPPORTUNITIES FOR LIBRARIES

  57. DON’T USE MACROS TO SPECIALISE GENSERVER

  58. EXTENDING OTP WITH CUSTOM BEHAVIOURS Looking beyond a gen_server

  59. None