DEV Community

Cover image for Um Primeiro Olhar sobre o Framework Phoenix
Rafael Andrade
Rafael Andrade

Posted on

Um Primeiro Olhar sobre o Framework Phoenix

Como desenvolvedor .NET embarcando em uma jornada para aprender Elixir, uma das primeiras perguntas é: "Como construo uma API web?" No ecossistema Elixir, a resposta é predominantemente o Framework Phoenix. Se você está familiarizado com ASP.NET Core, verá que o Phoenix é uma contraparte poderosa e elegante.

Neste artigo, vou detalhar os componentes-chave do Phoenix e fazer comparações com o mundo .NET para ajudar a estruturar meu entendimento.

Visão Geral

Phoenix é o framework de desenvolvimento web para a linguagem Elixir. Ele permite que você construa aplicações web modernas, incluindo APIs Web, Web Sockets (para funcionalidade em tempo real) e aplicativos tradicionais Model View Controller (MVC).

No mundo Microsoft, ele é o equivalente direto do ASP.NET Core. Phoenix é conhecido por alta produtividade do desenvolvedor e excepcional performance de aplicação.

Plug: A Abstração HTTP

Assim como o ASP.NET Core é construído em cima do conceito de middleware, o Phoenix é construído sobre o Plug.

Plug é tanto uma especificação para módulos compostos entre aplicações web quanto uma camada de abstração para servidores web (como Cowboy ou Bandit). O conceito central é uma conexão unificada (a struct %Plug.Conn{}, similar ao HttpContext no .NET) que é transformada conforme passa por uma série de funções.

Isso é mais granular que o middleware do ASP.NET Core. Um único Plug é uma função que recebe uma conexão e retorna uma conexão, tornando-os altamente compostáveis.

Pipelines: Middleware Organizado

No Phoenix, você agrupa Plugs em "pipelines" dentro do seu roteador. Esta é uma forma mais limpa e declarativa de organizar middleware comparado às chamadas app.UseMiddleware<T>() no arquivo Program.cs.

No Phoenix, criamos explicitamente um pipeline. Você adiciona seu middleware (chamado "plugs")—como autenticação ou validação—explicitamente antes que a requisição chegue ao seu controller.

Exemplo Phoenix:

defmodule HelloWeb.Router do
  use HelloWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {HelloWeb.LayoutView, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug HelloWeb.Plugs.Locale, "en"
  end

  scope "/", HelloWeb do
    pipe_through :browser

    get "/", PageController, :index
  end
Enter fullscreen mode Exit fullscreen mode

Na amostra acima, criamos um pipeline nomeado :browser. Antes que uma requisição possa chegar ao PageController, ela precisa passar por este pipeline. Isso parece muito mais claro que no .NET porque você pode ver, em um único lugar, exatamente quais passos uma requisição percorre antes de chegar ao controller.

Simplificando Controllers com Plugs

Plugs também podem ser usados para limpar a lógica do seu controller. Veja este controller "bagunçado":

defmodule HelloWeb.MessageController do
  use HelloWeb, :controller

  def show(conn, params) do
    case Authenticator.find_user(conn) do
      {:ok, user} ->
        case find_message(params["id"]) do
          nil ->
            conn |> put_flash(:info, "Aquela mensagem não foi encontrada") |> redirect(to: ~p"/")
          message ->
            if Authorizer.can_access?(user, message) do
              render(conn, :show, page: message)
            else
              conn |> put_flash(:info, "Você não pode acessar essa página") |> redirect(to: ~p"/")
            end
        end
      :error ->
        conn |> put_flash(:info, "Você precisa estar logado") |> redirect(to: ~p"/")
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

O aninhamento dificulta o acompanhamento do fluxo da aplicação. Podemos usar Plug para refatorar isso em um pipeline funcional:

defmodule HelloWeb.MessageController do
  use HelloWeb, :controller

  plug :authenticate
  plug :fetch_message
  plug :authorize_message

  def show(conn, params) do
    render(conn, :show, page: conn.assigns[:message])
  end

  defp authenticate(conn, _) do
    case Authenticator.find_user(conn) do
      {:ok, user} ->
        assign(conn, :user, user)
      :error ->
        conn |> put_flash(:info, "Você precisa estar logado") |> redirect(to: ~p"/") |> halt()
    end
  end

  defp fetch_message(conn, _) do
    case find_message(conn.params["id"]) do
      nil ->
        conn |> put_flash(:info, "Aquela mensagem não foi encontrada") |> redirect(to: ~p"/") |> halt()
      message ->
        assign(conn, :message, message)
    end
  end

  defp authorize_message(conn, _) do
    if Authorizer.can_access?(conn.assigns[:user], conn.assigns[:message]) do
      conn
    else
      conn |> put_flash(:info, "Você não pode acessar essa página") |> redirect(to: ~p"/") |> halt()
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Isso torna nosso método show drasticamente mais claro.

Rotas

O roteamento no Phoenix parece muito similar às "Minimal APIs" no .NET. Você tem um único arquivo definindo todas as rotas, apontando-as para o controller e pipeline específicos que devem ser usados.

defmodule HelloWeb.Router do
  use HelloWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {HelloWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", HelloWeb do
    pipe_through :browser

    get "/", PageController, :home
  end

  # Outros escopos podem usar pilhas personalizadas.
  # scope "/api", HelloWeb do
  #   pipe_through :api
  # end

  # ...
end
Enter fullscreen mode Exit fullscreen mode

Outro recurso excelente do Phoenix é a capacidade de listar todas as rotas da aplicação via CLI:

mix phx.routes
Enter fullscreen mode Exit fullscreen mode

Saída:

GET  /  HelloWeb.PageController :home
Enter fullscreen mode Exit fullscreen mode

Controllers

Criar um controller no Phoenix é simples. Você usa o módulo base para trazer a funcionalidade necessária e define actions como funções.

defmodule HelloWeb.PageController do
  use HelloWeb, :controller

  def home(conn, _params) do
    render(conn, :home)
  end
end
Enter fullscreen mode Exit fullscreen mode

Cada action recebe uma conexão (conn) e parâmetros, e retorna uma conexão (potencialmente modificada). Essa abordagem funcional—transformando a conexão através de uma série de passos—é um padrão fundamental.

Minha Opinião: Ecossistemas .NET vs. Elixir

Vindo do mundo .NET, encontro-me constantemente comparando os dois.

É refrescante que Elixir tenha um framework web forte e dominante. Em outros ecossistemas, você pode precisar aprender 2 ou 3 frameworks diferentes antes de decidir qual usar. Em Elixir, toda a comunidade se une em torno do Phoenix.

Phoenix parece brilhar mais quando falamos de recursos em tempo real (como LiveView). No .NET, temos SignalR e Blazor Server, mas não vejo tantos projetos adotando-os para o stack completo comparado à adoção do Phoenix no Elixir.

O Fator do Ecossistema: .NET tem uma empresa gigantesca por trás. Isso é tanto bom quanto ruim.

  • O Bom: Eles têm imensos recursos para investir no ecossistema.
  • O Ruim: Pode sufocar a inovação de código aberto. Por exemplo, ASP.NET Core é mantido pela Microsoft, então é muito difícil para um framework web impulsionado pela comunidade competir. Similarmente, Entity Framework Core efetivamente substituiu o NHibernate, e a Injeção de Dependência nativa da Microsoft reduziu a necessidade de bibliotecas como Autofac ou Ninject.

No mundo Elixir, não há um único gigante da tecnologia controlando a direção. As equipes do Elixir e Phoenix (que são relativamente pequenas) estão fazendo um trabalho incrível, e seu trabalho apoia a iniciativa de código aberto em vez de dominá-la.

Referências

https://www.phoenixframework.org/

https://hexdocs.pm/phoenix/overview.html

https://en.wikipedia.org/wiki/Phoenix_(web_framework)
https://elixirschool.com/en/lessons/misc/plug

Top comments (0)