Poznaj Phoenix: Framework podobny do Railsów dla nowoczesnych aplikacji internetowych na Elixir
Opublikowany: 2022-03-11Framework Phoenix szybko rośnie wraz z popularnością, oferując produktywność frameworków takich jak Ruby on Rails, będąc jednocześnie jednym z najszybszych dostępnych frameworków. Przełamuje mit, że aby zwiększyć produktywność, trzeba poświęcić wydajność.
Czym właściwie jest Phoenix?
Phoenix to platforma internetowa zbudowana w języku programowania Elixir. Elixir, zbudowany na maszynie wirtualnej Erlang, służy do budowania systemów rozproszonych o niskich opóźnieniach, odpornych na błędy, które są coraz bardziej niezbędnymi cechami nowoczesnych aplikacji internetowych. Możesz dowiedzieć się więcej o Eliksirze z tego wpisu na blogu lub ich oficjalnego przewodnika.
Jeśli jesteś programistą Ruby on Rails, zdecydowanie powinieneś zainteresować się Phoenix ze względu na obiecywane przez nie korzyści. Deweloperzy innych frameworków mogą również śledzić, jak Phoenix podchodzi do tworzenia stron internetowych.
W tym artykule dowiemy się o kilku rzeczach w Phoenix, o których powinieneś pamiętać, jeśli pochodzisz ze świata Ruby on Rails.
W przeciwieństwie do Ruby, Elixir jest funkcjonalnym językiem programowania, co jest prawdopodobnie największą różnicą, z jaką możesz mieć do czynienia. Istnieją jednak pewne kluczowe różnice między tymi dwiema platformami, o których każdy, kto uczy się Phoenix, powinien być świadomy, aby zmaksymalizować swoją produktywność.
Konwencje nazewnictwa są prostsze.
To jest małe, ale łatwo zepsuć, jeśli pochodzisz z Ruby on Rails.
W Phoenix przyjęło się pisać wszystko w liczbie pojedynczej. Więc miałbyś „UserController” zamiast „UsersController”, tak jak w Ruby on Rails. Ma to zastosowanie wszędzie z wyjątkiem nazywania tabel bazy danych, gdzie konwencją jest nazywanie tabeli w liczbie mnogiej.
To może nie wydawać się wielkim problemem po tym, jak dowiedziałem się, kiedy i gdzie używać liczby mnogiej w Ruby on Rails, ale na początku było to trochę zagmatwane, kiedy uczyłem się korzystać z Ruby on Rails i jestem pewien, że wielu pomyliło inni też. Phoenix daje ci jedną rzecz mniej, o którą musisz się martwić.
Routing jest łatwiejszy w zarządzaniu.
Phoenix i Ruby on Rails są bardzo podobne, jeśli chodzi o routing. Kluczowa różnica polega na tym, jak możesz kontrolować sposób przetwarzania żądania.
W Ruby on Rails (i innych aplikacjach Rack) odbywa się to za pomocą oprogramowania pośredniczącego, podczas gdy w Phoenix odbywa się to za pomocą tak zwanych „wtyczek”.
Wtyczki są tym, czego używasz do przetwarzania połączenia.
Na przykład, middleware Rails::Rack::Logger
rejestruje żądanie w Railsach, co w Phoenix byłoby wykonane za pomocą wtyczki Plug.Logger
. Zasadniczo wszystko, co można zrobić w oprogramowaniu pośredniczącym w szafie, można zrobić za pomocą wtyczek.
Oto przykład routera w 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
Najpierw spójrzmy na rurociągi.
Są to grupy wtyczek, przez które będzie przesyłane żądanie. Pomyśl o tym jako o stosie oprogramowania pośredniego. Można ich użyć na przykład do sprawdzenia, czy żądanie oczekuje kodu HTML, pobrania sesji i upewnienia się, że żądanie jest bezpieczne. To wszystko dzieje się przed dotarciem do kontrolera.
Ponieważ możesz określić wiele potoków, możesz wybrać, które wtyczki są niezbędne dla określonych tras. Na przykład w naszym routerze mamy inny potok dla żądań stron (HTML) i API (JSON).
Nie zawsze ma sens używanie dokładnie tego samego potoku dla różnych typów żądań. Phoenix daje nam taką elastyczność.
Widoki i szablony to dwie różne rzeczy.
Widoki w Phoenix to nie to samo, co widoki w Ruby on Rails.
Widoki w Phoenix odpowiadają za renderowanie szablonów i udostępnianie funkcji, które ułatwiają korzystanie z nieprzetworzonych danych. Widok w Phoenix najbardziej przypomina helper w Ruby on Rails, z dodatkiem renderowania szablonu.
Napiszmy przykład, który wyświetla „Hello, World!” w przeglądarce.
Ruby on Rails:
aplikacja/kontrolery/hello_controller.rb:
class HelloController < ApplicationController def index end end
app/views/hello/index.html.erb:
<h1>Hello, World!</h1>
Feniks:
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>
Oba te przykłady wyświetlają „Hello, World!” w przeglądarce, ale mają pewne kluczowe różnice.
Po pierwsze, musisz wyraźnie określić szablon, który chcesz renderować w Phoenix, inaczej niż w Ruby on Rails.
Następnie musieliśmy dołączyć widok w Phoenix, który znajduje się pomiędzy kontrolerem a szablonem.
Teraz możesz się zastanawiać, po co nam widok, jeśli jest pusty? Kluczem jest tutaj to, że pod maską Phoenix kompiluje szablon w funkcję mniej więcej równą poniższemu kodowi:
defmodule HelloWorld.HelloView do use HelloWorld.Web, :view def render("index.html", _assigns) do raw("<h1>Hello, World!</h1>") end end
Możesz usunąć szablon i użyć nowej funkcji renderowania, a uzyskasz ten sam wynik. Jest to przydatne, gdy chcesz tylko zwrócić tekst lub nawet JSON.
Modele to po prostu modele danych.
W oprogramowaniu Phoenix modele obsługują przede wszystkim walidację danych, ich schemat, relacje z innymi modelami oraz sposób ich prezentacji.
Określanie schematu w modelu może na pierwszy rzut oka wydawać się dziwne, ale umożliwia łatwe tworzenie pól „wirtualnych”, czyli pól, które nie są utrwalane w bazie danych. Spójrzmy na przykład:
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
Tutaj definiujemy cztery pola w tabeli „użytkownicy”: imię i nazwisko, adres e-mail, hasło i hash_hasła.
Niewiele jest tutaj interesujące, z wyjątkiem pola „hasło”, które jest ustawione jako „wirtualne”.
Pozwala nam to ustawić i pobrać to pole bez zapisywania zmian w bazie danych. Jest to przydatne, ponieważ mielibyśmy logikę do przekonwertowania tego hasła na hash, który zapisalibyśmy w polu „password_hash”, a następnie zapisali to w bazie danych.
Nadal musisz utworzyć migrację; schemat w modelu jest potrzebny, ponieważ pola nie są automatycznie ładowane do modelu, jak w Ruby on Rails.
Różnica między Phoenix i Ruby on Rails polega na tym, że model nie obsługuje trwałości w bazie danych. Jest to obsługiwane przez moduł o nazwie „Repo”, który jest skonfigurowany z informacjami z bazy danych, jak pokazano w poniższym przykładzie:
config :my_app, Repo, adapter: Ecto.Adapters.Postgres, database: "ecto_simple", username: "postgres", password: "postgres", hostname: "localhost"
Ten kod jest zawarty w plikach konfiguracyjnych specyficznych dla środowiska, takich jak config/dev.exs
lub config/test.exs
. Dzięki temu możemy następnie użyć Repo do wykonywania operacji na bazie danych, takich jak tworzenie i aktualizacja.

Repo.insert(%User{name: "John Smith", example: "[email protected]"}) do {:ok, user} -> # Insertion was successful {:error, changeset} -> # Insertion failed end
Jest to typowy przykład w kontrolerach w Phoenix.
Udostępniamy Użytkownikowi imię i adres e-mail, a Repo podejmuje próbę utworzenia nowego rekordu w bazie danych. Możemy wtedy, opcjonalnie, obsłużyć udaną lub nieudaną próbę, jak pokazano w przykładzie.
Aby lepiej zrozumieć ten kod, musisz zrozumieć dopasowanie wzorców w Elixirze. Wartość zwracana przez funkcję jest krotką. Funkcja zwraca krotkę dwóch wartości, status, a następnie model lub zbiór zmian. Zestaw zmian to sposób na śledzenie zmian i walidację modelu (zestawy zmian zostaną omówione w następnej sekcji).
Pierwsza krotka, od góry do dołu, pasująca do wzorca krotki zwróconej przez funkcję, która próbowała wstawić Użytkownika do bazy danych, uruchomi swoją zdefiniowaną funkcję.
Moglibyśmy ustawić zmienną dla statusu zamiast atomu (co w zasadzie jest tym, czym jest symbol w Ruby), ale wtedy dopasujemy zarówno udane, jak i nieudane próby i użyjemy tej samej funkcji w obu sytuacjach. Określając, który atom chcemy dopasować, definiujemy funkcję specjalnie dla tego statusu.
Ruby on Rails ma wbudowaną w model funkcjonalność utrwalania poprzez ActiveRecord. Zwiększa to odpowiedzialność za model i czasami może w rezultacie sprawić, że testowanie modelu będzie bardziej złożone. W Phoenix zostało to rozdzielone w sposób, który ma sens i zapobiega rozdęciu każdego modelu logiką trwałości.
Zestawy zmian umożliwiają jasne reguły walidacji i transformacji.
W Ruby on Rails sprawdzanie poprawności i przekształcanie danych może być źródłem trudnych do znalezienia błędów. Dzieje się tak, ponieważ nie jest od razu oczywiste, kiedy dane są przekształcane przez wywołania zwrotne, takie jak „before_create” i walidacje.
W Phoenix wyraźnie wykonujesz te walidacje i przekształcenia przy użyciu zestawów zmian. To jedna z moich ulubionych funkcji w Phoenix.
Przyjrzyjmy się zestawowi zmian, dodając jeden do poprzedniego modelu:
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
Tutaj zestaw zmian robi dwie rzeczy.
Najpierw wywołuje funkcję „cast”, która jest białą listą dozwolonych pól, podobną do „strong_parameters” w Ruby on Rails, a następnie sprawdza, czy pola „email” i „password” są uwzględnione, tworząc pole „name” opcjonalny. W ten sposób tylko dozwolone pola mogą być modyfikowane przez użytkowników.
Zaletą tego podejścia jest to, że nie ograniczamy się do jednego zestawu zmian. Możemy stworzyć wiele zestawów zmian. Zestaw zmian do rejestracji i jeden do aktualizacji użytkownika jest powszechny. Może chcemy tylko wymagać pola hasła podczas rejestracji, ale nie podczas aktualizacji użytkownika.
Porównaj to podejście z tym, co zwykle robi się w Ruby on Rails, musisz określić, że walidacja powinna być uruchamiana tylko na „create”. Czasami sprawia to, że w Railsach trudno jest zorientować się, co robi twój kod, gdy masz złożony model.
Funkcja importowania jest prosta, ale elastyczna.
Wiele funkcji biblioteki o nazwie „Ecto” jest importowanych do modeli. Modele zwykle mają tę linię u góry:
use HelloPhoenix.Web, :model
Moduł „HelloPhoenix.Web” znajduje się w „web/web.ex”. W module powinna znajdować się funkcja o nazwie „model” w następujący sposób:
def model do quote do use Ecto.Schema import Ecto import Ecto.Changeset import Ecto.Query end end
Tutaj możesz zobaczyć, jakie moduły importujemy z Ecto. Możesz usunąć lub dodać dowolne inne moduły, które chcesz tutaj, a zostaną one zaimportowane do wszystkich modeli.
Istnieją również podobne funkcje, takie jak „widok” i „kontroler”, które służą temu samemu celowi odpowiednio dla widoków i kontrolerów.
quote
i słowa kluczowe mogą wydawać use
mylące. W tym przykładzie możesz myśleć o cytowaniu jako o bezpośrednim uruchomieniu tego kodu w kontekście modułu, który wywołuje tę funkcję. Będzie to więc odpowiednik napisania kodu w obrębie cytatu w module.
Słowo kluczowe use umożliwia również uruchamianie kodu w kontekście miejsca, w którym zostało wywołane. Zasadniczo wymaga określonego modułu, a następnie wywołuje makro __using__
w module, w którym jest uruchamiany w kontekście miejsca, w którym zostało wywołane. Możesz przeczytać więcej o cytowaniu i używaniu w oficjalnym przewodniku.
To naprawdę pomaga zrozumieć, gdzie znajdują się określone funkcje we frameworku i pomaga zmniejszyć poczucie, że framework robi dużo „magii”.
U podstaw leży współbieżność.
Współbieżność jest dostępna w Phoenix za darmo, ponieważ jest główną cechą Elixir. Otrzymujesz aplikację, która może tworzyć wiele procesów i działać na wielu rdzeniach bez obaw o bezpieczeństwo i niezawodność wątków.
Możesz odrodzić nowy proces w Elixirze, po prostu:
spawn fn -> 1 + 2 end
Wszystko po spawn
i przed end
zostanie uruchomione w nowym procesie.
W rzeczywistości każde żądanie w Phoenix jest obsługiwane we własnym procesie. Elixir wykorzystuje moc maszyny wirtualnej Erlang, aby zapewnić niezawodną i wydajną współbieżność „za darmo”.
To sprawia, że Phoenix jest doskonałym wyborem do uruchamiania usług korzystających z WebSockets, ponieważ WebSockets muszą utrzymywać otwarte połączenie między klientem a serwerem (co oznacza, że musisz zbudować aplikację tak, aby mogła obsługiwać tysiące jednoczesnych połączeń).
Te wymagania znacznie skomplikowałyby projekt zbudowany na Ruby on Rails, ale Phoenix może spełnić te wymagania za darmo poprzez Elixir.
Jeśli chcesz używać WebSockets w swojej aplikacji Phoenix, będziesz musiał użyć kanałów. Jest to odpowiednik ActionCable
w Ruby on Rails, ale jest mniej skomplikowany w konfiguracji, ponieważ nie trzeba uruchamiać osobnego serwera.
Phoenix sprawia, że tworzenie nowoczesnych aplikacji internetowych jest bezbolesne.
Chociaż w dużej mierze skupialiśmy się na różnicach, Phoenix ma pewne cechy wspólne z Ruby on Rails.
Phoenix z grubsza podąża za tym samym wzorcem MVC, co Ruby on Rails, więc ustalenie, dokąd zmierza kod, nie powinno być trudne teraz, gdy znasz główne różnice. Phoenix ma również podobne generatory jak Ruby on Rails do tworzenia modeli, kontrolerów, migracji i nie tylko.
Po nauczeniu się Elixir powoli zbliżasz się do poziomów produktywności Ruby on Rails, gdy już poczujesz się bardziej komfortowo z Phoenix.
Kilka razy nie czuję się tak produktywny, gdy napotykam problem, który został rozwiązany przez klejnot w Ruby, a nie mogę znaleźć podobnej biblioteki dla Elixir. Na szczęście te luki są powoli uzupełniane przez rosnącą społeczność Elixir.
Różnice w Phoenix pomagają rozwiązać wiele problemów związanych z zarządzaniem złożonymi projektami Ruby on Rails. Chociaż nie rozwiązują wszystkich problemów, pomagają pchać Cię we właściwym kierunku. Wybór powolnego frameworka, ponieważ zależy Ci na produktywności, nie jest już uzasadnioną wymówką, Phoenix pozwala Ci mieć jedno i drugie.