Incontra Phoenix: un framework simile a Rails per app Web moderne su Elixir
Pubblicato: 2022-03-11Il framework Phoenix è cresciuto con popolarità a un ritmo rapido, offrendo la produttività di framework come Ruby on Rails, pur essendo uno dei framework più veloci disponibili. Rompe il mito secondo cui devi sacrificare le prestazioni per aumentare la produttività.
Allora, cos'è esattamente Phoenix?
Phoenix è un framework web costruito con il linguaggio di programmazione Elixir. Elixir, basato su Erlang VM, viene utilizzato per la creazione di sistemi distribuiti a bassa latenza, fault-tolerant, qualità sempre più necessarie delle moderne applicazioni web. Puoi saperne di più su Elixir da questo post del blog o dalla loro guida ufficiale.
Se sei uno sviluppatore di Ruby on Rails, dovresti assolutamente interessarti a Phoenix a causa dei guadagni in termini di prestazioni che promette. Anche gli sviluppatori di altri framework possono seguire per vedere come Phoenix si avvicina allo sviluppo web.
In questo articolo impareremo alcune delle cose in Phoenix che dovresti tenere a mente se vieni dal mondo di Ruby on Rails.
A differenza di Ruby, Elixir è un linguaggio di programmazione funzionale, che è probabilmente la più grande differenza che potresti dover affrontare. Tuttavia, ci sono alcune differenze chiave tra queste due piattaforme di cui chiunque stia imparando Phoenix dovrebbe essere consapevole per massimizzare la propria produttività.
Le convenzioni di denominazione sono più semplici.
Questo è piccolo, ma è facile sbagliare se vieni da Ruby on Rails.
A Phoenix, la convenzione è di scrivere tutto al singolare. Quindi avresti un "UserController" invece di un "UsersController" come faresti in Ruby on Rails. Questo si applica ovunque tranne quando si denomina le tabelle del database, dove la convenzione è nominare la tabella nella forma plurale.
Questo potrebbe non sembrare un grosso problema dopo aver appreso quando e dove usare la forma plurale in Ruby on Rails, ma all'inizio era un po' confuso, quando stavo imparando a usare Ruby on Rails, e sono sicuro che ha confuso molti anche altri. Phoenix ti dà una cosa in meno di cui preoccuparti.
Il percorso è più facile da gestire.
Phoenix e Ruby on Rails sono molto simili quando si tratta di routing. La differenza fondamentale sta nel modo in cui puoi controllare il modo in cui viene elaborata una richiesta.
In Ruby on Rails (e altre applicazioni Rack) ciò avviene tramite middleware, mentre in Phoenix ciò viene fatto con quelli che vengono chiamati "plug".
Le spine sono ciò che usi per elaborare una connessione.
Ad esempio, il middleware Rails::Rack::Logger
registra una richiesta in Rails, che in Phoenix verrebbe eseguita con il plug Plug.Logger
. Fondamentalmente tutto ciò che puoi fare nel middleware Rack può essere fatto usando i plug.
Ecco un esempio di router a Phoenix:
defmodule HelloWorld.Router do use HelloWorld.Web, :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 "/", HelloWorld do pipe_through :browser get "/", HelloController, :index end scope "/api", HelloWorld do pipe_through :api end end
Per prima cosa, diamo un'occhiata alle condutture.
Si tratta di gruppi di prese attraverso le quali viaggerà la richiesta. Pensalo come uno stack di middleware. Questi possono essere utilizzati, ad esempio, per verificare che la richiesta richieda HTML, recuperare la sessione e assicurarsi che la richiesta sia sicura. Tutto questo accade prima di raggiungere il controller.
Poiché puoi specificare più pipeline, puoi selezionare e scegliere quali tappi sono necessari per percorsi specifici. Nel nostro router, ad esempio, abbiamo una pipeline diversa per le richieste di pagine (HTML) e API (JSON).
Non ha sempre senso usare la stessa identica pipeline per diversi tipi di richieste. Phoenix ci offre quella flessibilità.
Viste e modelli sono due cose diverse.
Le visualizzazioni in Phoenix non sono le stesse di Ruby on Rails.
Le viste in Phoenix sono responsabili del rendering dei modelli e della fornitura di funzioni che semplificano l'utilizzo dei dati grezzi da parte dei modelli. Una vista in Phoenix ricorda molto da vicino un helper in Ruby on Rails, con l'aggiunta del rendering del modello.
Scriviamo un esempio che mostra "Hello, World!" nel browser.
Rubino su rotaie:
app/controller/hello_controller.rb:
class HelloController < ApplicationController def index end end
app/views/hello/index.html.erb:
<h1>Hello, World!</h1>
Fenice:
web/controller/hello_controller.ex:
defmodule HelloWorld.HelloController do use HelloWorld.Web, :controller def index(conn, _params) do render conn, "index.html" end end
web/views/hello_view.ex:
defmodule HelloWorld.HelloView do use HelloWorld.Web, :view end
web/templates/ciao/index.html.eex:
<h1>Hello, World!</h1>
Questi esempi mostrano entrambi "Hello, World!" nel browser ma presentano alcune differenze fondamentali.
Innanzitutto, devi dichiarare esplicitamente il modello che desideri rendere in Phoenix a differenza di Ruby on Rails.
Successivamente, abbiamo dovuto includere una vista in Phoenix che si trova tra il controller e il modello.
Ora, ti starai chiedendo perché abbiamo bisogno di una vista se è vuota? La chiave qui è che, sotto il cofano, Phoenix compila il modello in una funzione più o meno uguale al codice seguente:
defmodule HelloWorld.HelloView do use HelloWorld.Web, :view def render("index.html", _assigns) do raw("<h1>Hello, World!</h1>") end end
Puoi eliminare il modello e utilizzare la nuova funzione di rendering e otterrai lo stesso risultato. Questo è utile quando vuoi solo restituire del testo o anche JSON.
I modelli sono proprio questo: modelli di dati.
In Phoenix, i modelli gestiscono principalmente la convalida dei dati, il relativo schema, le relazioni con altri modelli e il modo in cui vengono presentati.
Specificare lo schema nel modello può sembrare strano all'inizio, ma consente di creare facilmente campi "virtuali", che sono campi che non vengono mantenuti nel database. Diamo un'occhiata a un esempio:
defmodule HelloPhoenix.User do use HelloPhoenix.Web, :model schema "users" do field :name, :string field :email, :string field :password, :string, virtual: true field :password_hash, :string end end
Qui definiamo quattro campi nella tabella "utenti": nome, email, password e password_hash.
Non c'è molto di interessante qui, ad eccezione del campo "password" che è impostato come "virtuale".
Questo ci consente di impostare e ottenere questo campo senza salvare le modifiche nel database. È utile perché avremmo la logica per convertire quella password in un hash, che salveremmo nel campo "password_hash" e quindi lo salveremmo nel database.
Devi ancora creare una migrazione; lo schema nel modello è necessario perché i campi non vengono caricati automaticamente nel modello come in Ruby on Rails.
La differenza tra Phoenix e Ruby on Rails è che il modello non gestisce la persistenza nel database. Questo è gestito da un modulo chiamato "Repo" che è configurato con le informazioni del database, come mostrato nell'esempio seguente:
config :my_app, Repo, adapter: Ecto.Adapters.Postgres, database: "ecto_simple", username: "postgres", password: "postgres", hostname: "localhost"
Questo codice è incluso nei file di configurazione specifici dell'ambiente, come config/dev.exs
o config/test.exs
. Ciò ci consente di utilizzare Repo per eseguire operazioni di database come la creazione e l'aggiornamento.

Repo.insert(%User{name: "John Smith", example: "[email protected]"}) do {:ok, user} -> # Insertion was successful {:error, changeset} -> # Insertion failed end
Questo è un esempio comune nei controller di Phoenix.
Forniamo a un utente un nome e un'e-mail e Repo tenta di creare un nuovo record nel database. Possiamo quindi, facoltativamente, gestire il tentativo riuscito o fallito come mostrato nell'esempio.
Per comprendere meglio questo codice, è necessario comprendere il pattern matching in Elixir. Il valore restituito dalla funzione è una tupla. La funzione restituisce una tupla di due valori, uno stato e quindi il modello o un set di modifiche. Un set di modifiche è un modo per tenere traccia delle modifiche e convalidare un modello (i set di modifiche verranno discussi nella sezione successiva).
La prima tupla, dall'alto verso il basso, che corrisponde allo schema della tupla restituita dalla funzione che ha tentato di inserire l'Utente nel database, eseguirà la sua funzione definita.
Avremmo potuto impostare una variabile per lo stato invece di un atomo (che è fondamentalmente ciò che è un simbolo in Ruby), ma poi avremmo abbinato i tentativi riusciti e falliti e avremmo usato la stessa funzione per entrambe le situazioni. Specificando quale atomo vogliamo abbinare, definiamo una funzione specifica per quello stato.
Ruby on Rails ha la funzionalità di persistenza integrata nel modello tramite ActiveRecord. Ciò aggiunge più responsabilità al modello e, a volte, può rendere più complesso il test di un modello. A Phoenix, questo è stato separato in un modo che ha senso e impedisce di gonfiare ogni modello con la logica della persistenza.
I set di modifiche consentono chiare regole di convalida e trasformazione.
In Ruby on Rails, la convalida e la trasformazione dei dati possono essere fonte di bug difficili da trovare. Questo perché non è immediatamente evidente quando i dati vengono trasformati da callback come "before_create" e convalide.
In Phoenix, esegui esplicitamente queste convalide e trasformazioni utilizzando i set di modifiche. Questa è una delle mie caratteristiche preferite in Phoenix.
Diamo un'occhiata a un changeset aggiungendone uno al modello precedente:
defmodule HelloPhoenix.User do use HelloPhoenix.Web, :model schema "users" do field :name, :string field :email, :string field :password, :string, virtual: true field :password_hash, :string end def changeset(struct, params \\ %{}) do struct |> cast(params, [:name, :email, :password]) |> validate_required([:email, :password]) end end
Qui il changeset fa due cose.
Innanzitutto, chiama la funzione "cast" che è una whitelist di campi consentiti, simile a "strong_parameters" in Ruby on Rails, quindi convalida che i campi "email" e "password" siano inclusi, creando il campo "nome" opzionale. In questo modo, solo i campi consentiti possono essere modificati dagli utenti.
La cosa bella di questo approccio è che non siamo limitati a un set di modifiche. Possiamo creare più changeset. Un set di modifiche per la registrazione e uno per l'aggiornamento dell'utente è comune. Forse vogliamo solo richiedere il campo della password durante la registrazione ma non durante l'aggiornamento dell'utente.
Confronta questo approccio con ciò che viene comunemente fatto in Ruby on Rails, dovresti specificare che la convalida dovrebbe essere eseguita solo su "create". Questo a volte rende difficile in Rails capire cosa sta facendo il tuo codice una volta che hai un modello complesso.
La funzionalità di importazione è semplice, ma flessibile.
Gran parte delle funzionalità di una libreria denominata “Ecto” viene importata nei modelli. I modelli di solito hanno questa linea vicino alla parte superiore:
use HelloPhoenix.Web, :model
Il modulo “HelloPhoenix.Web” si trova in “web/web.ex”. Nel modulo dovrebbe esserci una funzione chiamata "modello" come segue:
def model do quote do use Ecto.Schema import Ecto import Ecto.Changeset import Ecto.Query end end
Qui puoi vedere quali moduli stiamo importando da Ecto. Puoi rimuovere o aggiungere qualsiasi altro modulo che desideri qui e verranno importati in tutti i modelli.
Esistono anche funzioni simili come "visualizzazione" e "controllore" che hanno lo stesso scopo rispettivamente per le visualizzazioni e i controller.
Le parole chiave di quote
e use
potrebbero sembrare confuse. Per questo esempio, puoi pensare a un preventivo come se eseguisse direttamente quel codice nel contesto del modulo che sta chiamando quella funzione. Quindi sarà l'equivalente di aver scritto il codice all'interno di virgolette nel modulo.
La parola chiave use consente inoltre di eseguire codice nel contesto in cui viene chiamato. Richiede essenzialmente il modulo specificato, quindi chiama la macro __using__
sul modulo che lo esegue nel contesto in cui è stato chiamato. Puoi leggere di più sul preventivo e sull'uso nella guida ufficiale.
Questo ti aiuta davvero a capire dove si trovano determinate funzioni nel framework e aiuta a ridurre la sensazione che il framework stia facendo molta "magia".
La concorrenza è al centro.
La concorrenza è gratuita in Phoenix perché è una caratteristica principale di Elixir. Ottieni un'applicazione in grado di generare più processi ed essere eseguita su più core senza alcuna preoccupazione per la sicurezza e l'affidabilità dei thread.
Puoi generare un nuovo processo in Elixir semplicemente in questo modo:
spawn fn -> 1 + 2 end
Tutto dopo lo spawn
e prima della end
verrà eseguito in un nuovo processo.
In effetti, ogni richiesta a Phoenix viene gestita nel proprio processo. Elixir utilizza la potenza di Erlang VM per offrire una concorrenza affidabile ed efficiente "gratuitamente".
Ciò rende Phoenix anche un'ottima scelta per l'esecuzione di servizi che utilizzano WebSocket, poiché i WebSocket devono mantenere una connessione aperta tra il client e il server (il che significa che è necessario creare la propria applicazione in modo che possa gestire possibilmente migliaia di connessioni simultanee).
Questi requisiti aggiungerebbero molta complessità a un progetto basato su Ruby on Rails, ma Phoenix può soddisfare questi requisiti gratuitamente tramite Elixir.
Se desideri utilizzare WebSocket nella tua applicazione Phoenix, dovrai utilizzare i canali. È l'equivalente di ActionCable
in Ruby on Rails, ma è meno complesso da configurare perché non è necessario eseguire un server separato.
Phoenix rende indolore la creazione di moderne app Web.
Anche se ci siamo concentrati in gran parte sulle differenze, Phoenix ha alcune cose in comune con Ruby on Rails.
Phoenix segue più o meno lo stesso schema MVC di Ruby on Rails, quindi capire quale codice va dove non dovrebbe essere difficile ora che conosci le differenze principali. Phoenix ha anche generatori simili a Ruby on Rails per la creazione di modelli, controller, migrazioni e altro.
Dopo aver appreso Elisir, ti avvicinerai lentamente ai livelli di produttività di Ruby on Rails una volta che ti sentirai più a tuo agio con Phoenix.
Le poche volte in cui non mi sento così produttivo è quando incontro un problema che è stato risolto da una gemma in Ruby e non riesco a trovare una libreria simile per Elixir. Fortunatamente, queste lacune vengono lentamente colmate dalla crescente comunità di Elixir.
Le differenze in Phoenix aiutano a risolvere gran parte del dolore derivante dalla gestione di complessi progetti Ruby on Rails. Sebbene non risolvano tutti i problemi, aiutano a spingerti nella giusta direzione. Scegliere un framework lento perché vuoi produttività non è più una scusa valida, Phoenix ti consente di avere entrambi.