# Module Organization Patterns How modules are structured, named, and organized in Elixir core and Phoenix. ## 1. One Module per Concept, Nested for Sub-Concepts **Source:** `lib/elixir/lib/` directory structure ``` gen_server.ex — GenServer (the main module) task.ex — Task (the main module) task/supervised.ex — Task.Supervised (internal implementation) supervisor.ex — Supervisor (the main module) ``` **Source:** `lib/phoenix/` directory structure ``` channel.ex — Phoenix.Channel channel/ — Phoenix.Channel.* submodules server.ex — Phoenix.Channel.Server router.ex — Phoenix.Router router/ — Phoenix.Router.* submodules route.ex — Phoenix.Router.Route scope.ex — Phoenix.Router.Scope resource.ex — Phoenix.Router.Resource endpoint.ex — Phoenix.Endpoint endpoint/ — Phoenix.Endpoint.* submodules supervisor.ex — Phoenix.Endpoint.Supervisor ``` **Why:** The parent module is the public API. Submodules handle implementation details. File layout mirrors module hierarchy — `Phoenix.Router.Route` lives in `router/route.ex`. **Anti-pattern:** Putting all modules in a flat directory, or nesting too deeply (more than 3 levels is usually a sign of over-engineering). --- ## 2. Public API at the Top, Private Functions at the Bottom **Source:** `lib/elixir/lib/agent.ex` (full module structure) ```elixir defmodule Agent do @moduledoc "..." # Types @type on_start :: ... @type name :: ... @type agent :: ... @type state :: ... # child_spec (for supervisors) def child_spec(arg) do ... end # __using__ macro defmacro __using__(opts) do ... end # Public API def start_link(fun, options \\ []) do ... end def start(fun, options \\ []) do ... end def get(agent, fun, timeout \\ 5000) do ... end def get_and_update(agent, fun, timeout \\ 5000) do ... end def update(agent, fun, timeout \\ 5000) do ... end def cast(agent, fun) do ... end def stop(agent, reason \\ :normal, timeout \\ :infinity) do ... end end ``` The order: 1. `@moduledoc` 2. Types 3. `child_spec` / `__using__` 4. `start_link` / `start` (lifecycle) 5. Public API functions (alphabetical or logical grouping) 6. `stop` (lifecycle end) **Anti-pattern:** Mixing private helpers between public functions, or putting `start_link` at the bottom where supervisors have to hunt for it. --- ## 3. `@moduledoc false` for Internal Modules **Source:** `lib/phoenix/router/route.ex:5-7` ```elixir # This module defines the Route struct that is used # throughout Phoenix's router. This struct is private # as it contains internal routing information. @moduledoc false ``` **Why:** Internal modules that exist for code organization but aren't part of the public API get `@moduledoc false`. They won't appear in generated documentation. **Anti-pattern:** Documenting internal modules and confusing users about what's public API vs implementation detail. --- ## 4. Struct Definition Conventions **Source:** `lib/elixir/lib/task.ex:279-296` ```elixir @enforce_keys [:mfa, :owner, :pid, :ref] defstruct @enforce_keys @type t :: %__MODULE__{ mfa: mfa(), owner: pid(), pid: pid() | nil, ref: ref() } ``` **Source:** `lib/phoenix/router/route.ex:30-46` ```elixir defstruct [ :verb, :line, :kind, :path, :hosts, :plug, :plug_opts, :helper, :private, :pipe_through, :assigns, :metadata, :trailing_slash?, :warn_on_verify? ] @type t :: %Route{} ``` Two patterns: 1. **Task:** `@enforce_keys` + minimal struct — all fields required, enforced at creation 2. **Route:** All optional fields listed — flexible construction **Why:** Use `@enforce_keys` when a struct is meaningless without certain fields. Omit it for structs built incrementally. **Anti-pattern:** Never using `@enforce_keys` — allows creating invalid structs that crash later when a required field is `nil`. --- ## 5. Selective Imports in `__using__` **Source:** `lib/phoenix/channel.ex:463-464` ```elixir import unquote(__MODULE__) import Phoenix.Socket, only: [assign: 3, assign: 2] ``` **Source:** `lib/phoenix/router.ex:271-275` ```elixir import Phoenix.Router import Plug.Conn import Phoenix.Controller ``` **Why:** The `use` macro sets up the module's environment — importing the functions you'll need. Phoenix.Channel imports `assign` from Socket because channels work with sockets constantly. **Anti-pattern:** Importing everything without restriction — namespace pollution and hard-to-trace function origins. --- ## 6. Alias at Module Scope for Readability **Source:** `lib/phoenix/router.ex:268` ```elixir alias Phoenix.Router.{Resource, Scope, Route, Helpers} ``` **Why:** Multi-alias reduces repetition and groups related modules. The curly-brace syntax makes it clear these all share a parent namespace. **Anti-pattern:** Using full module paths everywhere (`Phoenix.Router.Resource.new(...)`) — verbose and hard to read. --- ## 7. Boolean-Suffixed Fields in Structs **Source:** `lib/phoenix/router/route.ex:43-44` ```elixir :trailing_slash?, :warn_on_verify? ``` **Why:** The `?` suffix on struct fields mirrors the Elixir convention for boolean-returning functions. It makes the field's type obvious without checking the typespec. **Anti-pattern:** Using bare names like `:trailing_slash` or `:has_trailing_slash` — the `?` convention is more idiomatic and self-documenting.