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

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

    View Slide

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

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. THE MASTERPLAN
    • The GenServer
    • OTP special processes
    • OTP behaviours
    • Custom behaviours

    View Slide

  7. THE EXPERIMENT

    View Slide

  8. View Slide

  9. WHO USED ELIXIR?

    View Slide

  10. WHO USED SPAWN & RECEIVE FOR PMAP?

    View Slide

  11. WHO WROTE A GENSERVER
    IMPLEMENTATION?

    View Slide

  12. WHO WROTE A BEHAVIOUR?

    View Slide

  13. BEHAVIOUR

    View Slide

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

    View Slide

  15. View Slide

  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 (…)”

    View Slide

  17. TWO USE CASES FOR BEHAVIOURS
    message driven data driven
    behaviour spectrum

    View Slide

  18. GENSERVER IS EMERGENT

    View Slide

  19. View Slide

  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

    View Slide

  21. MESSAGE HANDLER PROCESS
    • waits for a message
    • parses the message
    • handles the message
    • sends a reply

    View Slide

  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

    View Slide

  23. MESSAGE HANDLER PROCESS
    • waits for a message
    • parses the message
    • handles the message
    • sends a reply

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  27. FORMAT_STATUS
    • :sys.get_status/2
    • abnormal termination and logging

    View Slide

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

    View Slide

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

    View Slide

  30. View Slide

  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

    View Slide

  32. View Slide

  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

    View Slide

  34. View Slide

  35. GEN_SERVER TO THE RESCUE!

    View Slide

  36. GEN_SERVER FOR EVERYTHING?
    • pooling
    • database connection
    • web server
    • chat room
    • data streams

    View Slide

  37. View Slide

  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,

    View Slide

  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

    View Slide

  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

    View Slide

  41. View Slide

  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

    View Slide

  43. COWBOY_MIDDLEWARE AND PLUG

    View Slide

  44. View Slide

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

    View Slide

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

    View Slide

  47. HACKNEY_POOL_HANDLER
    • start/0
    • checkout/4
    • checkin/2

    View Slide

  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

    View Slide

  49. THERE ARE TWO WAYS TO DO BEHAVIOURS

    View Slide

  50. View Slide

  51. MAC ROS

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  55. BEHAVIOURS ARE POWERFUL ABSTRACTIONS

    View Slide

  56. THEY ARE GREAT OPPORTUNITIES FOR
    LIBRARIES

    View Slide

  57. DON’T USE MACROS TO SPECIALISE
    GENSERVER

    View Slide

  58. EXTENDING OTP WITH CUSTOM
    BEHAVIOURS
    Looking beyond a gen_server

    View Slide

  59. View Slide