Conozca a Phoenix: un marco similar a Rails para aplicaciones web modernas en Elixir
Publicado: 2022-03-11El marco Phoenix ha ido creciendo en popularidad a un ritmo acelerado, ofreciendo la productividad de marcos como Ruby on Rails, al mismo tiempo que es uno de los marcos más rápidos disponibles. Rompe el mito de que hay que sacrificar el rendimiento para aumentar la productividad.
Entonces, ¿qué es exactamente Phoenix?
Phoenix es un marco web construido con el lenguaje de programación Elixir. Elixir, basado en la máquina virtual Erlang, se utiliza para crear sistemas distribuidos tolerantes a fallas y de baja latencia, que son cualidades cada vez más necesarias de las aplicaciones web modernas. Puede obtener más información sobre Elixir en esta publicación de blog o en su guía oficial.
Si es un desarrollador de Ruby on Rails, definitivamente debería interesarse en Phoenix debido a las ganancias de rendimiento que promete. Los desarrolladores de otros marcos también pueden seguirlo para ver cómo aborda Phoenix el desarrollo web.
En este artículo aprenderemos algunas de las cosas en Phoenix que debes tener en cuenta si vienes del mundo de Ruby on Rails.
A diferencia de Ruby, Elixir es un lenguaje de programación funcional, que es probablemente la mayor diferencia con la que tendrás que lidiar. Sin embargo, existen algunas diferencias clave entre estas dos plataformas que cualquiera que esté aprendiendo Phoenix debe tener en cuenta para maximizar su productividad.
Las convenciones de nomenclatura son más simples.
Este es pequeño, pero es fácil estropearlo si vienes de Ruby on Rails.
En Phoenix, la convención es escribir todo en forma singular. Entonces tendría un "UserController" en lugar de un "UsersController" como lo haría en Ruby on Rails. Esto se aplica en todas partes, excepto cuando se nombran tablas de bases de datos, donde la convención es nombrar la tabla en plural.
Esto puede no parecer un gran problema después de aprender cuándo y dónde usar la forma plural en Ruby on Rails, pero fue un poco confuso al principio, cuando estaba aprendiendo a usar Ruby on Rails, y estoy seguro de que ha confundido a muchos. otros también. Phoenix le da una cosa menos de qué preocuparse.
El enrutamiento es más fácil de administrar.
Phoenix y Ruby on Rails son muy similares cuando se trata de enrutamiento. La diferencia clave está en cómo puede controlar la forma en que se procesa una solicitud.
En Ruby on Rails (y otras aplicaciones de Rack) esto se hace a través de middleware, mientras que en Phoenix, esto se hace con lo que se conoce como "enchufes".
Los enchufes son lo que usas para procesar una conexión.
Por ejemplo, el middleware Rails::Rack::Logger
registra una solicitud en Rails, lo que en Phoenix se haría con el complemento Plug.Logger
. Básicamente, cualquier cosa que pueda hacer en el middleware de Rack se puede hacer usando complementos.
Aquí hay un ejemplo de un enrutador en 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
Primero, echemos un vistazo a las tuberías.
Son grupos de enchufes por los que viajará la petición. Piense en ello como una pila de middleware. Estos se pueden usar, por ejemplo, para verificar que la solicitud espera HTML, recuperar la sesión y asegurarse de que la solicitud sea segura. Todo esto sucede antes de llegar al controlador.
Debido a que puede especificar varias tuberías, puede elegir qué enchufes son necesarios para rutas específicas. En nuestro enrutador, por ejemplo, tenemos una canalización diferente para solicitudes de páginas (HTML) y API (JSON).
No siempre tiene sentido usar exactamente la misma canalización para diferentes tipos de solicitudes. Phoenix nos brinda esa flexibilidad.
Las vistas y las plantillas son dos cosas diferentes.
Las vistas en Phoenix no son lo mismo que las vistas en Ruby on Rails.
Las vistas en Phoenix se encargan de representar las plantillas y proporcionar funciones que facilitan el uso de los datos sin procesar para las plantillas. Una vista en Phoenix se parece más a un ayudante en Ruby on Rails, con la adición de renderizar la plantilla.
Escribamos un ejemplo que muestre "¡Hola, mundo!" en el navegador.
Ruby on Rails:
aplicación/controladores/hola_controlador.rb:
class HelloController < ApplicationController def index end end
app/views/hola/index.html.erb:
<h1>Hello, World!</h1>
Fénix:
web/controladores/hola_controlador.ex:
defmodule HelloWorld.HelloController do use HelloWorld.Web, :controller def index(conn, _params) do render conn, "index.html" end end
web/views/hola_view.ex:
defmodule HelloWorld.HelloView do use HelloWorld.Web, :view end
web/templates/hola/index.html.eex:
<h1>Hello, World!</h1>
Ambos ejemplos muestran "¡Hola, mundo!" en el navegador, pero tienen algunas diferencias clave.
Primero, debe indicar explícitamente la plantilla que desea renderizar en Phoenix, a diferencia de Ruby on Rails.
A continuación, tuvimos que incluir una vista en Phoenix que se encuentra entre el controlador y la plantilla.
Ahora, puede que se pregunte por qué necesitamos una vista si está vacía. La clave aquí es que, bajo el capó, Phoenix compila la plantilla en una función aproximadamente igual al siguiente código:
defmodule HelloWorld.HelloView do use HelloWorld.Web, :view def render("index.html", _assigns) do raw("<h1>Hello, World!</h1>") end end
Puede eliminar la plantilla y usar la nueva función de renderizado y obtendrá el mismo resultado. Esto es útil cuando solo desea devolver texto o incluso JSON.
Los modelos son solo eso: modelos de datos.
En Phoenix, los modelos manejan principalmente la validación de datos, su esquema, sus relaciones con otros modelos y cómo se presenta.
Especificar el esquema en el modelo puede parecer extraño al principio, pero le permite crear fácilmente campos "virtuales", que son campos que no se conservan en la base de datos. Echemos un vistazo a un ejemplo:
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
Aquí definimos cuatro campos en la tabla de "usuarios": nombre, correo electrónico, contraseña y password_hash.
No hay mucho interesante aquí, excepto por el campo "contraseña" que está configurado como "virtual".
Esto nos permite configurar y obtener este campo sin guardar los cambios en la base de datos. Es útil porque tendríamos lógica para convertir esa contraseña en un hash, que guardaríamos en el campo "password_hash" y luego lo guardaríamos en la base de datos.
Todavía necesita crear una migración; se necesita el esquema en el modelo porque los campos no se cargan automáticamente en el modelo como en Ruby on Rails.
La diferencia entre Phoenix y Ruby on Rails es que el modelo no maneja la persistencia de la base de datos. Esto es manejado por un módulo llamado “Repo” que se configura con la información de la base de datos, como se muestra en el siguiente ejemplo:
config :my_app, Repo, adapter: Ecto.Adapters.Postgres, database: "ecto_simple", username: "postgres", password: "postgres", hostname: "localhost"
Este código se incluye en los archivos de configuración específicos del entorno, como config/dev.exs
o config/test.exs
. Esto nos permite luego usar Repo para realizar operaciones de base de datos como crear y actualizar.

Repo.insert(%User{name: "John Smith", example: "[email protected]"}) do {:ok, user} -> # Insertion was successful {:error, changeset} -> # Insertion failed end
Este es un ejemplo común en los controladores de Phoenix.
Le proporcionamos a un usuario un nombre y un correo electrónico y Repo intenta crear un nuevo registro en la base de datos. Entonces podemos, opcionalmente, manejar el intento exitoso o fallido como se muestra en el ejemplo.
Para comprender mejor este código, debe comprender la coincidencia de patrones en Elixir. El valor devuelto por la función es una tupla. La función devuelve una tupla de dos valores, un estado y luego el modelo o un conjunto de cambios. Un conjunto de cambios es una forma de realizar un seguimiento de los cambios y validar un modelo (los conjuntos de cambios se analizarán en la siguiente sección).
La primera tupla, de arriba a abajo, que coincida con el patrón de la tupla devuelta por la función que intentó insertar el Usuario en la base de datos, ejecutará su función definida.
Podríamos haber establecido una variable para el estado en lugar de un átomo (que es básicamente lo que es un símbolo en Ruby), pero luego igualaríamos los intentos exitosos y fallidos y usaríamos la misma función para ambas situaciones. Especificando qué átomo queremos hacer coincidir, definimos una función específicamente para ese estado.
Ruby on Rails tiene la funcionalidad de persistencia integrada en el modelo a través de ActiveRecord. Esto agrega más responsabilidad al modelo y, en ocasiones, puede hacer que probar un modelo sea más complejo como resultado. En Phoenix, esto se ha separado de una manera que tiene sentido y evita inflar cada modelo con lógica de persistencia.
Los conjuntos de cambios permiten reglas claras de validación y transformación.
En Ruby on Rails, la validación y transformación de datos puede ser la fuente de errores difíciles de encontrar. Esto se debe a que no es inmediatamente obvio cuando los datos se transforman mediante devoluciones de llamada como "before_create" y validaciones.
En Phoenix, haces explícitamente estas validaciones y transformaciones usando conjuntos de cambios. Esta es una de mis características favoritas en Phoenix.
Echemos un vistazo a un conjunto de cambios agregando uno al modelo 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
Aquí el conjunto de cambios hace dos cosas.
Primero, llama a la función "cast", que es una lista blanca de campos permitidos, similar a "strong_parameters" en Ruby on Rails, y luego valida que los campos "correo electrónico" y "contraseña" estén incluidos, haciendo que el campo "nombre" Opcional. De esta manera, solo los campos que usted permita pueden ser modificados por los usuarios.
Lo bueno de este enfoque es que no estamos limitados a un conjunto de cambios. Podemos crear múltiples conjuntos de cambios. Es común un conjunto de cambios para el registro y otro para actualizar el usuario. Tal vez solo queramos solicitar el campo de contraseña al registrarnos pero no al actualizar el usuario.
Compare este enfoque con lo que se hace comúnmente en Ruby on Rails, tendría que especificar que la validación debe ejecutarse solo en "crear". Esto a veces dificulta que Rails descubra qué está haciendo su código una vez que tiene un modelo complejo.
La funcionalidad de importación es sencilla, pero flexible.
Gran parte de la funcionalidad de una biblioteca llamada "Ecto" se importa a los modelos. Los modelos suelen tener esta línea cerca de la parte superior:
use HelloPhoenix.Web, :model
El módulo “HelloPhoenix.Web” se encuentra en “web/web.ex”. En el módulo, debe haber una función llamada "modelo" de la siguiente manera:
def model do quote do use Ecto.Schema import Ecto import Ecto.Changeset import Ecto.Query end end
Aquí puedes ver qué módulos estamos importando de Ecto. Puede eliminar o agregar cualquier otro módulo que desee aquí y se importarán a todos los modelos.
También hay funciones similares como "vista" y "controlador" que tienen el mismo propósito para las vistas y los controladores, respectivamente.
Las palabras clave quote
y use
pueden parecer confusas. Para este ejemplo, puede pensar en una cita como la ejecución directa de ese código en el contexto del módulo que llama a esa función. Entonces será el equivalente a haber escrito el código dentro de las comillas en el módulo.
La palabra clave use también le permite ejecutar código en el contexto de donde se llama. Esencialmente requiere el módulo especificado, luego llama a la macro __using__
en el módulo que lo ejecuta en el contexto de donde fue llamado. Puede leer más sobre cotización y uso en la guía oficial.
Esto realmente lo ayuda a comprender dónde se encuentran ciertas funciones en el marco y ayuda a reducir la sensación de que el marco está haciendo mucha "magia".
La concurrencia es el núcleo.
La concurrencia es gratuita en Phoenix porque es una característica principal de Elixir. Obtiene una aplicación que puede generar múltiples procesos y ejecutarse en múltiples núcleos sin preocuparse por la seguridad y confiabilidad de los subprocesos.
Puedes generar un nuevo proceso en Elixir así de simple:
spawn fn -> 1 + 2 end
Todo después de la spawn
y antes del end
se ejecutará en un nuevo proceso.
De hecho, cada solicitud en Phoenix se maneja en su propio proceso. Elixir utiliza el poder de Erlang VM para brindar una concurrencia confiable y eficiente "gratis".
Esto también convierte a Phoenix en una excelente opción para ejecutar servicios que usan WebSockets, ya que los WebSockets deben mantener una conexión abierta entre el cliente y el servidor (lo que significa que debe compilar su aplicación para que pueda manejar posiblemente miles de conexiones simultáneas).
Estos requisitos agregarían mucha complejidad a un proyecto creado en Ruby on Rails, pero Phoenix puede cumplir con estos requisitos de forma gratuita a través de Elixir.
Si desea usar WebSockets en su aplicación Phoenix, necesitará usar Channels. Es el equivalente de ActionCable
en Ruby on Rails, pero menos complejo de configurar porque no necesita ejecutar un servidor separado.
Phoenix hace que la creación de aplicaciones web modernas sea sencilla.
Si bien nos hemos centrado en gran medida en las diferencias, Phoenix tiene algunas cosas en común con Ruby on Rails.
Phoenix sigue aproximadamente el mismo patrón MVC que Ruby on Rails, por lo que averiguar qué código va a dónde no debería ser difícil ahora que conoce las principales diferencias. Phoenix también tiene generadores similares a Ruby on Rails para crear modelos, controladores, migraciones y más.
Después de aprender Elixir, se acercará lentamente a los niveles de productividad de Ruby on Rails una vez que se sienta más cómodo con Phoenix.
Las pocas veces que no me siento tan productivo es cuando me encuentro con un problema que se resolvió con una gema en Ruby y no puedo encontrar una biblioteca similar para Elixir. Afortunadamente, estos vacíos están siendo llenados lentamente por la creciente comunidad de Elixir.
Las diferencias en Phoenix ayudan a resolver gran parte del dolor que surgió con la gestión de proyectos complejos de Ruby on Rails. Si bien no resuelven todos los problemas, ayudan a empujarlo en la dirección correcta. Elegir un marco lento porque quieres productividad ya no es una excusa válida, Phoenix te permite tener ambos.