Sobre nós Guias Projetos Contactos
Админка
please wait

Elixir é uma linguagem de programação funcional e concorrente, construída sobre a VM de Erlang (BEAM), conhecida pela sua tolerância a falhas e escalabilidade. Phoenix é o principal framework web para Elixir, oferecendo produtividade ao estilo de Rails com as características de desempenho que tornaram Erlang famoso em sistemas de telecomunicações. Este guia aborda a criação de aplicações web prontas para produção com Phoenix, na perspetiva de um programador sénior.

Porquê Elixir e Phoenix

Elixir traz várias vantagens únicas:

  1. Concorrência: Processos leves (não threads do SO) permitem milhões de ligações concorrentes
  2. Tolerância a Falhas: Árvores de supervisão reiniciam automaticamente processos que falham
  3. Hot Code Reloading: Implementar atualizações sem interromper ligações
  4. Programação Funcional: Estruturas de dados imutáveis evitam classes inteiras de bugs
  5. Tempo real incorporado: WebSockets e PubSub são cidadãos de primeira classe

Configurar um Projeto Elixir

Instale primeiro o Elixir no seu sistema. Em CentOS/RHEL:

# Adicionar o repositório EPEL
sudo dnf install epel-release
sudo dnf install elixir erlang

Instale o helper do Phoenix e crie um projeto:

mix archive.install hex phx_new
mix phx.new my_app
cd my_app
mix ecto.setup
mix phx.server

Estrutura de um Projeto Phoenix

Compreender a estrutura de pastas é crucial:

my_app/
├── lib/
│ ├── my_app/ # Lógica de negócio (contexts)
│ │ ├── accounts.ex # Context Accounts
│ │ └── accounts/
│ │ ├── user.ex # Schema de utilizador
│ │ └── credential.ex
│ └── my_app_web/ # Camada web
│ ├── controllers/
│ ├── views/
│ ├── templates/
│ ├── channels/ # Canais WebSocket
│ └── router.ex
├── priv/
│ └── repo/migrations/ # Migrations da base de dados
├── assets/ # Assets de frontend
└── config/ # Ficheiros de configuração

Essenciais de Sintaxe em Elixir

Elixir tem uma sintaxe distinta que tira partido, de forma extensiva, do pattern matching:

defmodule MyApp.Calculator do
# Pattern matching nas cabeças das funções
def divide(_, 0) do
{:error, "Division by zero"}
end
def divide(x, y) do
{:ok, x / y}
end
# Operador pipe para encadeamento de funções
def process_data(data) do
data
|> String.trim()
|> String.downcase()
|> String.split(",")
end
# Funções anónimas
def map_values(list) do
Enum.map(list, fn x -> x * 2 end)
# Ou sintaxe mais curta: Enum.map(list, &(&1 * 2))
end
end

Pattern matching para extrair valores:

# Extrair de map
%{id: user_id} = %{id: 123, name: "John"}
# user_id é agora 123
# Extrair de string
"rows:" <> count = "rows:456"
# count é agora "456"

Criar uma Aplicação CRUD

Definir Rotas

Em lib/myappweb/router.ex:

defmodule MyAppWeb.Router do
use MyAppWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {MyAppWeb.LayoutView, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
scope "/", MyAppWeb do
pipe_through :browser
# Recursos RESTful
resources "/users", UserController
# Ou definir individualmente:
get "/posts", PostController, :index
get "/posts/new", PostController, :new
post "/posts", PostController, :create
get "/posts/:id/edit", PostController, :edit
put "/posts/:id", PostController, :update
delete "/posts/:id", PostController, :delete
end
end

Criar Migrations

Gerar uma migration:

mix ecto.gen.migration create_posts

Edite o ficheiro de migration em priv/repo/migrations/:

defmodule MyApp.Repo.Migrations.CreatePosts do
use Ecto.Migration
def change do
create table(:posts) do
add :title, :string, null: false
add :body, :text
add :published, :boolean, default: false
add :user_id, references(:users, on_delete: :delete_all), null: false
timestamps()
end
create index(:posts, [:user_id])
create index(:posts, [:published])
end
end

Executar migrations:

mix ecto.migrate

Definir Schemas (Models)

Crie lib/my_app/blog/post.ex:

defmodule MyApp.Blog.Post do
use Ecto.Schema
import Ecto.Changeset
# Configurar serialização JSON
@derive {Jason.Encoder, only: [:id, :title, :body, :published, :inserted_at]}
schema "posts" do
field :title, :string
field :body, :string
field :published, :boolean, default: false
belongs_to :user, MyApp.Accounts.User
has_many :comments, MyApp.Blog.Comment
timestamps()
end
def changeset(post, attrs) do
post
|> cast(attrs, [:title, :body, :published])
|> validate_required([:title])
|> validate_length(:title, min: 3, max: 255)
|> assoc_constraint(:user)
end
end

Criar Context (Lógica de Negócio)

Crie lib/my_app/blog.ex:

defmodule MyApp.Blog do
import Ecto.Query
alias MyApp.Repo
alias MyApp.Blog.Post
def list_posts do
Post
|> order_by(desc: :inserted_at)
|> Repo.all()
|> Repo.preload(:user)
end
def list_published_posts do
Post
|> where(published: true)
|> order_by(desc: :inserted_at)
|> Repo.all()
|> Repo.preload(:user)
end
def get_post!(id) do
Post
|> Repo.get!(id)
|> Repo.preload([:user, :comments])
end
def create_post(user, attrs \\ %{}) do
%Post{}
|> Post.changeset(attrs)
|> Ecto.Changeset.put_assoc(:user, user)
|> Repo.insert()
end
def update_post(%Post{} = post, attrs) do
post
|> Post.changeset(attrs)
|> Repo.update()
end
def delete_post(%Post{} = post) do
Repo.delete(post)
end
end

Implementar Controller

Crie lib/myappweb/controllers/post_controller.ex:

defmodule MyAppWeb.PostController do
use MyAppWeb, :controller
alias MyApp.Blog
alias MyApp.Blog.Post
def index(conn, _params) do
posts = Blog.list_posts()
render(conn, "index.html", posts: posts)
end
def new(conn, _params) do
changeset = Post.changeset(%Post{}, %{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"post" => post_params}) do
user = conn.assigns.current_user
case Blog.create_post(user, post_params) do
{:ok, post} ->
conn
|> put_flash(:info, "Post created successfully.")
|> redirect(to: Routes.post_path(conn, :show, post))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
post = Blog.get_post!(id)
render(conn, "show.html", post: post)
end
def edit(conn, %{"id" => id}) do
post = Blog.get_post!(id)
changeset = Post.changeset(post, %{})
render(conn, "edit.html", post: post, changeset: changeset)
end
def update(conn, %{"id" => id, "post" => post_params}) do
post = Blog.get_post!(id)
case Blog.update_post(post, post_params) do
{:ok, post} ->
conn
|> put_flash(:info, "Post updated successfully.")
|> redirect(to: Routes.post_path(conn, :show, post))
{:error, changeset} ->
render(conn, "edit.html", post: post, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
post = Blog.get_post!(id)
{:ok, _post} = Blog.delete_post(post)
conn
|> put_flash(:info, "Post deleted successfully.")
|> redirect(to: Routes.post_path(conn, :index))
end
end

Criar Templates

Crie lib/myappweb/templates/post/index.html.heex:

<h1>Posts</h1>
<%= for post <- @posts do %>
<div class="post-item">
<h2><%= link post.title, to: Routes.post_path(@conn, :show, post) %></h2>
<p>By <%= post.user.name %> on <%= post.inserted_at %></p>
<div class="actions">
<%= link "Edit", to: Routes.post_path(@conn, :edit, post) %>
<%= link "Delete", to: Routes.post_path(@conn, :delete, post),
method: :delete, data: [confirm: "Are you sure?"] %>
</div>
</div>
<% end %>
<%= link "New Post", to: Routes.post_path(@conn, :new), class: "btn btn-primary" %>

Autenticação com Sessions

Implemente a autenticação de utilizadores seguindo os padrões do Phoenix.

Criar Schema de Credenciais

mix phx.gen.context Accounts Credential credentials email:string:unique password_hash:string user_id:references:users

Session Controller

Crie lib/myappweb/controllers/session_controller.ex:

defmodule MyAppWeb.SessionController do
use MyAppWeb, :controller
alias MyApp.Accounts
def new(conn, _params) do
render(conn, "new.html")
end
def create(conn, %{"user" => %{"email" => email, "password" => password}}) do
case Accounts.authenticate_by_email_password(email, password) do
{:ok, user} ->
conn
|> put_flash(:info, "Welcome back!")
|> put_session(:user_id, user.id)
|> configure_session(renew: true)
|> redirect(to: "/")
{:error, :unauthorized} ->
conn
|> put_flash(:error, "Invalid email or password")
|> redirect(to: Routes.session_path(conn, :new))
end
end
def delete(conn, _params) do
conn
|> configure_session(drop: true)
|> put_flash(:info, "Signed out successfully")
|> redirect(to: "/")
end
end

Authentication Plug

Adicione ao router para rotas protegidas:

defmodule MyAppWeb.Router do
# ...
pipeline :authenticate do
plug :require_authenticated_user
end
scope "/admin", MyAppWeb.Admin do
pipe_through [:browser, :authenticate]
resources "/posts", PostController
end
defp require_authenticated_user(conn, _opts) do
case get_session(conn, :user_id) do
nil ->
conn
|> put_flash(:error, "Please sign in to access this page")
|> redirect(to: Routes.session_path(conn, :new))
|> halt()
user_id ->
assign(conn, :current_user, Accounts.get_user!(user_id))
end
end
end

Plugs (Middleware)

Plugs são módulos componíveis que transformam a ligação:

defmodule MyAppWeb.Plugs.LoadCurrentUser do
import Plug.Conn
import Phoenix.Controller
alias MyApp.Accounts
def init(opts), do: opts
def call(conn, _opts) do
case get_session(conn, :user_id) do
nil ->
assign(conn, :current_user, nil)
user_id ->
user = Accounts.get_user!(user_id)
assign(conn, :current_user, user)
end
end
end

Executar em Produção com Docker

Crie um Dockerfile:

# Fase de build
FROM elixir:1.15-alpine AS builder
RUN apk add --no-cache build-base git
WORKDIR /app
ENV MIX_ENV=prod
# Instalar hex e rebar
RUN mix local.hex --force && mix local.rebar --force
# Copiar ficheiros de dependências
COPY mix.exs mix.lock ./
RUN mix deps.get --only prod
RUN mix deps.compile
# Copiar ficheiros da aplicação
COPY lib lib
COPY priv priv
COPY assets assets
COPY config config
# Compilar assets
RUN mix assets.deploy
# Criar release
RUN mix release
# Fase de runtime
FROM alpine:3.18 AS runtime
RUN apk add --no-cache libstdc++ openssl ncurses-libs
WORKDIR /app
COPY --from=builder /app/_build/prod/rel/my_app ./
ENV HOME=/app
ENV MIX_ENV=prod
CMD ["bin/my_app", "start"]

Principais Conclusões

  1. Contexts organizam a lógica de negócio: Mantenha a camada web leve, coloque a lógica nos contexts
  2. Pattern matching em todo o lado: Use-o nas cabeças das funções para código mais limpo
  3. Operador pipe para legibilidade: Encadeie transformações de forma natural
  4. Plugs são middleware: Componha pipelines de processamento de pedidos
  5. Árvores de supervisão para fiabilidade: Os processos reiniciam automaticamente em caso de falha
  6. Ecto para a camada de dados: Changesets fornecem validação explícita de dados

Phoenix combina a elegância da programação funcional com padrões práticos de desenvolvimento web, tornando-o excelente para criar aplicações escaláveis e fáceis de manter.

 
 
 
Языки
Темы
Copyright © 1999 — 2026
ZK Interactive