Découvrez Phoenix : un framework de type Rails pour les applications Web modernes sur Elixir

Publié: 2022-03-11

Le framework Phoenix a gagné en popularité à un rythme rapide, offrant la productivité de frameworks comme Ruby on Rails, tout en étant l'un des frameworks les plus rapides disponibles. Cela brise le mythe selon lequel il faut sacrifier les performances pour augmenter la productivité.

Alors, qu'est-ce que Phénix exactement ?

Phoenix est un framework Web construit avec le langage de programmation Elixir. Elixir, construit sur la machine virtuelle Erlang, est utilisé pour créer des systèmes distribués à faible latence et tolérants aux pannes, qui sont des qualités de plus en plus nécessaires des applications Web modernes. Vous pouvez en savoir plus sur Elixir à partir de cet article de blog ou de leur guide officiel.

Si vous êtes un développeur Ruby on Rails, vous devriez certainement vous intéresser à Phoenix en raison des gains de performances qu'il promet. Les développeurs d'autres frameworks peuvent également suivre pour voir comment Phoenix aborde le développement Web.

Découvrez Phoenix sur Elixir : un framework de type Rails pour les applications Web modernes

Dans cet article, nous apprendrons certaines des choses à Phoenix que vous devriez garder à l'esprit si vous venez du monde de Ruby on Rails.

Contrairement à Ruby, Elixir est un langage de programmation fonctionnel, ce qui est probablement la plus grande différence que vous pourriez avoir à gérer. Cependant, il existe certaines différences clés entre ces deux plates-formes que toute personne apprenant Phoenix devrait connaître afin de maximiser sa productivité.

Les conventions de nommage sont plus simples.

C'est petit, mais il est facile de se tromper si vous venez de Ruby on Rails.

A Phoenix, la convention est de tout écrire au singulier. Vous auriez donc un "UserController" au lieu d'un "UsersController" comme vous le feriez dans Ruby on Rails. Cela s'applique partout sauf lors de la dénomination des tables de base de données, où la convention est de nommer la table au pluriel.

Cela peut ne pas sembler un gros problème après avoir appris quand et où utiliser la forme plurielle dans Ruby on Rails, mais c'était un peu déroutant au début, quand j'apprenais à utiliser Ruby on Rails, et je suis sûr que cela en a confondu beaucoup d'autres aussi. Phoenix vous donne une chose de moins à vous soucier.

Le routage est plus facile à gérer.

Phoenix et Ruby on Rails sont très similaires en matière de routage. La principale différence réside dans la façon dont vous pouvez contrôler la manière dont une demande est traitée.

Dans Ruby on Rails (et d'autres applications Rack), cela se fait via un middleware, alors que dans Phoenix, cela se fait avec ce que l'on appelle des «plugs».

Les prises sont ce que vous utilisez pour traiter une connexion.

Par exemple, le middleware Rails::Rack::Logger enregistre une requête dans Rails, ce qui dans Phoenix serait fait avec le plug Plug.Logger . Fondamentalement, tout ce que vous pouvez faire dans le middleware Rack peut être fait à l'aide de plugs.

Voici un exemple de routeur à 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

Examinons d'abord les pipelines.

Ce sont des groupes de bouchons à travers lesquels la requête va transiter. Considérez-le comme une pile middleware. Ceux-ci peuvent être utilisés, par exemple, pour vérifier que la requête attend du HTML, récupérer la session et s'assurer que la requête est sécurisée. Tout cela se passe avant d'atteindre le contrôleur.

Étant donné que vous pouvez spécifier plusieurs pipelines, vous pouvez choisir les prises nécessaires pour des itinéraires spécifiques. Dans notre routeur, par exemple, nous avons un pipeline différent pour les requêtes de pages (HTML) et d'API (JSON).

Il n'est pas toujours judicieux d'utiliser exactement le même pipeline pour différents types de requêtes. Phoenix nous donne cette flexibilité.

Les vues et les modèles sont deux choses différentes.

Les vues dans Phoenix ne sont pas les mêmes que les vues dans Ruby on Rails.

Les vues de Phoenix sont chargées de rendre les modèles et de fournir des fonctions qui facilitent l'utilisation des données brutes pour les modèles. Une vue dans Phoenix ressemble le plus à un assistant dans Ruby on Rails, avec en plus le rendu du modèle.

Écrivons un exemple qui affiche "Hello, World!" dans le navigateur.

Rubis sur rails :

app/controllers/hello_controller.rb :

 class HelloController < ApplicationController def index end end

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

 <h1>Hello, World!</h1>

Phénix:

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/bonjour/index.html.eex :

 <h1>Hello, World!</h1>

Ces exemples affichent tous les deux "Hello, World!" dans le navigateur, mais présentent quelques différences essentielles.

Tout d'abord, vous devez indiquer explicitement le modèle que vous souhaitez rendre dans Phoenix contrairement à Ruby on Rails.

Ensuite, nous avons dû inclure une vue dans Phoenix qui se situe entre le contrôleur et le modèle.

Maintenant, vous vous demandez peut-être pourquoi nous avons besoin d'une vue si elle est vide ? La clé ici est que, sous le capot, Phoenix compile le modèle en une fonction à peu près égale au code ci-dessous :

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

Vous pouvez supprimer le modèle et utiliser la nouvelle fonction de rendu, et vous obtiendrez le même résultat. Ceci est utile lorsque vous souhaitez simplement renvoyer du texte ou même du JSON.

Les modèles ne sont que cela : des modèles de données.

Dans Phoenix, les modèles gèrent principalement la validation des données, leur schéma, leurs relations avec d'autres modèles et leur présentation.

Spécifier le schéma dans le modèle peut sembler étrange au premier abord, mais cela vous permet de créer facilement des champs "virtuels", qui sont des champs qui ne sont pas conservés dans la base de données. Prenons un exemple :

 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

Ici, nous définissons quatre champs dans la table « utilisateurs » : nom, e-mail, mot de passe et mot de passe_hash.

Peu de choses sont intéressantes ici, à part le champ "mot de passe" qui est défini comme "virtuel".

Cela nous permet de définir et d'obtenir ce champ sans enregistrer les modifications apportées à la base de données. C'est utile car nous aurions une logique pour convertir ce mot de passe en un hachage, que nous enregistrerions dans le champ "password_hash", puis l'enregistrerions dans la base de données.

Vous devez toujours créer une migration ; le schéma dans le modèle est nécessaire car les champs ne sont pas automatiquement chargés dans le modèle comme dans Ruby on Rails.

La différence entre Phoenix et Ruby on Rails est que le modèle ne gère pas la persistance dans la base de données. Ceci est géré par un module appelé "Repo" qui est configuré avec les informations de la base de données, comme illustré dans l'exemple ci-dessous :

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

Ce code est inclus dans les fichiers de configuration spécifiques à l'environnement, tels que config/dev.exs ou config/test.exs . Cela nous permet ensuite d'utiliser Repo pour effectuer des opérations de base de données telles que la création et la mise à jour.

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

Ceci est un exemple courant dans les contrôleurs de Phoenix.

Nous fournissons à un utilisateur un nom et un e-mail et Repo tente de créer un nouvel enregistrement dans la base de données. Nous pouvons ensuite, éventuellement, gérer la tentative réussie ou échouée comme indiqué dans l'exemple.

Pour mieux comprendre ce code, vous devez comprendre le pattern matching dans Elixir. La valeur renvoyée par la fonction est un tuple. La fonction renvoie un tuple de deux valeurs, un statut, puis le modèle ou un ensemble de modifications. Un ensemble de modifications est un moyen de suivre les modifications et de valider un modèle (les ensembles de modifications seront abordés dans la section suivante).

Le premier tuple, de haut en bas, qui correspond au modèle du tuple renvoyé par la fonction qui a tenté d'insérer l'utilisateur dans la base de données, exécutera sa fonction définie.

Nous aurions pu définir une variable pour le statut au lieu d'un atome (qui est essentiellement ce qu'est un symbole dans Ruby), mais nous ferions alors correspondre les tentatives réussies et échouées et utiliserions la même fonction pour les deux situations. En spécifiant à quel atome nous voulons correspondre, nous définissons une fonction spécifiquement pour ce statut.

Ruby on Rails a la fonctionnalité de persistance intégrée au modèle via ActiveRecord. Cela ajoute plus de responsabilité au modèle et peut parfois rendre le test d'un modèle plus complexe. Dans Phoenix, cela a été séparé d'une manière qui a du sens et évite de gonfler chaque modèle avec une logique de persistance.

Les ensembles de modifications permettent des règles de validation et de transformation claires.

Dans Ruby on Rails, la validation et la transformation des données peuvent être la source de bogues difficiles à trouver. En effet, il n'est pas immédiatement évident que les données sont transformées par des rappels tels que "before_create" et des validations.

Dans Phoenix, vous effectuez explicitement ces validations et transformations à l'aide d'ensembles de modifications. C'est l'une de mes fonctionnalités préférées à Phoenix.

Examinons un ensemble de modifications en en ajoutant un au modèle précédent :

 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

Ici, le changeset fait deux choses.

Tout d'abord, il appelle la fonction "cast" qui est une liste blanche de champs autorisés, similaire à "strong_parameters" dans Ruby on Rails, puis il valide que les champs "email" et "password" sont inclus, rendant le champ "name" optionnel. De cette façon, seuls les champs que vous autorisez peuvent être modifiés par les utilisateurs.

La bonne chose à propos de cette approche est que nous ne sommes pas limités à un seul ensemble de modifications. Nous pouvons créer plusieurs ensembles de modifications. Un ensemble de modifications pour l'enregistrement et un autre pour la mise à jour de l'utilisateur sont courants. Peut-être voulons-nous uniquement exiger le champ du mot de passe lors de l'enregistrement, mais pas lors de la mise à jour de l'utilisateur.

Comparez cette approche à ce qui se fait couramment dans Ruby on Rails, vous devrez spécifier que la validation ne doit être exécutée que sur "create". Cela rend parfois difficile dans Rails de comprendre ce que fait votre code une fois que vous avez un modèle complexe.

La fonctionnalité d'importation est simple, mais flexible.

Une grande partie des fonctionnalités d'une bibliothèque appelée "Ecto" est importée dans les modèles. Les modèles ont généralement cette ligne vers le haut :

 use HelloPhoenix.Web, :model

Le module "HelloPhoenix.Web" est situé dans "web/web.ex". Dans le module, il devrait y avoir une fonction appelée "model" comme suit :

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

Ici vous pouvez voir quels modules nous importons d'Ecto. Vous pouvez supprimer ou ajouter tous les autres modules que vous souhaitez ici et ils seront importés dans tous les modèles.

Il existe également des fonctions similaires telles que "vue" et "contrôleur" qui ont le même objectif pour les vues et les contrôleurs, respectivement.

Les mots-clés quote et use peuvent sembler déroutants. Pour cet exemple, vous pouvez considérer une citation comme exécutant directement ce code dans le contexte du module qui appelle cette fonction. Ce sera donc l'équivalent d'avoir écrit le code entre guillemets dans le module.

Le mot-clé use vous permet également d'exécuter du code dans le contexte où il est appelé. Il nécessite essentiellement le module spécifié, puis appelle la macro __using__ sur le module qui l'exécute dans le contexte où il a été appelé. Vous pouvez en savoir plus sur la citation et l'utilisation dans le guide officiel.

Cela vous aide vraiment à comprendre où se trouvent certaines fonctions dans le framework et aide à réduire le sentiment que le framework fait beaucoup de « magie ».

La simultanéité est au cœur.

La simultanéité est gratuite à Phoenix car c'est une caractéristique principale d'Elixir. Vous obtenez une application qui peut générer plusieurs processus et s'exécuter sur plusieurs cœurs sans vous soucier de la sécurité et de la fiabilité des threads.

Vous pouvez générer un nouveau processus dans Elixir simplement :

 spawn fn -> 1 + 2 end

Tout après le spawn et avant la end s'exécutera dans un nouveau processus.

En fait, chaque demande à Phoenix est traitée selon son propre processus. Elixir utilise la puissance de la machine virtuelle Erlang pour apporter une concurrence fiable et efficace "gratuitement".

Cela fait également de Phoenix un excellent choix pour exécuter des services qui utilisent WebSockets, car WebSockets doit maintenir une connexion ouverte entre le client et le serveur (ce qui signifie que vous devez créer votre application afin qu'elle puisse gérer éventuellement des milliers de connexions simultanées).

Ces exigences ajouteraient beaucoup de complexité à un projet construit sur Ruby on Rails, mais Phoenix peut répondre à ces exigences gratuitement via Elixir.

Si vous souhaitez utiliser WebSockets dans votre application Phoenix, vous devrez utiliser des canaux. C'est l'équivalent d' ActionCable dans Ruby on Rails, mais moins complexe à mettre en place car vous n'avez pas besoin d'exécuter un serveur séparé.

Phoenix facilite la création d'applications Web modernes.

Bien que nous nous soyons largement concentrés sur les différences, Phoenix a certaines choses en commun avec Ruby on Rails.

Phoenix suit à peu près le même modèle MVC que Ruby on Rails, donc déterminer quel code va où ne devrait pas être difficile maintenant que vous connaissez les principales différences. Phoenix dispose également de générateurs similaires à Ruby on Rails pour créer des modèles, des contrôleurs, des migrations, etc.

Après avoir appris Elixir, vous vous rapprocherez lentement des niveaux de productivité de Ruby on Rails une fois que vous serez plus à l'aise avec Phoenix.

Les rares fois où je ne me sens pas aussi productif, c'est quand je rencontre un problème qui a été résolu par une gemme dans Ruby, et je ne trouve pas de bibliothèque similaire pour Elixir. Heureusement, ces lacunes sont lentement comblées par la communauté croissante d'Elixir.

Les différences dans Phoenix aident à résoudre une grande partie des problèmes liés à la gestion de projets Ruby on Rails complexes. Bien qu'ils ne résolvent pas tous les problèmes, ils vous aident à vous orienter dans la bonne direction. Choisir un framework lent parce que vous voulez de la productivité n'est plus une excuse valable, Phoenix vous permet d'avoir les deux.