Alessio Biancalana Grab The Blaster di Alessio Biancalana

Elixir per idioti /8, costruiamo una API REST super semplice

È un sacco di tempo che non parliamo di Elixir, e il nostro percorso era rimasto un po’ appeso con la parte 7, dove avevamo creato un’applicazioncina di prova con Phoenix Framework. Se vogliamo fare cose più complicate come modellare un microservizio tutto nostro attorno a un dominio ben preciso però qualcosa come quello che abbiamo visto è limitante, perché visualizzare delle paginette web limita un pochino l’interoperabilità che possiamo avere con altri servizi.

E se al posto di pagine web volessimo servire dei JSON in puro stile API REST-ful1?

Elixir e Phoenix coprono anche questo caso d’uso in maniera eccellente. In realtà potremmo fare il tutto anche senza Phoenix solo usando il webserver che ne è alla base, cioè Cowboy, ma Phoenix offre una serie di semplificazioni “on top” che ci rendono leggermente più semplice la vita, accorciano in maniera notevole questo tutorial, e risultano anche comode per evoluzioni future della nostra piccola API, come il supporto out-of-the-box a JWT2.

Bando alle ciance, andiamo a creare il nostro nuovo progettino:

$ mix new tiny_echo_api --no-webpack --no-ecto

Usando lo scaffolder di Phoenix, che è fatto molto bene, andiamo a dichiarare che stiamo creando un nuovo progetto senza bisogno di frontend (--no-webpack) e senza strato di persistenza su database (--no-ecto)3.

Spostiamoci nella directory che è stata appena creata, installiamo le dipendenze del nostro progetto e facciamolo partire:

$ cd tiny_api
$ mix deps.get
$ mix phx.server

localhost:4000 ci mostrerà la solita paginetta di benvenuto, non diamole troppa attenzione, e andiamo immediatamente a implementare un nuovo endpoint che prende in input una stringa, e ce la restituisce dentro un JSON. Non un grande servizio in sé, ma è un ottimo modo per fare tutto il giro, vedere cosa ci mette a dispozione Phoenix e eventualmente cominciare a costruirci sopra qualcosa.

Apriamo il router, che abbiamo già visto nell’episodio precedente:

# lib/tiny_api_web/router.ex

defmodule TinyApiWeb.Router do
  use TinyApiWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

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

  scope "/", TinyApiWeb do
    pipe_through :browser

    get "/", PageController, :index
  end

  # Other scopes may use custom stacks.
  # scope "/api", TinyApiWeb do
  #   pipe_through :api
  # end

  # Enables LiveDashboard only for development
  #
  # If you want to use the LiveDashboard in production, you should put
  # it behind authentication and allow only admins to access it.
  # If your application does not have an admins-only section yet,
  # you can use Plug.BasicAuth to set up some basic authentication
  # as long as you are also using SSL (which you should anyway).
  if Mix.env() in [:dev, :test] do
    import Phoenix.LiveDashboard.Router

    scope "/" do
      pipe_through :browser
      live_dashboard "/dashboard", metrics: TinyApiWeb.Telemetry
    end
  end
end

E andiamo a modificare le rotte dichiarate in questo modo:

scope "/", TinyApiWeb do
  pipe_through :browser

  get "/", PageController, :index
  get "/echo/:payload", EchoController, :echo
end

In questo modo andiamo a dichiarare una nuova rotta disponibile solo in GET che ha la forma /echo/qualcosa, e andremo poi a fare in modo che quando noi chiamiamo la nostra applicazione sulla rotta /echo/qualcosa quella ci risponda a sua volta qualcosa dentro un JSON di risposta.

Nel router abbiamo dichiarato che andremo a pescare la funzione echo da un controller chiamato EchoController; Phoenix si aspetterà il modulo corrispondente, quindi andiamolo a creare, mettendoci dentro anche l’implementazione della funzione echo:

# lib/tiny_api_web/controllers/echo_controller.ex

defmodule TinyApiWeb.EchoController do
  @moduledoc """
  This module handles echo requests and replies back
  with an echo JSON containing the word used by the request.
  """

  use TinyApiWeb, :controller

  def echo(conn, params) do
    response = %{"reply" => params["payload"]}
    json(conn, response)
  end
end

Eccoci qua: come vediamo abbiamo definito il modulo TinyApiWeb.EchoController, ci abbiamo messo addirittura una bellissima @moduledoc per aiutare chi si imbatte in questo modulo a capire che cosa fa (Elixir dispone di utility che generano automaticamente la documentazione corrispondente da queste annotazioni), gli abbiamo detto di comportarsi come un controller Phoenix attraverso l’uso di use, e abbiamo implementato la funzione echo, che va poi a rispondere usando la funzione json messaci a disposizione da Phoenix per poter rispondere direttamente usando la mappa che abbiamo dichiarato.

Facciamo ripartire il servizio con mix phx.server e andiamo a vedere su http://localhost:4000/echo/this-is-an-answer che cosa abbiamo combinato:

$ curl localhost:4000/echo/this-is-an-answer
{"reply":"this-is-an-answer"}

Bonus trick: usare la destrutturazione

Volendo sfruttare la potenza della sintassi di Elixir, possiamo implementare la funzione echo in un altro modo, andando a usare la destrutturazione per tirare fuori il campo payload dai parametri della chiamata poi poi andare a costruire la mappa con la risposta direttamente inline:

def echo(conn, params) do
  %{"payload" => payload} = params
  json(conn, %{"reply" => payload})
end

Oppure, volendo essere più concisi, andando a sfruttare tutto quello che la sintassi di Elixir ci mette a disposizione, così da scrivere tutto su una sola riga:

def echo(conn, %{"payload" => payload}), do: json(conn, %{"reply" => payload})

A voi giudicare se in questo modo si perde di leggibilità. Quello che andiamo a fare in questo modo è utilizzare il pattern matching insieme alla destrutturazione direttamente nella firma della funzione. Le tre implementazioni che abbiamo visto sono assolutamente equivalenti.

Volete implementare un nuovo endpoint nel vostro servizio? Adesso avete visto come funziona tutto il giro. Potete sbizzarrirvi: fatelo! :-)

  1. In realtà questo termine è abusatissimo, dato che REST è un paradigma che presuppone che le risorse vengano richiamate in un certo modo. Per ora facciamo solo finta che un’API REST ben scritta sia una API che segue la semantica HTTP (GET/POST/eccetera) e restituisce una risposta in JSON. Magari in futuro vediamo insieme come deve essere fatta una API REST-ful “a modino”, mh? ;-) 

  2. Anche qui, su JSON Web Token ci si potrebbe scrivere una bibbia: per brevità questo lo demandiamo ad un altro post perché non è lo scopo di questa trattazione, tantomeno per una persona alle prime armi con un nuovo stack. 

  3. Mentre Webpack è il tool che si occupa di mettere insieme tutto il JavaScript che scriviamo nelle nostre applicazioni, e che Phoenix include in modo automatico, Ecto è la libreria che viene utilizzata per comunicare con il database della nostra applicazione, che di default è PostgreSQL. Ecto è uno strumento ultrapotente, lo vedremo nel dettaglio in un episodio in futuro! 

comments powered by Disqus