# Behaviour Patterns How behaviours are designed, implemented, and used in Elixir core and Phoenix. ## 1. Behaviour Definition with `@callback` **Source:** `lib/elixir/lib/gen_server.ex:577-812` (all callback definitions) ```elixir @callback init(init_arg :: term) :: {:ok, state} | {:ok, state, timeout | :hibernate | {:continue, continue_arg}} | :ignore | {:stop, reason :: any} @callback handle_call(request :: term, from, state :: term) :: {:reply, reply, new_state} | {:noreply, new_state} | {:stop, reason, reply, new_state} | {:stop, reason, new_state} when reply: term, new_state: term, reason: term ``` **Why:** Callbacks with full type unions document every valid return. Named parameters (`init_arg`, `request`, `state`) serve as documentation. The `when` clause defines type variables used across the union. **Anti-pattern:** Defining callbacks with `@callback handle_call(term, term, term) :: term` — provides zero guidance to implementors. --- ## 2. `@optional_callbacks` for Extensibility **Source:** `lib/phoenix/channel.ex:442-448` ```elixir @optional_callbacks handle_in: 3, handle_out: 3, handle_info: 2, handle_call: 3, handle_cast: 2, code_change: 3, terminate: 2 ``` **Why:** Only `join/3` is required for a Channel. Everything else has sensible defaults. This keeps the minimum implementation surface small — a Channel that just joins and broadcasts needs only one function. **Anti-pattern:** Making all callbacks required when most have reasonable defaults — forces implementors to write boilerplate they don't need. --- ## 3. `@behaviour` Declaration in `__using__` **Source:** `lib/phoenix/channel.ex:450-453` ```elixir defmacro __using__(opts \\ []) do quote do opts = unquote(opts) @behaviour unquote(__MODULE__) @on_definition unquote(__MODULE__) @before_compile unquote(__MODULE__) ... end end ``` **Source:** `lib/elixir/lib/gen_server.ex:836` ```elixir quote location: :keep, bind_quoted: [opts: opts] do @behaviour GenServer ... end ``` **Why:** Setting `@behaviour` inside `use` means users get compile-time warnings about missing callbacks automatically. They don't need to know about the behaviour mechanism — `use Phoenix.Channel` handles it. **Anti-pattern:** Requiring users to manually add both `use MyModule` AND `@behaviour MyModule`. --- ## 4. Default Implementations via `defoverridable` **Source:** `lib/elixir/lib/gen_server.ex:849` ```elixir def child_spec(init_arg) do default = %{ id: __MODULE__, start: {__MODULE__, :start_link, [init_arg]} } Supervisor.child_spec(default, unquote(Macro.escape(opts))) end defoverridable child_spec: 1 ``` **Why:** `defoverridable` provides a working default that users CAN customize but don't HAVE to. The generated function works for the 90% case. The 10% can override it. **Anti-pattern:** Not using `defoverridable` — users who need custom behavior must bypass the `use` macro entirely. --- ## 5. Phoenix Channel: Behaviour + Process + Protocol **Source:** `lib/phoenix/channel.ex:364-448` (full callback set) The Channel behaviour combines: 1. **Required callback:** `join/3` (authorization gate) 2. **Optional callbacks:** `handle_in/3`, `handle_info/2`, etc. (event handlers) 3. **Process semantics:** Each channel is a GenServer (line 476-479) 4. **Configuration via module attributes:** `@phoenix_log_join`, `@phoenix_hibernate_after` ```elixir # From __using__ — configures the process @phoenix_hibernate_after Keyword.get(opts, :hibernate_after, 15_000) @phoenix_shutdown Keyword.get(opts, :shutdown, 5000) def child_spec(init_arg) do %{ id: __MODULE__, start: {__MODULE__, :start_link, [init_arg]}, shutdown: @phoenix_shutdown, restart: :temporary } end def start_link(triplet) do GenServer.start_link(Phoenix.Channel.Server, triplet, hibernate_after: @phoenix_hibernate_after ) end ``` **Why:** The Channel behaviour demonstrates layering — it's a behaviour (compile-time contract), a process (runtime entity), and configurable (via options to `use`). Each concern is handled by the appropriate mechanism. **Anti-pattern:** Trying to encode runtime configuration in the behaviour contract itself, or conflating compile-time and runtime concerns. --- ## 6. Callback Documentation Pattern **Source:** `lib/phoenix/channel.ex:350-363` (join callback doc) ```elixir @doc """ Handle channel joins by `topic`. ... ## Example def join("room:lobby", payload, socket) do if authorized?(payload) do {:ok, socket} else {:error, %{reason: "unauthorized"}} end end """ @callback join(topic :: binary, payload :: payload, socket :: Socket.t()) :: {:ok, Socket.t()} | {:ok, reply :: payload, Socket.t()} | {:error, reason :: map} ``` **Why:** Every callback gets: 1. A `@doc` explaining when it's called and what it should do 2. A concrete example 3. The full type spec with all valid returns This trio (doc + example + spec) gives implementors everything they need. **Anti-pattern:** Defining callbacks without documentation — implementors have to read source code to understand when callbacks fire. --- ## 7. Phoenix.Endpoint: Behaviour as Interface Contract **Source:** `lib/phoenix/endpoint.ex:408` ```elixir defmacro __using__(opts) do quote do @behaviour Phoenix.Endpoint unquote(config(opts)) unquote(pubsub()) unquote(plug()) unquote(server()) end end ``` **Why:** The Endpoint uses `@behaviour` to define what an endpoint MUST provide (like `config/2`), then `__using__` generates the common implementation. The behaviour is the interface; the macro provides the default implementation. **Anti-pattern:** Using only a behaviour without a `use` macro when significant boilerplate is required — forces every implementor to write the same code.