A API RESTful JSON mais simples do Elixir

Como implementar o ponto de extremidade da API JSON no Elixir sem nenhuma estrutura?


Do tradutor:
O artigo fornece um exemplo de um aplicativo Web muito simples que pode ser considerado como Olá, Mundo! na criação da API mais simples do Elixir.
O código de amostra é ligeiramente modificado para corresponder às versões atuais das bibliotecas.
O código de exemplo completo com as alterações pode ser visto no GitHub .



Desafios de novos idiomas


Muitos desenvolvedores vêm para o Elixir do mundo do Ruby. Este é um ambiente muito maduro em termos do número de bibliotecas e estruturas disponíveis. E essa maturidade às vezes não é suficiente para mim no Elixir. Quando preciso de um serviço de terceiros, o resultado de uma pesquisa adequada pode ser o seguinte:


  • existe uma biblioteca oficial bem suportada (muito rara);
  • existe uma biblioteca oficial, mas desatualizada ou com bugs (às vezes acontece);
  • Existe uma biblioteca bem suportada, desenvolvida por alguém da comunidade (às vezes de tempos em tempos);
  • existe uma biblioteca desenvolvida por alguém da comunidade, mas não é mais suportada (um caso muito comum);
  • existem várias bibliotecas, cada uma delas escrita por alguém para suas próprias necessidades, e não possui os recursos necessários (a opção mais popular);
  • existe minha própria biblioteca, combinando o melhor das opções anteriores ... (encontrada com muita frequência).

API JSON simples no Elixir



Você pode se surpreender, mas Ruby nem sempre está nos trilhos ( Ruby on Rails, lembra? - Nota do tradutor ). Nem sempre é necessária a comunicação com a web. Embora neste caso em particular, vamos falar sobre a web.


Quando se trata de implementar um único terminal RESTful, geralmente existem muitas opções:



Estes são exemplos de ferramentas que eu pessoalmente usei. Meus colegas estão satisfeitos com os usuários do Sinatra. Eles conseguiram experimentar Hanami. Posso escolher qualquer opção que me convenha, mesmo dependendo do meu humor atual.


Mas quando mudei para o Elixir, a escolha era limitada. Embora existam várias "estruturas" alternativas (cujos nomes, por razões óbvias, não mencionarei aqui), é quase impossível usá-las!


Passei o dia inteiro organizando todas as bibliotecas já mencionadas na Internet. Atuando como um robô do Slack, tentei implantar um servidor HTTP2 simples no Heroku , mas me rendi no final do dia. Literalmente, nenhuma das opções que encontrei foi capaz de implementar os requisitos básicos.


Nem sempre uma solução - Phoenix


Phoenix é minha estrutura da web favorita, mas às vezes é redundante. Eu não queria usá-lo, inserindo toda a estrutura no projeto apenas em prol de um ponto de extremidade; e não importa que seja muito simples.


Também não pude usar bibliotecas prontas, porque, como eu já disse, todas as bibliotecas que encontrei não eram adequadas para minhas necessidades (eram necessárias roteamento básico e suporte a JSON) ou não eram convenientes o suficiente para uma implantação fácil e rápida no Heroku. Dê um passo para trás, pensei.



Mas, na verdade, a própria Phoenix é construída com base em algo , não é?


Plug & Cowboy vêm ao resgate


Se você precisar criar um servidor verdadeiramente minimalista no Ruby, poderá simplesmente usar o rack - uma interface modular para servidores Web Ruby.


Felizmente, algo semelhante está disponível no Elixir. Nesse caso, usaremos os seguintes elementos:


  • cowboy - um servidor HTTP pequeno e rápido para Erlang / OTP, que implementa a pilha e o roteamento HTTP completos, otimizados para minimizar a latência e o uso de memória;
  • plug - um conjunto de adaptadores para vários servidores da web em execução na Erlang VM; cada adaptador fornece uma interface direta para o servidor da web localizado atrás dele;
  • poison é uma biblioteca para processar JSON no Elixir.

Implementação


Desejo implementar componentes como ponto final (ponto final), roteador (roteador) e analisador JSON (manipulador JSON). Gostaria de implantar o resultado no Heroku e poder processar solicitações de entrada. Vamos ver como isso pode ser alcançado.


App


Verifique se o seu projeto Elixir contém um supervisor. Para fazer isso, crie um projeto como este:


 mix new minimal_server --sup 

Verifique se mix.exs contém:


 def application do [ extra_applications: [:logger], mod: {MinimalServer.Application, []} ] end 

e crie o arquivo lib/minimal_server/application.ex :


 defmodule MinimalServer.Application do use Application def start(_type, _args), do: Supervisor.start_link(children(), opts()) defp children do [] end defp opts do [ strategy: :one_for_one, name: MinimalServer.Supervisor ] end end 

Bibliotecas


As seguintes bibliotecas devem ser especificadas em mix.exs :


 defp deps do [ {:poison, "~> 4.0"}, {:plug, "~> 1.7"}, {:cowboy, "~> 2.5"}, {:plug_cowboy, "~> 2.0"} ] end 

Em seguida, baixe e compile as dependências:


 mix do deps.get, deps.compile, compile 

Ponto final


Agora tudo está pronto para criar um ponto de entrada para o servidor. Vamos criar um arquivo lib/minimal_server/endpoint.ex com o seguinte conteúdo:


 defmodule MinimalServer.Endpoint do use Plug.Router plug(:match) plug(Plug.Parsers, parsers: [:json], pass: ["application/json"], json_decoder: Poison ) plug(:dispatch) match _ do send_resp(conn, 404, "Requested page not found!") end end 

O módulo Plug contém o Plug.Router para redirecionar solicitações recebidas, dependendo do caminho usado e do método HTTP. Após o recebimento da solicitação, o roteador chamará o module :match , representado pela função match/2 , responsável por encontrar a rota correspondente, e depois a redirecionará para o module :dispatch , que executará o código correspondente.


Como queremos que nossa API seja compatível com JSON, precisamos implementar o Plug.Parsers . Como ele processa application/json com o dado :json_decoder , vamos usá-lo para analisar o corpo da solicitação.


Como resultado, criamos uma rota temporária "qualquer solicitação" que corresponde a todas as solicitações e responde com o código HTTP não encontrado (404).


Roteador


A implementação de um roteador será o último passo na criação de nosso aplicativo. Este é o último elemento de todo o pipeline que criamos: começando com o recebimento de uma solicitação de um navegador da Web e terminando com a formação de uma resposta.


O roteador processará a solicitação recebida do cliente e enviará de volta alguma mensagem no formato desejado ( adicione o código acima ao arquivo lib/minimal_server/router.ex - nota do tradutor ):


 defmodule MinimalServer.Router do use Plug.Router plug(:match) plug(:dispatch) get "/" do conn |> put_resp_content_type("application/json") |> send_resp(200, Poison.encode!(message())) end defp message do %{ response_type: "in_channel", text: "Hello from BOT :)" } end end 

No módulo Router acima, a solicitação será processada apenas se for enviada pelo método GET e enviada ao longo da rota / . O módulo Router responderá com um cabeçalho Content-Type contendo application/json e body:


 { "response_type": "in_channel", "text": "Hello from BOT :)" } 

Juntando tudo


Agora é a hora de alterar o módulo Endpoint para encaminhar solicitações ao roteador e modificar o Application para iniciar o próprio módulo Endpoint .


O primeiro pode ser feito adicionando MinimalServer.Endpoint [ antes da match _ do ... end rule - aprox. tradutor ] string


 forward("/bot", to: MinimalServer.Router) 

Isso garante que todos os pedidos para /bot sejam roteados e processados ​​pelo módulo Router .


O segundo pode ser implementado adicionando as funções child_spec/1 e start_link/1 arquivo endpoint.ex :


 defmodule MinimalServer.Endpoint do # ... def child_spec(opts) do %{ id: __MODULE__, start: {__MODULE__, :start_link, [opts]} } end def start_link(_opts), do: Plug.Cowboy.http(__MODULE__, []) end 

Agora você pode modificar application.ex adicionando MinimalServer.Endpoint à lista retornada pela função children/0 .


 defmodule MinimalServer.Application do # ... defp children do [ MinimalServer.Endpoint ] end end 

Para iniciar o servidor, basta:


 mix run --no-halt 

Finalmente, você pode visitar o endereço http: // localhost: 4000 / bot e ver nossa mensagem :)


Implantação



Config


Na maioria das vezes, em um ambiente local e para operação, o servidor é configurado de maneira diferente. Portanto, precisamos inserir configurações separadas para cada um desses modos. Primeiro, config.exs nossos config.exs adicionando:


 config :minimal_server, MinimalServer.Endpoint, port: 4000 

Nesse caso, quando o aplicativo iniciar nos modos de test , prod e dev , ele receberá a porta 4000 se essas configurações não forem alteradas.


Do tradutor

Neste ponto, o autor do texto original esqueceu de mencionar como modificar config.exs para que você possa usar opções diferentes para modos diferentes. Para fazer isso, adicione import_config "#{Mix.env()}.exs" ; na última linha em config/config.exs ; o resultado é algo como:


 use Mix.Config config :minimal_server, MinimalServer.Endpoint, port: 4000 import_config "#{Mix.env()}.exs" 

Depois disso, crie os arquivos prod.exs , test.exs , dev.exs no diretório de config , colocando-o em cada linha:


 use Mix.Config 

Na produção, geralmente não queremos definir o número da porta, mas dependemos de alguma variável de ambiente do sistema, por exemplo:


 config :minimal_server, MinimalServer.Endpoint, port: "PORT" |> System.get_env() |> String.to_integer() 

Adicione o texto acima ao final de config/prod.exs - aprox. tradutor


Depois disso, um valor fixo será usado localmente e, na operação operacional, uma configuração de variáveis ​​de ambiente.


Vamos implementar esse esquema no endpoint.ex ( substituindo a função start_link / 1 - comentário do tradutor ):


 defmodule MinimalServer.Endpoint do # ... require Logger def start_link(_opts) do with {:ok, [port: port] = config} <- Application.fetch_env(:minimal_server, __MODULE__) do Logger.info("Starting server at http://localhost:#{port}/") Plug.Adapters.Cowboy2.http(__MODULE__, [], config) end end end 

Heroku


O Heroku oferece a implantação mais simples com um clique, sem nenhuma configuração complicada. Para implantar nosso projeto, você precisa preparar alguns arquivos simples e criar um aplicativo remoto .



Depois de instalar o Heroku CLI, você pode criar um novo aplicativo da seguinte maneira:


 $ heroku create minimal-server-habr Creating ⬢ minimal-server-habr... done https://minimal-server-habr.herokuapp.com/ | https://git.heroku.com/minimal-server-habr.git 

Agora adicione o Elixir Build Kit ao seu aplicativo:


 heroku buildpacks:set \ https://github.com/HashNuke/heroku-buildpack-elixir.git 

No momento desta tradução, as versões atuais do Elixir e Erlang são (mais ou menos):


 erlang_version=21.1 elixir_version=1.8.1 

Para configurar o próprio kit de construção, inclua as linhas acima no arquivo elixir_buildpack.config .


A última etapa é criar um Procfile e, novamente, é muito simples:


 web: mix run --no-halt 

Nota do tradutor: para evitar um erro durante a construção no Heroku, você deve definir o valor das variáveis ​​de ambiente que são usadas no aplicativo:


 $ heroku config:set PORT=4000 Setting PORT and restarting ⬢ minimal-server-habr... done, v5 PORT: 4000 

Assim que você confirmar novos arquivos [ usando git - aprox. tradutor ], você pode enviá-los para o Heroku:


 $ git push heroku master Initializing repository, done. updating 'refs/heads/master' ... 

E isso é tudo! O aplicativo está disponível em https://minimal-server-habr.herokuapp.com .


Sumário


Neste ponto, você já entendeu como implementar a API JSON RESTful e o servidor HTTP mais simples no Elixir sem usar nenhuma estrutura, usando apenas 3 bibliotecas ( aproximadamente 4 - tradutor ).


Quando você precisa fornecer acesso a pontos de extremidade simples, absolutamente não precisa usar o Phoenix todas as vezes, não importa quão legal seja, assim como qualquer outra estrutura.


Curioso por que não há estruturas confiáveis, bem testadas e suportadas em algum lugar entre plug + cowboy e Phoenix? Talvez não haja necessidade real de implementar coisas simples? Talvez cada empresa use sua própria biblioteca? Ou talvez todo mundo use Phoenix ou a abordagem apresentada?



O repositório , como sempre, está disponível no meu GitHub.

Source: https://habr.com/ru/post/pt444554/


All Articles