Les bases à connaître
Cette page explique les notions dont le reste de la documentation se sert, avec des exemples tirés du code de l'API. Pas besoin de tout retenir : reviens-y quand un mot t'arrête.
Elixir, le langage
Elixir est un langage fonctionnel qui tourne sur la machine virtuelle d'Erlang (la BEAM), conçue pour les systèmes qui ne doivent jamais s'arrêter, comme les centraux téléphoniques.
Les données ne changent jamais
Une valeur n'est jamais modifiée sur place : une fonction renvoie une nouvelle valeur. Il n'y a donc pas d'effet de bord caché : ce qu'une fonction reçoit, elle ne peut pas l'abîmer.
Le pattern matching
= ne veut pas dire « affecter » mais « faire correspondre un motif ». C'est l'outil le plus utilisé du projet. Par exemple, la règle qui dit que personne ne pointe pour un autre (authorization.ex) :
defp allowed?(%User{id: id}, :clock, id), do: trueLa variable id apparaît deux fois : la clause ne s'applique que si l'id de la personne connectée est égal à celui de la personne qui pointe. Toutes les autres combinaisons tombent dans une clause qui refuse.
Le pipe |>
a |> f(b) veut dire f(a, b). On écrit une suite de transformations dans l'ordre où on la lit :
first_day = today() |> Date.add(-1) |> Date.beginning_of_week() |> Date.add(-21)« Aujourd'hui, moins un jour, le lundi de cette semaine, moins trois semaines. »
with : une suite d'étapes qui peuvent échouer
Chaque ligne motif <- expression doit correspondre à son motif pour passer à la suivante. Dès qu'une étape renvoie autre chose (souvent {:error, raison}), le with s'arrête et renvoie cette valeur. C'est la forme de presque tous les contrôleurs :
with {:ok, user_id} <- parse_id(user_id),
:ok <- Authorization.authorize(conn.assigns.current_user, :clock, user_id),
{:ok, clock, result} <- Clocks.clock(user_id, params) do
render(conn, :show, clock: clock)
end{:ok, valeur} et {:error, raison}
Une fonction qui peut échouer ne lève pas d'exception : elle renvoie un tuple qui dit comment ça s'est passé. Celui qui l'appelle est obligé d'y penser.
Les processus et la supervision
Le serveur est un ensemble de petits processus isolés : la connexion à la base, le serveur web… Un superviseur les surveille et redémarre celui qui plante. C'est la philosophie « let it crash » : on ne cherche pas à prévoir chaque panne, on sait repartir proprement. La liste est dans lib/timemanager/application.ex.
Phoenix, le framework web
| Notion | Ce que c'est | Dans le projet |
|---|---|---|
| Endpoint | la porte d'entrée : tout ce que chaque requête traverse | lib/timemanager_web/endpoint.ex |
| Plug | une fonction qui reçoit la requête (conn) et la renvoie modifiée, ou l'arrête (halt) | Plugs.Authenticate vérifie le jeton |
| Router | l'aiguillage : quelle adresse va à quel contrôleur | lib/timemanager_web/router.ex |
| Pipeline | une suite de plugs appliquée à un groupe de routes | :api (JSON seulement), :authenticated |
| Contrôleur | reçoit la requête, appelle le métier, choisit la réponse | ClockController, UserController… |
| Vue JSON | choisit exactement les champs envoyés | ClockJSON, UserJSON… |
| Fallback | transforme toute erreur en réponse JSON | FallbackController |
Pourquoi une vue JSON, et pas le schéma tel quel ?
On n'envoie jamais un enregistrement de la base directement. La vue choisit chaque champ : un champ interne, comme l'empreinte du mot de passe d'un compte, ne peut donc jamais fuiter par oubli.
Ecto, l'accès à la base
| Notion | Ce que c'est | Exemple |
|---|---|---|
| Schéma | la forme d'une table en Elixir | Clocks.Clock pour la table clocks |
| Changeset | une modification vérifiée : ce qu'on accepte, les règles, les erreurs | CorrectionRequest.create_changeset/2 |
| Repo | ce qui parle vraiment à PostgreSQL | Repo.insert/1, Repo.transaction/1 |
| Migration | un fichier qui crée ou modifie une table, dans l'ordre | priv/repo/migrations/ |
| Requête | du SQL écrit en Elixir | from u in User, where: u.id == ^id |
Un changeset est la notion clé. Il garde la liste des changements et des erreurs. Si une règle échoue, rien n'est écrit, et l'erreur repart vers le client avec un code stable (voir les erreurs 422 dans Swagger).
Le contexte : le métier rangé par domaine
Un contexte est un module qui regroupe un domaine du métier et cache la base derrière des fonctions claires :
| Contexte | Domaine |
|---|---|
Accounts | comptes, mots de passe, liens envoyés par e-mail |
Auth.Sessions | sessions et rotation des jetons |
Authorization | le seul endroit où les droits sont décidés |
Clocks | les pointages d'arrivée et de départ |
Workingtime | les journées, les pauses, l'historique des modifications |
Teams | les équipes et leurs membres |
Corrections | les demandes de correction |
Absences | les jours d'absence |
WeekValidations | les semaines validées |
Notifications | la cloche et ses e-mails |
Reports | les totaux et les moyennes |
PersonalData | la copie de « Mes données » |
Un contrôleur n'écrit jamais de requête SQL : il demande au contexte, par exemple Clocks.clock(user_id, params).
Pour aller plus loin
- Elixir School : un cours progressif, en français.
- Guides de Phoenix et guides d'Ecto.