Faceți cunoștință cu Phoenix: un cadru asemănător șinelor pentru aplicații web moderne pe Elixir

Publicat: 2022-03-11

Cadrul Phoenix a crescut cu popularitate într-un ritm rapid, oferind productivitatea cadrelor precum Ruby on Rails, fiind în același timp unul dintre cele mai rapide cadre disponibile. Încalcă mitul că trebuie să sacrifici performanța pentru a crește productivitatea.

Deci, ce este exact Phoenix?

Phoenix este un cadru web construit cu limbajul de programare Elixir. Elixir, construit pe Erlang VM, este folosit pentru construirea de sisteme distribuite cu latență scăzută, tolerante la erori, care sunt calități din ce în ce mai necesare ale aplicațiilor web moderne. Puteți afla mai multe despre Elixir din această postare pe blog sau din ghidul lor oficial.

Dacă sunteți un dezvoltator Ruby on Rails, cu siguranță ar trebui să vă interesați de Phoenix din cauza câștigurilor de performanță pe care le promite. De asemenea, dezvoltatorii altor cadre pot urmări pentru a vedea cum abordează Phoenix dezvoltarea web.

Faceți cunoștință cu Phoenix pe Elixir: un cadru asemănător șinelor pentru aplicații web moderne

În acest articol vom afla câteva dintre lucrurile din Phoenix pe care ar trebui să le țineți cont dacă veniți din lumea Ruby on Rails.

Spre deosebire de Ruby, Elixir este un limbaj de programare funcțional, care este probabil cea mai mare diferență cu care este posibil să aveți de-a face. Cu toate acestea, există câteva diferențe cheie între aceste două platforme de care oricine învață Phoenix ar trebui să fie conștient pentru a-și maximiza productivitatea.

Convențiile de numire sunt mai simple.

Acesta este unul mic, dar este ușor să dai peste cap dacă vii de la Ruby on Rails.

În Phoenix, convenția este de a scrie totul la singular. Deci, ați avea un „UserController” în loc de un „UsersController” așa cum ați avea în Ruby on Rails. Acest lucru se aplică peste tot, cu excepția denumirii tabelelor bazei de date, unde convenția este de a numi tabelul la plural.

S-ar putea să nu pară mare lucru după ce am aflat când și unde să folosești forma pluralului în Ruby on Rails, dar a fost puțin confuz la început, când am învățat să folosesc Ruby on Rails și sunt sigur că i-a încurcat pe mulți. si altele. Phoenix vă oferă un lucru mai puțin de care să vă faceți griji.

Rutarea este mai ușor de gestionat.

Phoenix și Ruby on Rails sunt foarte asemănătoare când vine vorba de rutare. Diferența cheie constă în modul în care puteți controla modul în care este procesată o solicitare.

În Ruby on Rails (și în alte aplicații Rack), acest lucru se face prin middleware, în timp ce în Phoenix, acest lucru se face cu ceea ce se numește „plugs”.

Mufele sunt ceea ce utilizați pentru a procesa o conexiune.

De exemplu, middleware-ul Rails::Rack::Logger înregistrează o solicitare în Rails, care în Phoenix s-ar face cu conectorul Plug.Logger . Practic, orice puteți face în middleware-ul Rack se poate face folosind mufe.

Iată un exemplu de router în 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

Mai întâi, să ne uităm la conducte.

Acestea sunt grupuri de prize prin care se va deplasa cererea. Gândiți-vă la asta ca la o stivă de middleware. Acestea pot fi folosite, de exemplu, pentru a verifica dacă cererea așteaptă HTML, pentru a prelua sesiunea și pentru a vă asigura că solicitarea este sigură. Toate acestea se întâmplă înainte de a ajunge la controler.

Deoarece puteți specifica mai multe conducte, puteți alege și alege ce dopuri sunt necesare pentru anumite rute. În routerul nostru, de exemplu, avem o conductă diferită pentru cererile de pagini (HTML) și API (JSON).

Nu are întotdeauna sens să folosiți exact aceeași conductă pentru diferite tipuri de solicitări. Phoenix ne oferă această flexibilitate.

Vizualizările și șabloanele sunt două lucruri diferite.

Vizualizările din Phoenix nu sunt aceleași cu vizualizările din Ruby on Rails.

Views din Phoenix sunt responsabile de randarea șabloanelor și de furnizarea de funcții care facilitează utilizarea datelor brute de către șabloane. O vizualizare din Phoenix seamănă cel mai mult cu un ajutor din Ruby on Rails, cu adăugarea de redare a șablonului.

Să scriem un exemplu care afișează „Hello, World!” în browser.

Ruby on Rails:

app/controllers/hello_controller.rb:

 class HelloController < ApplicationController def index end end

app/views/hello/index.html.erb:

 <h1>Hello, World!</h1>

Phoenix:

web/controllers/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/hello/index.html.eex:

 <h1>Hello, World!</h1>

Aceste exemple afișează ambele „Hello, World!” în browser, dar au câteva diferențe cheie.

În primul rând, trebuie să precizați în mod explicit șablonul pe care doriți să îl redați în Phoenix, spre deosebire de Ruby on Rails.

Apoi, a trebuit să includem o vedere în Phoenix, care se află între controler și șablon.

Acum, poate vă întrebați de ce avem nevoie de o vedere dacă este goală? Cheia aici este că, sub capotă, Phoenix compilează șablonul într-o funcție aproximativ egală cu codul de mai jos:

 defmodule HelloWorld.HelloView do use HelloWorld.Web, :view def render("index.html", _assigns) do raw("<h1>Hello, World!</h1>") end end

Puteți șterge șablonul și utilizați noua funcție de randare și veți obține același rezultat. Acest lucru este util atunci când doriți doar să returnați text sau chiar JSON.

Modelele sunt doar atât: modele de date.

În Phoenix, modelele se ocupă în primul rând de validarea datelor, schema acesteia, relațiile sale cu alte modele și modul în care sunt prezentate.

Specificarea schemei în model poate suna ciudat la început, dar vă permite să creați cu ușurință câmpuri „virtuale”, care sunt câmpuri care nu sunt persistente în baza de date. Să aruncăm o privire la un exemplu:

 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

Aici definim patru câmpuri în tabelul „utilizatori”: nume, e-mail, parolă și password_hash.

Nu prea este interesant aici, cu excepția câmpului „parolă” care este setat ca „virtual”.

Acest lucru ne permite să setăm și să obținem acest câmp fără a salva modificările în baza de date. Este util pentru că am avea o logică să convertim acea parolă într-un hash, pe care l-am salva în câmpul „password_hash” și apoi o salvam în baza de date.

Mai trebuie să creați o migrare; schema din model este necesară deoarece câmpurile nu sunt încărcate automat în model ca în Ruby on Rails.

Diferența dintre Phoenix și Ruby on Rails este că modelul nu gestionează persistența în baza de date. Acest lucru este gestionat de un modul numit „Repo” care este configurat cu informațiile bazei de date, așa cum se arată în exemplul de mai jos:

 config :my_app, Repo, adapter: Ecto.Adapters.Postgres, database: "ecto_simple", username: "postgres", password: "postgres", hostname: "localhost"

Acest cod este inclus în fișierele de configurare specifice mediului, cum ar fi config/dev.exs sau config/test.exs . Acest lucru ne permite să folosim apoi Repo pentru a efectua operațiuni de bază de date, cum ar fi crearea și actualizarea.

 Repo.insert(%User{name: "John Smith", example: "[email protected]"}) do {:ok, user} -> # Insertion was successful {:error, changeset} -> # Insertion failed end

Acesta este un exemplu comun în controlerele din Phoenix.

Oferim unui Utilizator un nume și un e-mail, iar Repo încearcă să creeze o nouă înregistrare în baza de date. Putem apoi, opțional, să gestionăm încercarea reușită sau eșuată, așa cum se arată în exemplu.

Pentru a înțelege mai bine acest cod, trebuie să înțelegeți potrivirea modelelor în Elixir. Valoarea returnată de funcție este un tuplu. Funcția returnează un tuplu de două valori, o stare și apoi fie modelul, fie un set de modificări. Un set de modificări este o modalitate de a urmări modificările și de a valida un model (seturile de modificări vor fi discutate în secțiunea următoare).

Primul tuplu, de sus în jos, care se potrivește cu modelul tuplu-ului returnat de funcția care a încercat să insereze utilizatorul în baza de date, va rula funcția definită.

Am fi putut seta o variabilă pentru stare în loc de un atom (care este, practic, ceea ce este un simbol în Ruby), dar apoi am putea potrivi atât încercările reușite, cât și cele eșuate și am folosi aceeași funcție pentru ambele situații. Specificând atomul pe care vrem să-l potrivim, definim o funcție special pentru acea stare.

Ruby on Rails are funcționalitatea de persistență încorporată în model prin ActiveRecord. Acest lucru adaugă mai multă responsabilitate modelului și, uneori, poate face testarea unui model mai complexă, ca rezultat. În Phoenix, acest lucru a fost separat într-un mod care are sens și previne balonarea fiecărui model cu logica persistenței.

Seturile de modificări permit reguli clare de validare și transformare.

În Ruby on Rails, validarea și transformarea datelor poate fi sursa unor erori greu de găsit. Acest lucru se datorează faptului că nu este imediat evident când datele sunt transformate prin apeluri inverse, cum ar fi „before_create” și validări.

În Phoenix, faceți în mod explicit aceste validări și transformări folosind seturi de modificări. Aceasta este una dintre funcțiile mele preferate din Phoenix.

Să aruncăm o privire asupra unui set de modificări adăugând unul la modelul anterior:

 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

Aici setul de modificări face două lucruri.

Mai întâi, apelează funcția „cast” care este o listă albă de câmpuri permise, similar cu „strong_parameters” din Ruby on Rails, apoi validează faptul că câmpurile „e-mail” și „parolă” sunt incluse, făcând câmpul „nume” optional. În acest fel, numai câmpurile pe care le permiteți pot fi modificate de către utilizatori.

Lucrul frumos la această abordare este că nu ne limităm la un set de modificări. Putem crea mai multe seturi de modificări. Un set de modificări pentru înregistrare și unul pentru actualizarea utilizatorului este obișnuit. Poate vrem să cerem câmpul de parolă doar la înregistrare, dar nu și la actualizarea utilizatorului.

Comparați această abordare cu ceea ce se face în mod obișnuit în Ruby on Rails, ar trebui să specificați că validarea ar trebui să fie rulată numai la „creare”. Acest lucru îngreunează uneori în Rails să vă dați seama ce face codul dvs. odată ce aveți un model complex.

Funcționalitatea de import este simplă, dar flexibilă.

O mare parte din funcționalitatea unei biblioteci numită „Ecto” este importată în modele. Modelele au de obicei această linie în partea de sus:

 use HelloPhoenix.Web, :model

Modulul „HelloPhoenix.Web” se află în „web/web.ex”. În modul, ar trebui să existe o funcție numită „model”, după cum urmează:

 def model do quote do use Ecto.Schema import Ecto import Ecto.Changeset import Ecto.Query end end

Aici puteți vedea ce module importăm de la Ecto. Puteți elimina sau adăuga orice alte module pe care le doriți aici și acestea vor fi importate în toate modelele.

Există, de asemenea, funcții similare, cum ar fi „vizualizare” și „controller”, care servesc aceluiași scop pentru vizualizări și, respectiv, controlere.

quote și use cuvintelor cheie pot părea confuze. Pentru acest exemplu, vă puteți gândi la un citat ca rulând direct acel cod în contextul modulului care apelează acea funcție. Deci, va fi echivalent cu a fi scris codul în interiorul citatului din modul.

Cuvântul cheie use vă permite, de asemenea, să rulați cod în contextul în care este numit. În esență, necesită modulul specificat, apoi apelează macro-ul __using__ de pe modulul care îl rulează în contextul în care a fost apelat. Puteți citi mai multe despre citare și utilizare în ghidul oficial.

Acest lucru vă ajută cu adevărat să înțelegeți unde sunt situate anumite funcții în cadru și vă ajută să reduceți sentimentul că cadrul face multă „magie”.

Concurența este la bază.

Concurența vine gratuit în Phoenix, deoarece este o caracteristică principală a Elixir. Obțineți o aplicație care poate genera mai multe procese și poate rula pe mai multe nuclee fără să vă faceți griji cu privire la siguranța și fiabilitatea firelor.

Puteți genera un nou proces în Elixir pur și simplu:

 spawn fn -> 1 + 2 end

Tot ce se va întâmpla după spawn și înainte de end va rula într-un nou proces.

De fapt, fiecare cerere din Phoenix este tratată în propriul proces. Elixir folosește puterea Erlang VM pentru a aduce concurență fiabilă și eficientă „Gratuit”.

Acest lucru face, de asemenea, Phoenix o alegere excelentă pentru rularea serviciilor care utilizează WebSockets, deoarece WebSockets trebuie să mențină o conexiune deschisă între client și server (ceea ce înseamnă că trebuie să vă construiți aplicația astfel încât să poată gestiona mii de conexiuni simultane).

Aceste cerințe ar adăuga multă complexitate unui proiect construit pe Ruby on Rails, dar Phoenix poate îndeplini aceste cerințe gratuit prin Elixir.

Dacă doriți să utilizați WebSockets în aplicația dvs. Phoenix, va trebui să utilizați Canale. Este echivalentul ActionCable din Ruby on Rails, dar mai puțin complex de configurat, deoarece nu trebuie să rulați un server separat.

Phoenix face construirea de aplicații web moderne fără durere.

Deși ne-am concentrat în mare măsură pe diferențe, Phoenix are unele lucruri în comun cu Ruby on Rails.

Phoenix urmează aproximativ același model MVC ca și Ruby on Rails, așa că nu ar trebui să fie dificil să-ți dai seama ce cod merge încotro, acum că știi despre principalele diferențe. Phoenix are, de asemenea, generatoare similare cu Ruby on Rails pentru crearea de modele, controlere, migrații și multe altele.

După ce ați învățat Elixir, vă veți apropia încet de nivelurile de productivitate Ruby on Rails odată ce vă simțiți mai confortabil cu Phoenix.

De puținele ori în care mă simt la fel de productiv este atunci când întâmpin o problemă care a fost rezolvată de o bijuterie în Ruby și nu găsesc o bibliotecă similară pentru Elixir. Din fericire, aceste goluri sunt umplute încet de comunitatea Elixir în creștere.

Diferențele din Phoenix ajută la rezolvarea multor dureri generate de gestionarea proiectelor complexe Ruby on Rails. Deși nu rezolvă toate problemele, ele vă ajută să vă împingeți în direcția corectă. Alegerea unui cadru lent pentru că doriți productivitate nu mai este o scuză valabilă, Phoenix vă permite să aveți ambele.