Знакомьтесь с Phoenix: Rails-подобная платформа для современных веб-приложений на Elixir
Опубликовано: 2022-03-11Фреймворк Phoenix быстро набирает популярность, предлагая производительность таких фреймворков, как Ruby on Rails, и в то же время является одним из самых быстрых доступных фреймворков. Это разрушает миф о том, что вы должны пожертвовать производительностью, чтобы повысить производительность.
Так что же такое Феникс?
Phoenix — это веб-фреймворк, созданный на языке программирования Elixir. Elixir, построенный на виртуальной машине Erlang, используется для создания отказоустойчивых распределенных систем с малой задержкой, которые становятся все более необходимыми качествами современных веб-приложений. Вы можете узнать больше об Эликсире из этого сообщения в блоге или их официального руководства.
Если вы разработчик Ruby on Rails, вам определенно следует заинтересоваться Phoenix из-за обещаемого прироста производительности. Разработчики других фреймворков также могут узнать, как Phoenix подходит к веб-разработке.
В этой статье мы узнаем о некоторых вещах в Phoenix, о которых вам следует помнить, если вы пришли из мира Ruby on Rails.
В отличие от Ruby, Elixir — это функциональный язык программирования, что, вероятно, является самым большим отличием, с которым вам, возможно, придется иметь дело. Тем не менее, между этими двумя платформами есть некоторые ключевые различия, о которых должен знать каждый, кто изучает Phoenix, чтобы максимизировать свою производительность.
Соглашения об именах проще.
Это небольшой пример, но его легко испортить, если вы переходите с Ruby on Rails.
В Фениксе принято писать все в единственном числе. Таким образом, у вас будет «UserController» вместо «UsersController», как в Ruby on Rails. Это применимо везде, кроме именования таблиц базы данных, где принято называть таблицу во множественном числе.
Это может показаться не таким уж большим делом после того, как я узнал, когда и где использовать форму множественного числа в Ruby on Rails, но поначалу это немного сбивало с толку, когда я учился использовать Ruby on Rails, и я уверен, что это сбило многих с толку. другие также. Phoenix избавляет вас от одного беспокойства меньше.
Маршрутизацией легче управлять.
Phoenix и Ruby on Rails очень похожи в том, что касается маршрутизации. Ключевое отличие заключается в том, как вы можете контролировать способ обработки запроса.
В Ruby on Rails (и других Rack-приложениях) это делается с помощью промежуточного программного обеспечения, тогда как в Phoenix это делается с помощью так называемых «плагинов».
Плагины — это то, что вы используете для обработки соединения.
Например, промежуточное ПО Rails::Rack::Logger
регистрирует запрос в Rails, что в Phoenix было бы выполнено с помощью подключаемого модуля Plug.Logger
. По сути, все, что вы можете сделать в промежуточном программном обеспечении Rack, можно сделать с помощью подключаемых модулей.
Вот пример роутера в Фениксе:
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
Во-первых, давайте посмотрим на трубопроводы.
Это группы пробок, через которые будет проходить запрос. Думайте об этом как о стеке промежуточного программного обеспечения. Их можно использовать, например, для проверки того, что запрос ожидает HTML, получения сеанса и проверки безопасности запроса. Это все происходит до достижения контроллера.
Поскольку вы можете указать несколько конвейеров, вы можете выбирать, какие заглушки необходимы для конкретных маршрутов. Например, в нашем маршрутизаторе у нас есть другой конвейер для запросов страниц (HTML) и API (JSON).
Не всегда имеет смысл использовать один и тот же конвейер для разных типов запросов. Phoenix дает нам такую гибкость.
Представления и шаблоны — это две разные вещи.
Представления в Phoenix отличаются от представлений в Ruby on Rails.
Представления в Phoenix отвечают за отображение шаблонов и предоставление функций, упрощающих использование необработанных данных для шаблонов. Представление в Phoenix больше всего напоминает помощника в Ruby on Rails с добавлением рендеринга шаблона.
Давайте напишем пример, который отображает «Hello, World!» в браузере.
Рубин на рельсах:
приложение/контроллеры/hello_controller.rb:
class HelloController < ApplicationController def index end end
приложение/представления/привет/index.html.erb:
<h1>Hello, World!</h1>
Феникс:
веб/контроллеры/hello_controller.ex:
defmodule HelloWorld.HelloController do use HelloWorld.Web, :controller def index(conn, _params) do render conn, "index.html" end end
веб/представления/hello_view.ex:
defmodule HelloWorld.HelloView do use HelloWorld.Web, :view end
веб/шаблоны/привет/index.html.eex:
<h1>Hello, World!</h1>
Оба этих примера отображают «Hello, World!» в браузере, но имеют некоторые ключевые отличия.
Во-первых, вы должны явно указать шаблон, который вы хотите отображать в Phoenix, в отличие от Ruby on Rails.
Далее нам нужно было включить представление в Phoenix, которое стоит между контроллером и шаблоном.
Теперь вам может быть интересно, зачем нам нужно представление, если оно пустое? Ключевым моментом здесь является то, что под капотом Phoenix компилирует шаблон в функцию, примерно подобную приведенному ниже коду:
defmodule HelloWorld.HelloView do use HelloWorld.Web, :view def render("index.html", _assigns) do raw("<h1>Hello, World!</h1>") end end
Вы можете удалить шаблон и использовать новую функцию рендеринга, и вы получите тот же результат. Это полезно, когда вы просто хотите вернуть текст или даже JSON.
Модели — это просто модели данных.
В Phoenix модели главным образом обрабатывают проверку данных, их схему, отношения с другими моделями и то, как они представлены.
На первый взгляд указание схемы в модели может показаться странным, но это позволяет легко создавать «виртуальные» поля, то есть поля, которые не сохраняются в базе данных. Давайте рассмотрим пример:
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
Здесь мы определяем четыре поля в таблице «users»: имя, адрес электронной почты, пароль и password_hash.
Здесь не так много интересного, кроме поля «пароль», которое установлено как «виртуальный».
Это позволяет нам устанавливать и получать это поле без сохранения изменений в базе данных. Это полезно, потому что у нас будет логика для преобразования этого пароля в хэш, который мы сохраним в поле «password_hash», а затем сохраним его в базе данных.
Вам все еще нужно создать миграцию; схема в модели необходима, потому что поля не загружаются в модель автоматически, как в Ruby on Rails.
Разница между Phoenix и Ruby on Rails заключается в том, что модель не поддерживает сохранение данных в базе данных. Это обрабатывается модулем под названием «Repo», который настроен с информацией о базе данных, как показано в примере ниже:
config :my_app, Repo, adapter: Ecto.Adapters.Postgres, database: "ecto_simple", username: "postgres", password: "postgres", hostname: "localhost"
Этот код включен в файлы конфигурации среды, такие как config/dev.exs
или config/test.exs
. Это позволяет нам затем использовать Repo для выполнения операций с базой данных, таких как создание и обновление.

Repo.insert(%User{name: "John Smith", example: "[email protected]"}) do {:ok, user} -> # Insertion was successful {:error, changeset} -> # Insertion failed end
Это распространенный пример в контроллерах в Phoenix.
Мы предоставляем пользователю имя и адрес электронной почты, и репо пытается создать новую запись в базе данных. Затем мы можем, при желании, обработать успешную или неудачную попытку, как показано в примере.
Чтобы лучше понять этот код, вам нужно разобраться в сопоставлении с образцом в Elixir. Значение, возвращаемое функцией, является кортежем. Функция возвращает кортеж из двух значений, статус, а затем либо модель, либо набор изменений. Набор изменений — это способ отслеживать изменения и проверять модель (наборы изменений будут обсуждаться в следующем разделе).
Первый кортеж сверху вниз, который соответствует шаблону кортежа, возвращаемого функцией, которая пыталась вставить пользователя в базу данных, запустит свою определенную функцию.
Мы могли бы установить переменную для статуса вместо атома (что, по сути, является символом в Ruby), но тогда мы сопоставили бы как успешные, так и неудачные попытки и использовали бы одну и ту же функцию для обеих ситуаций. Указав, какой атом мы хотим сопоставить, мы определяем функцию специально для этого статуса.
Ruby on Rails имеет функцию сохранения, встроенную в модель через ActiveRecord. Это увеличивает ответственность модели и иногда может усложнить ее тестирование. В Phoenix это было разделено таким образом, чтобы это имело смысл и предотвращало раздувание каждой модели логикой постоянства.
Наборы изменений допускают четкие правила проверки и преобразования.
В Ruby on Rails проверка и преобразование данных могут быть источником трудно находимых ошибок. Это связано с тем, что не сразу становится очевидным, когда данные преобразуются с помощью обратных вызовов, таких как «before_create» и проверок.
В Phoenix вы явно выполняете эти проверки и преобразования с помощью наборов изменений. Это одна из моих любимых функций в Phoenix.
Давайте посмотрим на набор изменений, добавив его к предыдущей модели:
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
Здесь набор изменений делает две вещи.
Во-первых, он вызывает функцию «cast», которая представляет собой белый список разрешенных полей, аналогичный «strong_parameters» в Ruby on Rails, а затем проверяет, включены ли поля «email» и «password», делая поле «name» необязательный. Таким образом, пользователи смогут изменять только те поля, которые вы разрешили.
В этом подходе хорошо то, что мы не ограничены одним набором изменений. Мы можем создать несколько наборов изменений. Набор изменений для регистрации и один для обновления пользователя являются общими. Возможно, мы хотим, чтобы поле пароля требовалось только при регистрации, но не при обновлении пользователя.
Сравните этот подход с тем, что обычно делается в Ruby on Rails, вам нужно будет указать, что проверка должна выполняться только при «создании». Это иногда затрудняет в Rails определение того, что делает ваш код, когда у вас есть сложная модель.
Функциональность импорта проста, но гибка.
Большая часть функциональности библиотеки под названием «Ecto» импортирована в модели. У моделей обычно есть эта линия вверху:
use HelloPhoenix.Web, :model
Модуль «HelloPhoenix.Web» находится в «web/web.ex». В модуле должна быть функция под названием «модель» следующим образом:
def model do quote do use Ecto.Schema import Ecto import Ecto.Changeset import Ecto.Query end end
Здесь вы можете увидеть, какие модули мы импортируем из Ecto. Вы можете удалить или добавить любые другие модули, которые вы хотите здесь, и они будут импортированы во все модели.
Существуют также аналогичные функции, такие как «представление» и «контроллер», которые служат той же цели для представлений и контроллеров соответственно.
Ключевые слова quote
и use
могут показаться запутанными. В этом примере вы можете думать о цитате как о непосредственном запуске этого кода в контексте модуля, вызывающего эту функцию. Таким образом, это будет эквивалентно написанию кода внутри цитаты в модуле.
Ключевое слово use также позволяет запускать код в том контексте, в котором он вызывается. По сути, для этого требуется указанный модуль, а затем вызывается макрос __using__
для модуля, в котором он запущен, в контексте того, где он был вызван. Вы можете прочитать больше о цитате и использовании в официальном руководстве.
Это действительно помогает вам понять, где находятся определенные функции во фреймворке, и помогает уменьшить ощущение, что фреймворк делает много «волшебства».
Параллелизм лежит в основе.
Параллелизм предоставляется бесплатно в Phoenix, потому что это основная функция Elixir. Вы получаете приложение, которое может запускать несколько процессов и работать на нескольких ядрах, не беспокоясь о безопасности и надежности потоков.
Вы можете просто создать новый процесс в Эликсире:
spawn fn -> 1 + 2 end
Все после spawn
и до end
будет выполняться в новом процессе.
Фактически каждый запрос в Phoenix обрабатывается в отдельном процессе. Elixir использует мощь виртуальной машины Erlang для обеспечения надежного и эффективного параллелизма «бесплатно».
Это также делает Phoenix отличным выбором для запуска служб, использующих WebSockets, поскольку WebSockets необходимо поддерживать открытое соединение между клиентом и сервером (а это означает, что вам нужно построить приложение так, чтобы оно могло обрабатывать, возможно, тысячи одновременных подключений).
Эти требования значительно усложнили бы проект, построенный на Ruby on Rails, но Phoenix может выполнить эти требования бесплатно с помощью Elixir.
Если вы хотите использовать WebSockets в своем приложении Phoenix, вам нужно будет использовать каналы. Это эквивалент ActionCable
в Ruby on Rails, но его проще настроить, поскольку вам не нужно запускать отдельный сервер.
Phoenix позволяет безболезненно создавать современные веб-приложения.
Хотя мы в основном сосредоточились на различиях, у Phoenix есть кое-что общее с Ruby on Rails.
Phoenix примерно следует тому же шаблону MVC, что и Ruby on Rails, поэтому выяснить, какой код куда идет, не должно быть сложно, когда вы знаете об основных различиях. В Phoenix также есть генераторы, аналогичные Ruby on Rails, для создания моделей, контроллеров, миграций и многого другого.
Изучив Elixir, вы постепенно приблизитесь к уровням продуктивности Ruby on Rails, как только освоитесь с Phoenix.
Несколько раз я чувствую себя не столь продуктивным, когда я сталкиваюсь с проблемой, которая была решена с помощью драгоценного камня в Ruby, и я не могу найти аналогичную библиотеку для Elixir. К счастью, эти пробелы постепенно заполняются растущим сообществом Elixir.
Различия в Phoenix помогают решить многие проблемы, связанные с управлением сложными проектами Ruby on Rails. Хотя они не решают всех проблем, они помогают подтолкнуть вас в правильном направлении. Выбор медленного фреймворка, потому что вы хотите повысить производительность, больше не является уважительной причиной, Phoenix позволяет вам иметь и то, и другое.