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.
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.