Files
elixir-patterns/comparison/elixir-vs-phoenix.md
T
Aaron Weiker 4ea9a884aa docs: idiomatic Elixir and Phoenix patterns with source citations
Extracted patterns, conventions, and code smells directly from the
Elixir and Phoenix source code with file path and line number citations.

Covers: GenServer, error handling, data transforms, process design,
testing, documentation, typespecs, macros, behaviours, module organization,
Phoenix-specific patterns, framework deviations, and anti-patterns.
2026-04-29 22:50:12 -07:00

4.9 KiB

Elixir Core vs Phoenix: Side-by-Side Comparison

How the same concepts are approached differently (or similarly) between Elixir core and Phoenix.

Process Lifecycle

Aspect Elixir Core Phoenix
Default restart :permanent (GenServer, Supervisor) :temporary (Channel)
Hibernation Not set by default 15s idle → hibernate (Channel)
Process identity Registry :via tuples Topic-based (channels identified by topic)
Supervision Direct supervisor reference Endpoint supervisor manages all

Source (Elixir): lib/elixir/lib/gen_server.ex:843-848 (child_spec without restart = defaults to :permanent) Source (Phoenix): lib/phoenix/channel.ex:470-475 (explicit restart: :temporary)


Error Handling

Aspect Elixir Core Phoenix
Exception design Minimal struct fields HTTP-aware (plug_status)
Bang functions File.read! raises broadcast! raises
Failure response {:error, reason} tuple {:error, reason} + HTTP status
Recovery Supervisor restart Client reconnection

Source (Elixir): Standard tagged tuples throughout (lib/elixir/lib/agent.ex:210) Source (Phoenix): lib/phoenix/router.ex:7-8 (NoRouteError with plug_status: 404)


Behaviour Design

Aspect Elixir Core Phoenix
Required callbacks Most are optional Only join/3 required (Channel)
__using__ generates child_spec/1 + @behaviour child_spec + behaviour + config + imports
Configuration Via use Module, opts Via use Module, opts + module attributes
Before-compile Rarely used Heavily used (routes, intercepts)

Source (Elixir): lib/elixir/lib/gen_server.ex:834-851 Source (Phoenix): lib/phoenix/channel.ex:450-500


Macro Usage

Aspect Elixir Core Phoenix
Philosophy Minimal, prefer functions Justified by performance
__using__ Generates 1-2 functions Generates functions + sets up DSL
DSL creation Avoided (except Kernel/SpecialForms) Embraced (Router DSL)
Attribute accumulation Rare Central pattern (routes, sockets)

Source (Elixir): lib/elixir/lib/gen_server.ex:834 — simple __using__ Source (Phoenix): lib/phoenix/router.ex:263-290 — complex DSL setup with attribute accumulation


Module Organization

Aspect Elixir Core Phoenix
File naming gen_server.ex (snake_case) controller.ex (snake_case)
Nesting 2 levels max (Task.Supervised) 2-3 levels (Phoenix.Channel.Server)
Internal modules @moduledoc false @moduledoc false
Public API Functions on the main module Functions + macros on the main module

Both follow the same convention: public API on the parent module, implementation details in nested submodules with @moduledoc false.


State Management

Aspect Elixir Core Phoenix
Agent Simple state, function-based access Socket assigns (assign/2)
GenServer Full control, handle_call/cast/info Channel handles (same callbacks)
State shape Any term (developer's choice) %Socket{} struct (framework-defined)
State access Direct in callbacks Via socket.assigns

Source (Elixir): lib/elixir/lib/agent.ex:62-82 (compute in server vs client) Source (Phoenix): lib/phoenix/channel.ex:463-464 (import Phoenix.Socket, only: [assign: 3, assign: 2])


Documentation

Aspect Elixir Core Phoenix
Moduledoc size Very large (GenServer: 530 lines) Large (Router: ~260 lines)
Examples Doctests (verified by tests) Examples in docs (not always doctests)
Admonitions Info blocks for use Info blocks for use
Guides Linked from moduledoc Linked from moduledoc
Deprecation @doc deprecated: "Use X instead" Inline comments (TODO markers)

Both use the same documentation infrastructure (ExDoc), but Elixir core tends toward more exhaustive docs (GenServer's moduledoc is essentially a tutorial).


Configuration

Aspect Elixir Core Phoenix
Compile-time Module attributes Application.compile_env
Runtime Application env / init args config/2 callback + Application env
Per-instance Options to start_link Endpoint config per environment

Source (Phoenix): lib/phoenix/endpoint.ex:422-430 (compile-time config checking)

var!(code_reloading?) =
  Application.compile_env(@otp_app, [__MODULE__, :code_reloader], false)

This pattern — reading config at compile time and validating it against runtime — is Phoenix-specific. Elixir core reads config only at runtime.