# 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:911-919` (child_spec defaults to :permanent via Supervisor.child_spec) **Source (Phoenix):** `lib/phoenix/channel.ex:464-472` (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):** `lib/elixir/lib/agent.ex:187` (standard on_start type: `{:ok, pid} | {:error, ...}`) **Source (Phoenix):** `lib/phoenix/router.ex:2-6` (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:899-919` (__using__ generates child_spec + @behaviour) **Source (Phoenix):** `lib/phoenix/channel.ex:450-485` (__using__ generates child_spec + behaviour + DSL setup) --- ## 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:899` — simple `__using__` (behaviour + child_spec + defaults) **Source (Phoenix):** `lib/phoenix/router.ex:288-312` — complex DSL setup with attribute accumulation, imports, and @before_compile --- ## 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 pattern) **Source (Phoenix):** `lib/phoenix/channel.ex:463` (`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) ```elixir 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. --- ## Telemetry | Aspect | Elixir Core | Phoenix | |--------|-------------|---------| | **Built-in events** | None (telemetry is a separate library) | Extensive event catalog | | **Instrumentation** | Manual by library authors | Baked into router, endpoint, socket | | **Event naming** | Varies by library | `[:phoenix, :component, :phase]` convention | | **Logging** | `Logger` calls | Telemetry → Logger adapter (`Phoenix.Logger`) | **Source (Phoenix):** `lib/phoenix/logger.ex:7-50` (telemetry event catalog) **Source (Phoenix):** `lib/phoenix/router.ex:400-438` (telemetry in router dispatch) Phoenix wraps every request dispatch in telemetry start/stop/exception events. This provides distributed tracing, monitoring, and logging without any application code changes. --- ## Testing | Aspect | Elixir Core | Phoenix | |--------|-------------|---------| | **Test helper** | `ExUnit.Case` | `Phoenix.ConnTest`, `Phoenix.ChannelTest` | | **Test subject** | Module functions | Endpoint (full plug pipeline) | | **Communication** | Direct function calls | HTTP verbs (ConnTest), messages (ChannelTest) | | **Isolation** | Process per test | Process per test + sandbox (Ecto) | **Source (Phoenix):** `lib/phoenix/test/conn_test.ex:1-30` (endpoint-based integration testing) **Source (Phoenix):** `lib/phoenix/test/channel_test.ex:1-30` (process-based channel testing) Phoenix test helpers test at the integration level by default — `ConnTest` dispatches through the full plug pipeline, `ChannelTest` exercises the full channel lifecycle via message passing. This catches middleware bugs that unit tests miss.