Part 4 · 1 chapters · ~8 min

Idioms

with for happy paths, multi-clause functions over conditionals, assertive code, Phoenix contexts as domain boundaries, when to use processes (and when not), avoiding single-process bottlenecks, naming and module structure, @moduledoc and doctests, Credo, and common code-review comments.

5

Happy paths and boundaries

code
def create_transfer(params, user) do
  with {:ok, attrs} <- validate(params),
       {:ok, account} <- Accounts.fetch_for(user, attrs.from),
       :ok <- Limits.check(account, attrs.amount_kobo),
       {:ok, transfer} <- Ledger.hold_and_create(account, attrs) do
    {:ok, transfer}
  else
    {:error, :not_found} -> {:error, :not_found}
    {:error, :limit_exceeded} = e -> e
    {:error, %Ecto.Changeset{} = cs} -> {:error, cs}
  end
end

# anti-pattern: a single GenServer every request calls becomes a bottleneck (it handles one message at a time)
# fix: ETS for shared reads, a pool or a process per key (Registry) for writes
IDIOMATIC ELIXIR
what reviewers look for
with for happy pathsChain {:ok, _} steps; handleerrors once in else.pattern-match earlyMatch in function heads instead ofnested ifs.small modules, contextsPhoenix contexts group a domain'spublic API.processes for stateNot for organising code: plainmodules for that.assertive codeCrash on the unexpected instead ofreturning nil.docs and doctests@moduledoc, @doc with examplesthat run as tests.
swipe the figure sideways, or tap expand for full screen
1/4
with
with chains steps that return {:ok, _}; the first failure falls to else, keeping the happy path flat.
flat happy pathsone error branch