DEV Community

Cover image for 💧🦅Elixir: O Ciclo de Vida do Request no Phoenix
Maiqui Tomé 🇧🇷
Maiqui Tomé 🇧🇷

Posted on • Updated on

💧🦅Elixir: O Ciclo de Vida do Request no Phoenix

O objetivo deste artigo é falar sobre o Ciclo de Vida do Request no framework web para a Linguagem de Programação Elixir chamado Phoenix. Esse assunto é introdutório para começarmos a aprender o framework, onde criaremos duas páginas ao longo deste mini projeto.

Como base para este guia, foram retirados alguns trechos da documentação oficial do Phoenix, de algumas ideias do bootcamp da Rocketseat e do blog netguru.com.

O código final deste projeto você pode encontrar no meu github: https://github.com/maiquitome/hello_phoenix_dev

Criando o Projeto

Primeiramente, vamos criar o projeto rodando o comando abaixo:

$ mix phx.new hello_phoenix --no-ecto
Enter fullscreen mode Exit fullscreen mode

Note que usamos um underline para separar o nome hello_phoenix. Essa separação chamada de snake case é uma convenção da comunidade Elixir, usada bastante também para nomear arquivos. Com o underline, nossa aplicação vai conseguir converter o nome hello_phoenix para HelloPhoenix em nomes de módulos.
image

Note também que usamos a flag --no-ecto, isso diz ao Phoenix que não queremos usar um banco de dados nessa aplicação.

Vamos entrar agora na pasta do projeto com o comando cd hello_phoenix e subir o servidor com o comando mix phx.server. Vamos acessar no navegador o endereço http://localhost:4000/ e você deverá ver a tela de Boas-vindas do Phoenix:
image

Se você não tem ainda o Elixir e o Phoenix instalado, criei um artigo falando sobre isso: Instalação das ferramentas de um desenvolvedor Elixir

Uma Nova Página

Quando seu navegador acessa http://localhost:4000/ na barra de endereço, ele envia uma solicitação HTTP (HTTP request) para qualquer serviço que esteja sendo executado nesse endereço, neste caso, nossa aplicação Phoenix.

A solicitação HTTP é feita de um verbo e um caminho. Por exemplo, as seguintes solicitações do navegador são traduzidas em:

BARRA DE ENDEREÇO DO NAVEGADOR VERBO CAMINHO
http://localhost:4000/ GET /
http://localhost:4000/hello GET /hello
http://localhost:4000/hello/world GET /hello/world

Existem outros verbos HTTP. Por exemplo, o envio de um formulário onde normalmente usamos o verbo POST.

Os aplicativos da Web normalmente lidam com solicitações mapeando cada par de verbo / caminho em uma parte específica de seu aplicativo. Essa correspondência no Phoenix é feita pelo roteador (router). Por exemplo, podemos mapear "/articles" para uma parte de nosso aplicativo que mostra todos os artigos. Portanto, para adicionar uma nova página, nossa primeira tarefa é adicionar uma nova rota.

Uma Nova Rota

O roteador mapeia pares de verbo HTTP / caminho exclusivos para pares de controlador / ação que os tratarão. Os controladores (controllers) em Phoenix são simplesmente módulos Elixir. Ações (actions) são funções definidas nesses controladores.

O Phoenix gera um arquivo de roteador para nós em novos aplicativos no caminho lib/hello_phoenix_web/router.ex. É aqui que vamos encontrar a rota de Boas-Vindas do Phoenix.

O percurso do nosso "Welcome to Phoenix!", quando acessamos antes o endereço http://localhost:4000/ tem esta aparência.

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

Vamos digerir o que essa rota está nos dizendo. Visitando http:// localhost:4000/ emita uma solicitação HTTP GET (HTTP GET request) para o caminho raiz. Todas as solicitações como essa serão tratadas pela função index no módulo HelloPhoenixWeb.PageController definido em lib/hello_phoenix_web/controllers/page_controller.ex.

image

A página que iremos construir dirá simplesmente "Hello World, from Phoenix!" quando apontaremos nosso navegador para o endereço http://localhost:4000/hello.

A primeira coisa que precisamos fazer para criar essa página é definir uma rota para ela. Vamos abrir o arquivo lib/hello_phoenix_web/router.ex em um editor de texto. Para um aplicativo totalmente novo, o arquivo é semelhante:

image

Esse scope "/" simplesmente informa que todos os caminhos que estiverem nesse escopo (nesse bloco de código), serão colocados após o endereço http://localhost:4000. Outro exemplo, se tivéssemos outro bloco de código abaixo com scope "/api", então todos os caminhos que estivessem nesse escopo, seriam colocados após o endereço http://localhost:4000/api.

scope "/", HelloWeb do
  # por enquanto, não se preocupe com essa linha abaixo
  pipe_through :browser

  get "/", PageController, :index

  # aqui adicionamos a nova rota
  # http://localhost:4000/hello
  get "/hello", HelloController, :index
end
Enter fullscreen mode Exit fullscreen mode

Um Novo Controller

Os controladores são módulos Elixir e as ações são funções Elixir definidas neles. O objetivo das ações é reunir todos os dados e realizar as tarefas necessárias para a renderização. Em nossa nova rota precisamos de um módulo HelloPhoenixWeb.HelloController com uma ação index/2.

Para que isso aconteça, vamos criar o arquivo lib/hello_phoenix_web/controllers/hello_controller.ex e torná-lo parecido com o seguinte:

defmodule HelloPhoenixWeb.HelloController do
  # não se importe muito agora com a linha abaixo, apenas
  # saiba que isso define o nosso módulo como um Controller
  use HelloPhoenixWeb, :controller

  def index(conn, _params) do
    text(conn, "Hello World, from Phoenix!")
  end
end
Enter fullscreen mode Exit fullscreen mode

Ao acessarmos agora o endereço http://localhost:4000/hello no navagador, devemos ver a nossa mensagem. (Caso você tenha interrompido o servidor ao longo do caminho, a tarefa de reiniciá-lo é mix phx.server.)
image
Bem, você deve estar se perguntando, o que é esse conn e esse _params? essa função text tá vindo de onde? Pois bem, vamos agora entrar em detalhes para entender tudo isso.

Todas as ações do controlador levam dois argumentos. O primeiro é a conexão conn, sendo uma struct que contém muitos dados sobre a solicitação. O segundo, params, são os parâmetros da solicitação, ou seja, são dados passados junto da rota. Aqui, não estamos usando params ainda, então evitamos os avisos do compilador adicionando o underline _.

Vamos ver a variável conn com mais detalhes. Para isso, vamos modificar o código adicionando IO.inspect():

defmodule HelloPhoenixWeb.HelloController do
  use HelloPhoenixWeb, :controller

  def index(conn, _params) do
    conn
    |> IO.inspect()
    |> text("Hello World, from Phoenix!")
  end
end
Enter fullscreen mode Exit fullscreen mode

Agora, atualizando a página http://localhost:4000/hello, podemos ver no terminal que a variável conn tem o valor %Plug.Conn{}:
image

Se você não tem muita familiaridade com o Pipe Operator |> indico acessar esse guia para ter total entendimento do código: https://elixirschool.com/pt/lessons/basics/pipe-operator/

Antes de entender a variável params, vamos ver de onde está vindo a função text. Ela pertence ao módulo Phoenix.Controller, mas como já temos todas as funcionalidades do controller no nosso módulo, pelo motivo que colocamos a linha use HelloPhoenixWeb, :controller, não precisamos chamar a função com o módulo dessa forma Phoenix.Controller.text().

Mas por curiosidade, se colocarmos dessa maneira Phoenix.Controller.text() e repousarmos a seta do mouse sobre a função, podemos verificar informações de entrada, retorno e exemplos de uso:
Alt Text
Lembrando que essa funcionalidade de passar o mouse em cima faz parte da extensão do vscode ElixirLS. Eu falo sobre essa e outras ferramentas nesse artigo: Instalação das ferramentas de um desenvolvedor Elixir

Uma última coisa antes de partirmos para o entendimento do params é criar uma visualização (view).

image

Para isso acontecer precisamos alterar o nosso código do controller novamente:

defmodule HelloPhoenixWeb.HelloController do
  use HelloPhoenixWeb, :controller

  def index(conn, _params) do
    # aqui trocamos a função `text` para a função `render`
    render(conn, "index.html")
  end
end
Enter fullscreen mode Exit fullscreen mode

Agora ficou mais fácil você adivinhar de onde está vindo a função render, não é mesmo? se você pensou no módulo Phoenix.Controller você acertou!

O núcleo da action index agora é render(conn, "index.html"), que diz ao Phoenix para renderizar o "index.html". Os módulos responsáveis ​​pela renderização são as views. Por padrão, as views do Phoenix têm o nome do controlador, portanto, o Phoenix espera que exista um HelloPhoenixWeb.HelloView e que manipule o "index.html" para nós.

Nota: Usar um átomo como o nome do modelo (template) também funciona render(conn, :index). Nestes casos, o template será escolhido com base nos Accept headers, por exemplo, "index.html" ou "index.json".

Uma Nova View

As views do Phoenix atuam como a camada de apresentação. Por exemplo, esperamos que a saída da renderização de "index.html" seja uma página HTML completa. Para tornar nossas vidas mais fáceis, costumamos usar templates para criar essas páginas HTML.

Vamos criar uma nova view. Crie um arquivo com o diretório lib/hello_phoenix_web/views/hello_view.ex e faça com que tenha a seguinte aparência:

defmodule HelloPhoenixWeb.HelloView do
  use HelloWeb, :view
end
Enter fullscreen mode Exit fullscreen mode

Agora, para adicionarmos templates a esta view, simplesmente precisamos adicionar arquivos ao diretório lib/hello_phoenix_web/templates/hello. Observe que o nome do controlador (HelloController), o nome da view (HelloView) e o diretório do template (hello) seguem a mesma convenção de nomenclatura e são nomeados um após o outro.

Um arquivo de template tem a seguinte estrutura: NAME.FORMAT.TEMPLATING_LANGUAGE. Em nosso caso, criaremos um arquivo "index.html.eex" em
"lib/hello_phoenix_web/templates/hello/index.html.eex". ".eex" significa EEx, que é uma biblioteca para incorporar Elixir que é enviado como parte do próprio Elixir. Phoenix aprimora EEx para incluir escape automático de valores. Isso o protege de vulnerabilidades de segurança, como Cross-Site-Scripting, sem nenhum trabalho extra da nossa parte.

Crie lib/hello_web/templates/hello/index.html.eex e faça com que tenha a seguinte aparência:

<div class="phx-hero">
  <h2>Hello World, from Phoenix!</h2>
</div>
Enter fullscreen mode Exit fullscreen mode

Inspecionando a página conseguimos ver a classe "phx-hero":
image

Agora que temos a rota, o controlador, a view e o template, devemos ser capazes de apontar nossos navegadores para http://localhost:4000/hello e ver nossa saudação do Phoenix! (Caso você tenha interrompido o servidor ao longo do caminho, a tarefa de reiniciá-lo é mix phx.server.)

image

Há algumas coisas interessantes a serem observadas sobre o que acabamos de fazer. Não precisemos parar e reiniciar o servidor enquanto fazíamos essas alterações. Sim, o Phoenix tem hot code reloading! Além disso, embora nosso arquivo index.html.eex consistisse em apenas uma única tag div, a página que recebemos foi um documento HTML completo. Nosso modelo de índice é renderizado no layout do aplicativo - lib/hello_web/templates/layout/app.html.eex. Se você abri-lo, verá uma linha semelhante a esta:

<%= @inner_content %>
Enter fullscreen mode Exit fullscreen mode

que injeta nosso template no layout antes que o HTML seja enviado para o navegador.

image

Agora sim! Com o assunto sobre view já abordado, podemos partir para a construção da segunda página onde usaremos a variável params para entendermos ela melhor.

A Segunda Página

Vamos adicionar uma nova página que irá reconhecer um pedaço da URL, que será o nome de uma pessoa, atribuir esse valor "nome" a chave "person", e passar essa chave e valor para o controlador, capturando para dentro da variável params, e passando para um template, para que a pessoa possa dizer olá.

Lembrando que, para adicionar uma página, precisamos criar uma rota.

A Segunda Rota

Para criar a segunda rota, vamos usar o mesmo HelloController de antes, mas dessa vez com uma action diferente, que será nomeada como show ao invés de index.

scope "/", HelloPhoenixWeb do
    pipe_through :browser

    get "/", PageController, :index
    get "/hello", HelloController, :index

    # aqui adicionamos uma nova rota
    # http://localhost:4000/hello/nome_da_pessoa
    get "/hello/:person", HelloController, :show
end
Enter fullscreen mode Exit fullscreen mode

Observe que usamos a sintaxe :person no caminho. O Phoenix pegará qualquer valor que apareça nessa posição da URL e o converterá em um parâmetro (chave e valor). Por exemplo, se apontarmos o navegador para: http://localhost:4000/hello/Maiqui, o valor da chave "person" será "Maiqui".

A Segunda Action

As solicitações (requests) para nossa nova rota serão tratadas pela action show no HelloPhoenixWeb.HelloController. Já temos o controller em lib/hello_phoenix_web/controllers/hello_controller.ex, então tudo o que precisamos fazer é editar esse arquivo e adicionar uma action show a ele.

Agora como usaremos a variável params, não precisamos mais ter que usar o sinal _ na frente dela. Como no primeiro exemplo da action index, vamos usar a função text para mais rapidamente já podermos enxergar na tela do nosso navegador o conteúdo de params sem precisarmos de um template.

def show(conn, params) do
  text(conn, params)
end
Enter fullscreen mode Exit fullscreen mode

Acessando o endereço http://localhost:4000/hello/Maiqui veremos um erro de tipo. A função text espera um tipo String, mas recebeu um map com a chave "person" e o valor "Maiqui".

image

Se olharmos no terminal, também veremos os parâmetros:
image

Podemos converter o map params para um JSON (JavaScript Object Notation), que também é um formato de chave e valor, usado bastante para troca de informações entre sistemas web. A função Phoenix.Controller.json() consegue converter automaticamente um map para JSON (OBS: para structs existem alguns detalhes para funcionar, mas não vamos nos preocupar com isso agora). Então vamos trocar a funcão text pela função json.

def show(conn, params) do
  json(conn, params)
end
Enter fullscreen mode Exit fullscreen mode

Acessando o endereço http://localhost:4000/hello/Maiqui veremos agora nossos dados no formato JSON:

image

Podemos instalar uma extensão do chrome chamada JSON Viewer. Essa extensão é muito útil quando temos uma grande quantidade de dados para visualizar. Com ela instalada, nossa tela terá essa aparência:

image

Agora que sabemos o conteúdo de params, podemos partir para a construção do segundo template.

O Segundo Template

Vamos agora alterar o código da nossa action show. Adicionaremos a função render e passaremos três argumentos para ela: a conexão conn, o template "show.html", e um par de chave/valor onde :person_name é a chave, e params["person"] é o valor.

def show(conn, params) do
  render(conn, "show.html", person_name: params["person"])
end
Enter fullscreen mode Exit fullscreen mode

Para a última peça deste quebra-cabeça, precisaremos de um novo template. Como é para a action show do HelloController, ele irá para o diretório lib/hello_web/templates/hello e será chamado de show.html.eex. Ele se parecerá com o nosso primeiro template index.html.eex, exceto que precisaremos exibir o nome da pessoa.

Para fazer isso, usaremos as tags especiais EEx para executar expressões Elixir - <%= %>. Observe que a tag inicial tenha um sinal de igual como este: <%=. Isso significa que qualquer código Elixir que estiver entre essas tags será executado e o valor resultante substituirá a tag. Se o sinal de igual estiver faltando, o código ainda será executado, mas o valor não aparecerá na página.

E é assim que o template deve ser:

<div class="phx-hero">
  <h2>Hello World, from <%= @person_name %>!</h2>
</div>
Enter fullscreen mode Exit fullscreen mode

O nome da pessoa aparece como @person_name. Chamamos de "atribuições" ("assigns") os valores passados ​​do controller para as views. É um bit especial de sintaxe metaprogramada que representa assigns.person_name. Se você colocar assigns.person_name no lugar de @person_name também vai funcionar, mas @person_name é muito mais agradável para os olhos e muito mais fácil de trabalhar em um template.

Agora se você apontar seu navegador para http://localhost:4000/hello/Maiqui, deverá ver uma página semelhante a esta:

image

Neste momento, podemos partir para uma pequena refatoração na action show usando o nosso querido Pattern Matching do Elixir. Lembra que temos como paramêtro um map %{"person" => "Maiqui"}? Então, podemos fazer a atribuição da variável name na definição da função. Isso lembra um pouco a desestruturação na Linguagem de Programação JavaScript.

def show(conn, %{"person" => name}) do
  render(conn, "show.html", person_name: name)
end
Enter fullscreen mode Exit fullscreen mode

Sobre o Endpoint

Todas as solicitações HTTP (HTTP requests) começam em nosso endpoint do aplicativo. Você pode encontrá-lo como um módulo denominado HelloPhoenixWeb.Endpoint em lib/hello_phoenix_web/endpoint.ex. Depois de abrir o arquivo do endpoint, você verá que, semelhante ao roteador, o endpoint tem muitas chamadas para plug. Plug é uma biblioteca e especificação para unir aplicativos da web. É uma parte essencial de como o Phoenix lida com requests.

Por enquanto, basta dizer que cada Plug define uma parte do processamento da request. No endpoint, você encontrará um esqueleto mais ou menos assim:

defmodule HelloPhoenixWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :demo

  plug Plug.Static, ...
  plug Plug.RequestId
  plug Plug.Telemetry, ...
  plug Plug.Parsers, ...
  plug Plug.MethodOverride
  plug Plug.Head
  plug Plug.Session, ...
  plug HelloPhoenixWeb.Router
end
Enter fullscreen mode Exit fullscreen mode

Podemos ver que o último plug é o módulo HelloPhoenixWeb.Router. Então, a request passa por todos esses plugs antes de chegar no módulo HelloPhoenixWeb.Router. O endpoint contém o caminho comum e inicial pelo qual todas as requests passam. Se você quiser que algo aconteça em todas as requests, vá para o endpoint.

Um Resumão do Ciclo de Vida do Request

  1. Um usuário insere uma URL. O navegador cria uma solicitação (request) e a envia pelo protocolo HTTP/HTTPS.

  2. A estrutura de conexão denominada conn é criada pelo adaptador Plug Cowboy do servidor HTTP baseado em Erlang chamado Cowboy. Essa conexão será manipulada e transformada nas próximas etapas para gerar uma resposta.

  3. A request chega ao Endpoint, onde vários plugs executam algum tipo de processamento. Em muitos aplicativos da web, provavelmente escreveríamos um plug para verificar se o usuário está autenticado e - com base nisso - mostraríamos ao usuário um recurso ou redirecionaríamos para a página de login.

  4. O roteador (router) encaminha um par de verbo/caminho da request para um controlador (controller) onde será executada uma ação (action). Além disso, aqui definimos plugs e pipelines específicos (que são conjuntos de plugs) pelos quais as requests devem passar.

  5. No controller são recuperadas as informações da request, dados são buscados no banco de dados, serviços são chamados para executar a lógica de negócio, renderizar um modelo (template) específico ou renderizar um JSON. Resumindo, dados são preparados para a camada de apresentação.

  6. A view lida com os dados estruturados do controller e os converte em uma apresentação a ser mostrada aos usuários.

  7. No template é gerado o HTML que deve ser adicionado como corpo à resposta (response).

  8. Quando a view renderiza o template, ela chama a função send_resp para retornar uma resposta HTTP (HTTP response) ao cliente.

  9. Finalmente, o usuário está vendo dos dados retornados pelo nosso aplicativo web!

Conclusão

Busquei trazer aqui meus primeiros passos no aprendizado do Phoenix. Tenho aprendido bastante escrevendo ele e espero que possa ter te ajudado de alguma forma. Existem vários assuntos que vimos para serem analisados com mais profundidade. Conforme irei progredindo estarei escrevendo mais artigos. É notório que é ensinando que se aprende. Se algo que você leu lhe pareceu confuso e você tiver uma definição melhor do assunto, ou tiver alguma dúvida e eu puder ajudar, por favor compartilhe comigo, estou sempre no Linkedin ou deixe sua mensagem nos comentários :)

Oldest comments (0)