# 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). ### When to Use **Triggers:** - Your module has grown beyond ~300 lines with distinct sub-responsibilities - External code only needs the parent module but implementation is complex - You find yourself prefixing private functions with a concept name (e.g., `scope_push`, `scope_pop`) **Example — before:** ```elixir # Everything crammed into one flat module defmodule MyApp.Router do # 800 lines mixing route compilation, scope tracking, and helper generation def compile_route(...), do: # ... def push_scope(...), do: # ... def pop_scope(...), do: # ... def generate_helper(...), do: # ... end ``` **Example — after:** ```elixir # Parent module is the public API defmodule MyApp.Router do # Public API delegates to focused submodules def compile(routes), do: MyApp.Router.Compiler.compile(routes) end # Submodules handle implementation defmodule MyApp.Router.Compiler do @moduledoc false # ... end defmodule MyApp.Router.Scope do @moduledoc false # ... end ``` ### When NOT to Use **Don't use this when:** - The module is small and cohesive (< 200 lines) - Nesting would exceed 3 levels (`A.B.C.D` is usually too deep) - The "submodule" has its own independent public API (make it a sibling instead) **Over-application example:** ```elixir # Over-nesting a simple utility defmodule MyApp.Utils.String.Formatting.Case do def upcase(s), do: String.upcase(s) end ``` **Better alternative:** ```elixir defmodule MyApp.StringUtils do def upcase(s), do: String.upcase(s) end ``` **Why:** Nesting should reflect genuine conceptual hierarchy. If you're creating submodules for 2-3 functions that don't have independent complexity, you're adding navigational overhead without architectural benefit. --- ## 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. ### When to Use **Triggers:** - You're writing a new module and need to decide function ordering - A module has grown organically and functions are scattered randomly - You're reviewing code and finding it hard to locate the public API **Example — before:** ```elixir defmodule UserService do defp hash_password(pw), do: # ... def create(attrs) do # uses hash_password end def start_link(opts), do: GenServer.start_link(__MODULE__, opts) defp validate(attrs), do: # ... def get(id), do: # ... end ``` **Example — after:** ```elixir defmodule UserService do # Lifecycle def start_link(opts), do: GenServer.start_link(__MODULE__, opts) # Public API def create(attrs), do: # ... def get(id), do: # ... # Private helpers defp validate(attrs), do: # ... defp hash_password(pw), do: # ... end ``` ### When NOT to Use **Don't use this when:** - You have a tiny module (< 5 functions) where ordering doesn't matter much - The module is a pure data module (just a struct + typespec) - "Logical grouping" puts closely related public+private pairs together for readability **Over-application example:** ```elixir # Forcing start_link to the top in a module that isn't an OTP process defmodule MyApp.Parser do # This module has no lifecycle — don't force OTP ordering def start_link(_), do: raise "not a process" # Just to match the pattern? def parse(input), do: # ... end ``` **Better alternative:** ```elixir defmodule MyApp.Parser do @moduledoc "Parses input format X into structs" def parse(input), do: # ... def parse!(input), do: # ... defp tokenize(input), do: # ... end ``` **Why:** The ordering convention exists to make OTP-aware modules predictable. For non-OTP modules, lead with the primary public function (the one callers reach for first) and let the rest follow logically. --- ## 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. ### When to Use **Triggers:** - A module exists purely for internal code organization - Users of your library should never call this module directly - The module is a helper that could change or disappear between versions **Example — before:** ```elixir defmodule MyApp.Repo.QueryBuilder do @moduledoc """ Builds Ecto queries for the Repo module. """ # Now appears in docs, users try to call it directly end ``` **Example — after:** ```elixir defmodule MyApp.Repo.QueryBuilder do @moduledoc false # Hidden from docs, clearly internal end ``` ### When NOT to Use **Don't use this when:** - The module is part of your public API (even if rarely used) - Users need to implement callbacks or extend the module - The module defines a behaviour or protocol that others implement **Over-application example:** ```elixir # Hiding a module that users actually need defmodule MyApp.Errors do @moduledoc false # But users need to pattern-match on these! defmodule NotFound do defexception [:message] end end ``` **Better alternative:** ```elixir defmodule MyApp.Errors do @moduledoc "Error types raised by MyApp operations." defmodule NotFound do @moduledoc "Raised when a resource cannot be found." defexception [:message] end end ``` **Why:** `@moduledoc false` means "this is not for you." If users catch your exceptions or match on your structs, they need documentation. Hide implementation details, not public contracts. --- ## 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`. ### When to Use **Triggers:** - Your struct has fields that make no sense as `nil` (creating one without them is a bug) - You're modeling a value object where all fields define its identity - Incomplete structs would cause confusing runtime errors later **Example — before:** ```elixir defmodule Order do defstruct [:id, :customer_id, :items, :total] # Can create %Order{} with everything nil — meaningless end ``` **Example — after:** ```elixir defmodule Order do @enforce_keys [:customer_id, :items, :total] defstruct [:id | @enforce_keys] # %Order{} without required fields -> immediate compile/runtime error end ``` ### When NOT to Use **Don't use this when:** - The struct is built incrementally (e.g., a changeset or builder pattern) - Most fields have sensible defaults - The struct represents configuration where partial specs are valid **Over-application example:** ```elixir # Enforcing keys on a struct that's built in stages defmodule FormState do @enforce_keys [:step, :name, :email, :address, :payment] defstruct @enforce_keys # Can't create a partial form state for step 1! end ``` **Better alternative:** ```elixir defmodule FormState do defstruct step: 1, name: nil, email: nil, address: nil, payment: nil # Built incrementally as user progresses through steps end ``` **Why:** `@enforce_keys` is for structs that represent *complete* values. If your struct represents an evolving state or has legitimate intermediate forms, enforcing all keys makes construction impossible at early stages. --- ## 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. ### When to Use **Triggers:** - Your `use` macro needs to give the caller access to specific functions - You want to control exactly which functions enter the caller's namespace - The imported functions are central to the DSL or workflow the module enables **Example — before:** ```elixir defmacro __using__(_opts) do quote do # Imports EVERYTHING from three modules — namespace soup import MyApp.Router.Helpers import MyApp.Router.Scoping import MyApp.Router.Compilation end end ``` **Example — after:** ```elixir defmacro __using__(_opts) do quote do import MyApp.Router, only: [get: 2, post: 2, resources: 2, scope: 2] import MyApp.Conn, only: [assign: 3, put_status: 2] end end ``` ### When NOT to Use **Don't use this when:** - The caller could just `import` what they need themselves - You're importing utility functions that aren't part of your module's "DSL" - The imports create naming conflicts with common functions **Over-application example:** ```elixir defmacro __using__(_opts) do quote do import MyApp.Utils # 50+ utility functions dumped into caller import Enum # Why? Caller can do this themselves import Map # Polluting namespace with standard lib end end ``` **Better alternative:** ```elixir defmacro __using__(_opts) do quote do # Only import what THIS module's workflow requires import MyApp.DSL, only: [field: 2, validate: 1] end end ``` **Why:** `use` should import the *minimum* needed for the module's intended workflow. If you're importing generic utilities, you're making decisions for the caller that they should make themselves. --- ## 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. ### When to Use **Triggers:** - Multiple modules from the same parent namespace are used together - Full module paths are making code hard to read - The aliased modules are used frequently (3+ times in the file) **Example — before:** ```elixir def process(input) do Phoenix.Router.Route.new(input) |> Phoenix.Router.Scope.apply_scope(Phoenix.Router.Scope.current()) |> Phoenix.Router.Helpers.generate() end ``` **Example — after:** ```elixir alias Phoenix.Router.{Route, Scope, Helpers} def process(input) do Route.new(input) |> Scope.apply_scope(Scope.current()) |> Helpers.generate() end ``` ### When NOT to Use **Don't use this when:** - A module is referenced only once (inline the full path) - The alias would be ambiguous (two `Route` modules from different namespaces) - You're in a test file and the full path makes assertions clearer **Over-application example:** ```elixir # Aliasing a module used exactly once alias MyApp.Workers.BatchProcessor def run do BatchProcessor.start() # Only reference — alias adds noise end ``` **Better alternative:** ```elixir def run do MyApp.Workers.BatchProcessor.start() # One use — full path is fine end ``` **Why:** Aliases trade verbosity for indirection. When a module appears once, the full path is documentation. When it appears many times, the alias is readability. Find the crossover point (typically 2-3 uses). --- ## 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. ### When to Use **Triggers:** - A struct field stores a boolean value - The field answers a yes/no question about the struct - You want the field's type to be self-evident without checking typespecs **Example — before:** ```elixir defstruct [:path, :trailing_slash, :verified] # Is :trailing_slash the slash character? A boolean? The position? ``` **Example — after:** ```elixir defstruct [:path, :trailing_slash?, :verified?] # Immediately clear these are booleans ``` ### When NOT to Use **Don't use this when:** - The field isn't a boolean (e.g., `:status` that can be `:active`/`:inactive`) - You're working with external serialization that can't handle `?` in keys - The field represents a count, enum, or value rather than a yes/no question **Over-application example:** ```elixir defstruct [:user?, :admin?, :count?] # :user? — is this "is user present?" or "the user value"? # :count? — definitely not a boolean ``` **Better alternative:** ```elixir defstruct [:user, :admin?, :count] # :user is the user struct, :admin? is a boolean, :count is an integer ``` **Why:** The `?` suffix should only mark genuine booleans. Using it on non-boolean fields creates confusion about the field's type and breaks the convention's usefulness as a type signal.