4ea9a884aa
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.
118 lines
4.9 KiB
Markdown
118 lines
4.9 KiB
Markdown
# 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)
|
|
|
|
```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.
|