Elixir Roadmap Completo: Do Basico ao Avancado
> Este Livebook foi desenhado como trilha progressiva de estudo e referencia de arquitetura. > Objetivo: explicar com profundidade o que usar, por que usar, e como validar na pratica.
Como usar este Livebook
- Execute as celulas em ordem na primeira passada.
- Refaca os exercicios sem olhar as respostas sugeridas.
- Adapte os exemplos para um dominio real (faturamento, pedidos, autenticacao, etc).
- Mantenha foco em programacao funcional: dados entram, dados saem, efeitos isolados.
Roadmap
- Fundamentos de linguagem e pensamento funcional
- Ferramentas de produtividade (IEx, Mix, testes)
- Scripts robustos e automacao
- Banco de dados com Ecto
- Concorrencia e OTP
- Phoenix API profissional
- Observabilidade, seguranca e performance
- Cloud e operacao
- Elixir avancado (linguagem, runtime e distribuicao)
- Especializacoes opcionais (LiveView, umbrella, CRDT, Nerves, internals)
1. Setup e ciclo de desenvolvimento
1.1 Verificacao de ambiente
System.version()
Explicacao:
-
System.version/0retorna a versao de Elixir em runtime. - Esta chamada e util para validar compatibilidade de exemplos e bibliotecas.
1.2 Comandos base de projeto
mix new playground
cd playground
mix test
iex -S mix
Motivo de cada comando:
-
mix newcria estrutura minima de app Elixir. -
mix testvalida que o projeto inicia com suite verde. -
iex -S mixabre shell interativo carregando a aplicacao.
1.3 Exercicio
-
Crie um projeto
roadmap_playground. -
Rode
mix teste confirme resultado verde. -
Abra
iex -S mixe executeSystem.version().
2. Fundamentos da linguagem com explicacao linha a linha
2.1 Imutabilidade e rebind
number = 10
number = number + 5
number
Linha a linha:
-
number = 10: cria binding paranumberapontando para o valor10. -
number = number + 5: cria novo binding paranumber, agora com15. -
number: retorna o valor mais recente associado ao nome.
Motivo do modelo:
- Elimina classe inteira de bugs por estado mutavel compartilhado.
- Facilita raciocinio em concorrencia.
2.2 Pattern matching estrutural
{:ok, user} = {:ok, %{id: 1, name: "Ana"}}
%{id: id, name: name} = user
{id, name}
Linha a linha:
-
A tupla
{:ok, user}exige formato exato de sucesso. -
O map pattern
%{id: id, name: name}extrai campos com nomes explicitos. -
{id, name}confirma dados extraidos.
Motivo:
- Contratos de entrada ficam auto-documentados no proprio codigo.
- Fluxos invalidos falham cedo.
2.3 Funcoes pequenas e composicao
defmodule Price do
def apply_discount(total, percent) when is_number(total) and is_number(percent) do
total
|> Kernel.*(1 - percent / 100)
|> Float.round(2)
end
end
Price.apply_discount(200.0, 10)
Linha a linha:
-
defmodule Price: define fronteira de responsabilidade. -
Guard
when ...: valida precondicoes no contrato da funcao. - Pipe aplica transformacoes em sequencia legivel.
-
Float.round(2)fixa precisao para representacao de dinheiro no exemplo.
Motivo:
- Funcoes pequenas sao testaveis e reutilizaveis.
- Pipe favorece leitura declarativa.
2.4 case, cond, with
defmodule Parser do
def parse_positive_int(text) do
case Integer.parse(text) do
{value, ""} when value > 0 -> {:ok, value}
_ -> {:error, :invalid_positive_int}
end
end
def classify_temp(temp) do
cond do
temp < 10 -> :cold
temp < 25 -> :mild
true -> :hot
end
end
def parse_and_classify(text) do
with {:ok, value} <- parse_positive_int(text) do
{:ok, classify_temp(value)}
end
end
end
{Parser.parse_positive_int("12"), Parser.parse_and_classify("30")}
Motivo de cada construcao:
-
case: melhor quando decisao depende de forma do dado. -
cond: melhor para regras booleanas. -
with: melhor para pipeline de sucesso/erro com tuplas.
2.5 Exercicios
-
Crie
parse_email/1que retorna{:ok, email}ou{:error, :invalid_email}. -
Crie
withque chamaparse_email/1eload_user_by_email/1. - Faca fallback de erro sem excecao como fluxo normal.
3. Colecoes, Enum, Stream e recursao
3.1 Enum (eager) vs Stream (lazy)
values = 1..1_000_000
first_five_evens_times_three =
values
|> Stream.map(&(&1 * 3))
|> Stream.filter(&(rem(&1, 2) == 0))
|> Enum.take(5)
first_five_evens_times_three
Linha a linha:
-
valuesdefine range grande. -
Stream.mapnao processa tudo de imediato. -
Stream.filtercontinua lazy. -
Enum.take(5)dispara processamento apenas do necessario.
Motivo:
- Reduz uso de memoria em volumes altos.
- Permite pipelines previsiveis para ETL.
3.2 Recursao com acumulador
defmodule SumList do
def run(list), do: do_run(list, 0)
defp do_run([], acc), do: acc
defp do_run([head | tail], acc), do: do_run(tail, acc + head)
end
SumList.run([1, 2, 3, 4])
Motivo:
- Recursao e base da iteracao na BEAM.
- Acumulador evita recomputacao e melhora clareza de estado de progresso.
3.3 Exercicios
-
Implemente
product/1recursivo. -
Implemente
count_if/2semEnum.count/2. -
Compare versao recursiva com versao
Enume explique trade-off.
4. Erros como dados e contratos claros
4.1 Tuplas de sucesso/erro
defmodule AuthFlow do
def sign_in(email, pass) do
with {:ok, user} <- find_user(email),
:ok <- validate_password(user, pass),
{:ok, token} <- issue_token(user) do
{:ok, token}
else
{:error, reason} -> {:error, reason}
end
end
defp find_user("ana@example.com"), do: {:ok, %{id: 1, email: "ana@example.com", hash: "123"}}
defp find_user(_), do: {:error, :not_found}
defp validate_password(%{hash: hash}, pass) when hash == pass, do: :ok
defp validate_password(_, _), do: {:error, :invalid_credentials}
defp issue_token(user), do: {:ok, "token-#{user.id}"}
end
AuthFlow.sign_in("ana@example.com", "123")
Por que assim:
- Fluxo explicito de erro e sucesso.
- Nao mistura excecao com controle normal de negocio.
- Facilita testes de cenarios negativos.
4.2 Exercicios
-
Adicione etapa de
check_user_status/1nowith. -
Retorne
{:error, :blocked_user}quando status for"blocked". - Escreva testes para cada ramo.
5. Arquitetura funcional aplicada
5.1 Regra pura separada de IO
defmodule CheckoutDomain do
def compute_total(items, policy) do
subtotal = items |> Enum.map(&(&1.price * &1.qty)) |> Enum.sum()
apply_policy(subtotal, policy)
end
defp apply_policy(subtotal, %{type: :percent, value: percent}), do: Float.round(subtotal * (1 - percent / 100), 2)
defp apply_policy(subtotal, %{type: :flat, value: value}), do: max(subtotal - value, 0)
end
defmodule CheckoutUseCase do
def execute(items, policy, save_fn) do
total = CheckoutDomain.compute_total(items, policy)
save_fn.(%{items: items, total: total})
end
end
CheckoutUseCase.execute([
%{price: 100.0, qty: 1},
%{price: 50.0, qty: 2}
], %{type: :percent, value: 10}, fn order -> {:ok, order} end)
Motivo de design:
- Dominio nao depende de banco, HTTP ou fila.
-
Caso de uso orquestra efeitos por injecao de dependencia funcional (
save_fn).
5.2 Exercicios
-
Adicione cupom
:buy_x_get_yno dominio. - Mantenha funcao pura sem acesso a estado global.
- Escreva testes unitarios da nova regra.
6. Scripts robustos e automacao
6.1 Script com parametros e validacao
args = ["--from", "2026-01-01", "--to", "2026-01-31", "--format", "json"]
{opts, _rest, _invalid} =
OptionParser.parse(args,
switches: [from: :string, to: :string, format: :string],
aliases: [f: :from, t: :to]
)
params = %{
from: Keyword.get(opts, :from),
to: Keyword.get(opts, :to),
format: Keyword.get(opts, :format, "json")
}
params
Motivo:
- Parametros declarados com tipo e alias.
- Defaults controlados explicitamente.
- Estrutura final em map facilita serializacao e pipeline.
6.2 Template funcional para script produtivo
defmodule ScriptTemplate do
def run(input, load_fn, transform_fn, persist_fn) do
with {:ok, loaded} <- load_fn.(input),
{:ok, transformed} <- transform_fn.(loaded),
:ok <- persist_fn.(transformed) do
:ok
else
{:error, reason} -> {:error, reason}
end
end
end
load_fn = fn value -> {:ok, value} end
transform_fn = fn value -> {:ok, String.upcase(value)} end
persist_fn = fn _ -> :ok end
ScriptTemplate.run("hello", load_fn, transform_fn, persist_fn)
6.3 Exercicios
-
Adicione retry funcional ao
persist_fn. -
Crie modo
dry_runsem gravacao. - Retorne metrica de tempo total em milissegundos.
7. Ecto e banco de dados
7.1 Schema e changeset
defmodule UserEntity do
use Ecto.Schema
import Ecto.Changeset
@required_fields [:email, :name]
schema "users" do
field :email, :string
field :name, :string
field :status, :string, default: "active"
timestamps(type: :utc_datetime)
end
def changeset(struct, attrs) do
struct
|> cast(attrs, @required_fields ++ [:status])
|> validate_required(@required_fields)
|> validate_format(:email, ~r/@/)
|> validate_length(:name, min: 2, max: 120)
end
end
Motivo de cada etapa:
-
castlimita campos aceitos. -
validate_requiredgarante obrigatorios. -
validate_formatmodela contrato sem excecao. -
validate_lengthprotege consistencia de dados.
7.2 Contexto como fronteira
defmodule AccountsContext do
import Ecto.Query, warn: false
def list_active_users(repo) do
from(u in UserEntity, where: u.status == "active")
|> repo.all()
end
def create_user(repo, attrs) do
%UserEntity{}
|> UserEntity.changeset(attrs)
|> repo.insert()
end
end
Motivo:
- Controller/chamada externa nao conhece query detalhada.
- Regras de persistencia ficam encapsuladas em API interna clara.
7.3 Exercicio
-
Adicione
deactivate_user/2com update seguro. -
Garanta retorno
{:ok, user}ou{:error, changeset}. - Escreva teste de integracao para os dois fluxos.
8. Concorrencia e OTP
8.1 Processos e mailbox
parent = self()
spawn(fn ->
send(parent, {:result, 42})
end)
receive do
{:result, value} -> {:ok, value}
after
1_000 -> {:error, :timeout}
end
Motivo:
- Processo leve e isolado.
- Mensagem explicita entre fronteiras de concorrencia.
- Timeout evita bloqueio infinito.
8.2 Task com limite de concorrencia
1..20
|> Task.async_stream(fn n -> n * 10 end, max_concurrency: 5, timeout: 3_000)
|> Enum.map(fn {:ok, value} -> value end)
|> Enum.take(5)
Motivo:
-
Controle de throughput com
max_concurrency. - Evita saturar CPU/IO de forma descontrolada.
8.3 GenServer para estado coordenado
defmodule CounterServer do
use GenServer
def start_link(initial), do: GenServer.start_link(__MODULE__, initial, name: __MODULE__)
def increment, do: GenServer.call(__MODULE__, :increment)
def current, do: GenServer.call(__MODULE__, :current)
@impl true
def init(initial), do: {:ok, initial}
@impl true
def handle_call(:increment, _from, state), do: {:reply, state + 1, state + 1}
@impl true
def handle_call(:current, _from, state), do: {:reply, state, state}
end
8.4 Supervisor
children = [
{CounterServer, 0}
]
Supervisor.start_link(children, strategy: :one_for_one)
8.5 Exercicios
-
Crie
WalletServercomcredit/1,debit/1,balance/0. -
Isole regra de saldo em modulo puro (
WalletLogic). - Adicione supervisor com estrategia apropriada.
9. Phoenix API profissional
9.1 Controller fino e contexto
defmodule PostControllerExample do
def create(params, create_post_fn) do
with {:ok, post} <- create_post_fn.(params) do
{:created, post}
else
{:error, reason} -> {:unprocessable_entity, reason}
end
end
end
PostControllerExample.create(%{"title" => "Novo"}, fn attrs -> {:ok, attrs} end)
Motivo:
- Controller orquestra HTTP/retorno.
- Regra de negocio vive no contexto.
- Erros padronizados por contrato de tupla.
9.2 Fallback padrao
defmodule FallbackExample do
def call({:error, :not_found}), do: {:not_found, %{error: "not_found"}}
def call({:error, :validation}), do: {:unprocessable_entity, %{error: "validation"}}
end
FallbackExample.call({:error, :not_found})
9.3 Exercicio
-
Padronize erros
:unauthorized,:forbidden,:conflict. - Garanta corpo JSON consistente para todos.
- Cubra com testes de contrato.
10. Observabilidade, seguranca e performance
10.1 Telemetria
:telemetry.attach(
"roadmap-query-handler",
[:my_app, :repo, :query],
fn _event, measurements, metadata, _config ->
%{duration: measurements.total_time, query: metadata.query}
end,
nil
)
Motivo:
- Instrumenta sem acoplar dominio a logging.
- Permite metricas e alertas orientados a evento.
10.2 Seguranca por contrato
defmodule PayloadGuard do
def validate_size(payload, max_size) when byte_size(payload) <= max_size, do: :ok
def validate_size(_, _), do: {:error, :payload_too_large}
end
PayloadGuard.validate_size("abc", 5)
10.3 Performance orientada a medicao
defmodule PerfSample do
def map_enum(values), do: Enum.map(values, &(&1 * 2))
def map_for(values), do: for n <- values, do: n * 2
end
values = Enum.to_list(1..50_000)
{t1, _} = :timer.tc(fn -> PerfSample.map_enum(values) end)
{t2, _} = :timer.tc(fn -> PerfSample.map_for(values) end)
%{enum_us: t1, for_us: t2}
Exercicio:
-
Substitua
EnumporStreamem volume grande. - Meca novamente e compare latencia e memoria.
- Documente o criterio da decisao.
11. AWS e operacao
11.1 Config por ambiente
config = %{
access_key_id: System.get_env("AWS_ACCESS_KEY_ID"),
secret_access_key: System.get_env("AWS_SECRET_ACCESS_KEY"),
region: System.get_env("AWS_REGION", "us-east-1")
}
config
Motivo:
- Evita segredo hardcoded.
- Permite deploy com mesmo artefato em ambientes distintos.
11.2 Script funcional para envio de evento
defmodule EventPublisher do
def publish(payload, encode_fn, send_fn) do
with {:ok, encoded} <- encode_fn.(payload),
:ok <- send_fn.(encoded) do
:ok
else
{:error, reason} -> {:error, reason}
end
end
end
encode_fn = fn payload -> {:ok, :erlang.term_to_binary(payload)} end
send_fn = fn _ -> :ok end
EventPublisher.publish(%{event: "invoice.created"}, encode_fn, send_fn)
Exercicio:
- Modele retry com backoff.
- Adicione idempotency key por evento.
- Registre telemetria por tentativa.
12. Elixir avancado
12.1 Macros com criterio
defmodule EnsureMacro do
defmacro ensure(expression) do
quote do
case unquote(expression) do
{:ok, value} -> value
{:error, reason} -> raise "ensure_failed=#{inspect(reason)}"
end
end
end
end
defmodule MacroDemo do
import EnsureMacro
def run do
ensure({:ok, 10})
end
end
MacroDemo.run()
Motivo:
- Macro para reduzir boilerplate repetitivo de compilacao.
- Nao usar macro para esconder regra complexa de negocio.
12.2 Protocols e behaviours
defprotocol Exportable do
def export(data)
end
defimpl Exportable, for: Map do
def export(data), do: {:ok, data}
end
defmodule ExportAdapter do
@callback deliver(term()) :: :ok | {:error, term()}
end
12.3 Typespecs e dialyzer
defmodule MoneyMath do
@type amount :: non_neg_integer()
@spec add(amount(), amount()) :: amount()
def add(a, b), do: a + b
end
MoneyMath.add(10, 20)
12.4 Bitstrings
defmodule PacketDecoder do
def decode(<>), do: {:ok, %{version: version, payload: payload}}
def decode(_), do: {:error, :invalid_packet}
end
PacketDecoder.decode(<<1, 0, 3, "abc"::binary>>)
12.5 Exercicios
-
Crie protocol
RenderableparaMapeList. -
Crie behaviour
Notifiercom adaptersConsoleeNull. -
Decodifique pacote com campos
flagsechecksumem bitstring.
13. Runtime, distribuicao e producao
13.1 ETS e DETS
table = :ets.new(:sessions, [:set, :public, :named_table, read_concurrency: true])
true = :ets.insert(table, {"token-1", %{user_id: 1}})
:ets.lookup(table, "token-1")
{:ok, dets_table} = :dets.open_file(:sessions_disk, [type: :set, file: 'sessions_disk.dets'])
:ok = :dets.insert(dets_table, {"token-1", %{user_id: 1}})
:dets.lookup(dets_table, "token-1")
:dets.close(dets_table)
Motivo:
-
:etspara leitura/escrita muito rapida em memoria local. -
:detspara persistencia local simples.
13.2 :gen_statem para estados explicitos
defmodule DoorFSM do
@behaviour :gen_statem
def start_link, do: :gen_statem.start_link({:local, __MODULE__}, __MODULE__, :locked, [])
def callback_mode, do: :state_functions
def init(state), do: {:ok, state, %{}}
def locked(:cast, :unlock, data), do: {:next_state, :unlocked, data}
def locked(_, _, data), do: {:keep_state, data}
def unlocked(:cast, :lock, data), do: {:next_state, :locked, data}
def unlocked(_, _, data), do: {:keep_state, data}
end
13.3 Distribuicao BEAM
Node.self()
Node.list()
Regras de desenho:
- Timeout em toda chamada remota.
- Operacoes idempotentes para retry.
- Plano de reconciliacao apos netsplit.
13.4 Exercicio
-
Modele agregador distribuido com
:rpc.call/4. - Trate timeout e fallback por nodo.
- Emita evento de telemetria por falha de nodo.
14. Especializacoes opcionais
14.1 LiveView
defmodule CounterLiveMini do
use Phoenix.LiveView
def mount(_params, _session, socket), do: {:ok, assign(socket, :count, 0)}
def handle_event("inc", _params, socket), do: {:noreply, update(socket, :count, &(&1 + 1))}
end
14.2 Umbrella
mix new platform --umbrella
14.3 CRDT/Consenso
- CRDT para convergencia eventual em particao.
- Consenso forte apenas para invariantes criticas.
14.4 Nerves
mix nerves.new sensor_hub
14.5 Internals BEAM
-
AST com
quote/unquote. -
Chunks BEAM com
:beam_lib. - Custo de abstracao medido com benchmark/profiler.
15. Plano de exercicios por nivel
Nivel 1 (Fundamentos)
-
Parser de parametros com
caseewith. - Calculadora funcional de desconto com testes.
-
Recursao para
sum,product,count_if.
Nivel 2 (Persistencia)
-
Contexto
Accountscomcreate/list/get. -
Ecto.Multipara fluxo de fatura + pagamento. - Backfill seguro com lotes e idempotencia.
Nivel 3 (API e OTP)
-
API de
postscom fallback padrao. -
GenServer+Supervisorpara contador distribuido por key. - Telemetria de latencia por endpoint.
Nivel 4 (Producao)
- Pipeline assincrono com back-pressure.
-
Cache com
:etse invalidacao por evento. - Simulacao de falha de nodo com estrategia de recuperacao.
Nivel 5 (Avancado)
- Protocol + behaviour para exportacao multi-formato.
- Bitstring parser de protocolo binario com checksum.
- Macro pequena para reduzir boilerplate seguro.
16. Checklist de maturidade tecnica
- Separa dominio puro de fronteiras de IO.
-
Modela erros como dados (
{:error, reason}). - Tem suite de testes com casos de borda e falha.
- Usa observabilidade para decidir otimizacao.
- Consegue operar release com rollback e runbook.
-
Entende quando usar
GenServer,:gen_statem,:ets,:dets. - Consegue explicar cada escolha tecnica em termos de trade-off.
17. Proximos passos recomendados
- Criar um projeto guia real (billing platform) usando todas as camadas.
- Medir baseline de latencia e throughput antes de otimizar.
- Documentar ADRs (Architecture Decision Records) para escolhas principais.
- Rodar revisoes tecnicas quinzenais com foco em clareza de fronteiras.