• Contenu
  • Bas de page
logo ouidoulogo ouidoulogo ouidoulogo ouidou
  • Qui sommes-nous ?
  • Offres
    • 💻 Applications métier
    • 🤝 Collaboration des équipes
    • 🛡️ Sécurisation et optimisation du système d’information
    • 🔗 Transformation numérique
  • Expertises
    • 🖥️ Développement logiciel
    • ♾️ DevSecOps
    • ⚙️ Intégration de logiciels et négoce de licences
      • Atlassian : Jira, Confluence, Bitbucket…
      • Plateforme monday.com
      • GitLab
      • SonarQube
    • 📚​ Logiciel de CRM et de gestion
    • 🎨 UX/UI design
    • 🌐 Accessibilité Numérique
    • 🗂️​ Démarches simplifiées
    • 📝 Formations Atlassian
  • Références
  • Carrières
    • 🧐 Pourquoi rejoindre Ouidou ?
    • ✍🏻 Nous rejoindre
    • 👨‍💻 Rencontrer nos collaborateurs
    • 🚀 Grandir chez Ouidou
  • RSE
  • Ressources
    • 🗞️ Actualités
    • 🔍 Articles techniques
    • 📖 Livres blancs
    • 🎙️ Interviews Clients
Nous contacter
✕
État des lieux des polices
État des lieux des polices
25 janvier 2018
Les aliases Git de l’extrême
Les aliases Git de l’extrême
15 février 2018
Ressources > Articles techniques > Elixir focus sur OTP Server

Elixir focus sur OTP Server

Article écrit par Nicolas Cavigneaux

Dans l’article d’introduction sur Elixir j’ai mentionné le fait qu’OTP est un ensemble d’outils incroyables fourni par Erlang et qu’il mérite à lui seul tout un ensemble d’article.

Commençons donc par le premier, l’écriture de processus serveur.

OTP, c’est quoi déjà ?

Dès qu’on commence à s’intéresser à Elixir ou Erlang, on est rapidement confronté à l’acronyme OTP, un concept qui semble être central.

OTP est l’acronyme pour « Open Telecom Platform ». Naturellement en voyant ça, on se demande à quoi ça va bien pouvoir nous servir dans notre développement quotidien. Il s’avère que le nom est trompeur, c’est en fait un ensemble d’outils ayant des applications bien plus généralistes que la téléphonie.

Sous ce nom se cache un vaste ensemble de bibliothèques facilitant le développement de systèmes distribués, concurrents et tolérant les pannes.

Lorsque vous utilisez Elixir, vous utilisez OTP sans même vous en rendre compte. Les outils qui constituent Elixir en font tous un usage intensif.

GenServer

Dans cet article, nous allons voir comment créer nos propres outils tirants partie d’OTP, particulièrement de la partie GenServer qui simplifie la mise en place de la partie serveur dans une relation client / serveur.

GenServer a pour vocation de simplifier la mise en place de processus qui pourront gérer un état, exécuter du code de manière asynchrone, etc. Nous pourrions très bien écrire ça à la main, et c’est d’ailleurs un bon exercice pour comprendre le fonctionnement interne, mais l’utilisation de GenServer nous met à disposition une interface standard incluant des comportements par défaut que nous n’aurons qu’à écraser pour obtenir le fonctionnement désiré.

L’utilisation de GenServer simplifie aussi la gestion de nos processus par un superviseur mais nous verrons ça en détail dans un prochain article.

Écriture d’un serveur OTP

Écrire un serveur OTP consiste finalement à écrire un module qui contiendra les callbacks dont nous avons besoin. La grande majorité des serveurs ont les mêmes besoins, c’est pourquoi GenServer norme les callbacks disponibles via une interface (behaviour).

Par exemple, lorsqu’une requête est envoyée au serveur, la fonction handle_call va être appelée dans notre module. Cet appel de fonction se fait en passant en paramètres le message, l’origine et l’état courant du serveur. Cette fonction devra répondre avec un tuple décrivant le type de réponse, la valeur de la réponse et l’état mis à jour.

Créons un serveur OTP simpliste et voyons comment tout cela fonctionne en pratique. Dans cet exemple, on se contentera d’avoir un serveur qui nous renvoie un nombre qui sera incrémenté à chaque appel.

Création de l’application

Pour créer ce serveur, nous allons créer un projet dédié à l’aide de Mix.

$ mix new incrementer  * creating README.md * creating .gitignore * creating mix.exs * creating config * creating config/config.exs * creating lib * creating lib/incrementer.ex * creating test * creating test/test_helper.exs * creating test/incrementer_test.exs  Your Mix project was created successfully. You can use "mix" to compile it, test it, and more:      cd incrementer     mix test  Run "mix help" for more commands. 

Si vous avez lu l’article précédent, vous vous souvenez sûrement que Mix est un outil central dans la gestion de projets. Ici la commande new a créé une structure prête à l’emploi. Un README est disponible ainsi qu’un .gitignore adapté à un projet Elixir.

Nous avons également un répertoire dédié à la configuration de l’application, un répertoire prêt à recevoir les tests et le plus important dans le cadre de cet article, le répertoire lib qui va contenir notre code applicatif.

Mix a généré un fichier lib/incrementer.ex pour nous donner une base de travail :

## lib/incrementer.ex  defmodule Incrementer do   @moduledoc """   Documentation for Incrementer.   """    @doc """   Hello world.    ## Examples        iex> Incrementer.hello       :world    """   def hello do     :world   end end 

Nous n’allons pas en garder grand-chose, ce fichier auto-généré fait surtout office d’exemple de structuration et de documentation du code.

Première version minimaliste

Voici à quoi ressemble notre version modifiée :

defmodule Incrementer do   use GenServer    def init(number) do     {:ok, number}   end    def handle_call(:next, _from, current) do     {:reply, current, current + 1}   end end 

La toute première chose qu’on note c’est l’utilisation de GenServer. Pour faire simple, le mot-clé use permet de requérir le module spécifié puis d’appeler un callback dessus pour qu’il puisse injecter du code dans notre module. C’est de cette façon que GenServer nous fournit une implémentation par défaut.

On a ensuite déclaré une fonction init qui a pour vocation à être appelée lorsque notre serveur sera démarré via GenServer.start_link/2. Son but est simple, initialiser l’état du serveur avec le nombre qui nous sera passé en paramètre au lancement.

La seconde fonction handle_call est appelée lorsque le serveur reçoit une requête. Il est possible, et c’est souvent le cas, d’avoir plusieurs fonctions handle_call qui répondent à différents messages. C’est une fois encore un exemple de pattern matching.

Comme dit plus haut, les paramètres de cette fonction sont dans l’ordre :

  • le nom du message
  • l’émetteur (que nous ignorons ici grâce au _)
  • l’état courant du serveur

Cette fonction va répondre (atome :reply) avec l’état courant (le nombre courant) et retourner le nouvel état (le nombre incrémenté) pour l’appel suivant.

C’est le moment de tester notre petit serveur dans une console interactive :

iex -S mix  iex> {:ok, pid} = GenServer.start_link(Incrementer, 10) {:ok, #PID<0.142.0>} iex> GenServer.call(pid, :next) 10 iex> GenServer.call(pid, :next) 11 iex> GenServer.call(pid, :next) 12 iex> GenServer.call(pid, :next) 13 

Simple et efficace.

Il est à noter qu’un appel à call signifie que le client attend une réponse en retour et que c’est un appel synchrone.

Si vous souhaitez gérer des appels clients pour lesquels aucune réponse n’est attendue il faudra passer par cast. Dans ce cas le serveur devra implémenter une fonction handle_cast adéquate.

Gestion des appels asynchrones

On pourrait par exemple écrire une fonction qui permet de redéfinir le compteur courant :

def handle_cast({:set, number}, _state) do   {:noreply, number} end 

Cette fois, notre fonction n’attend que deux paramètres, la requête et l’état.

Vous aurez noté qu’ici on a choisit de passer un tuple plutôt qu’un simple atome. Nous avons fait ça pour pouvoir, lors de l’appel au serveur, passer à la fois un nom de message mais également une valeur associée. C’est en passant par un tuple que vous pouvez passer plus d’un argument lors de votre requête au serveur.

Notre fonction n’ayant à retourner quoi que ce soit au client, nous répondons avec un tuple de type :noreply en s’assurant de passer le nouvel état du serveur.

Essayons dans IEx :

iex> {:ok, pid} = GenServer.start_link(Incrementer, 10) {:ok, #PID<0.174.0>}  iex> GenServer.call(pid, :next) 10 iex> GenServer.call(pid, :next) 11 iex> GenServer.call(pid, :next) 12 iex> GenServer.cast(pid, {:set, 100}) :ok iex> GenServer.call(pid, :next) 100 

Nommer un processus

Notre exemple est très simple, utilisé uniquement localement et à travers une seule application, avec une seule instance. Ça ne pose donc aucun problème à l’utilisation.

Qu’advient-il quand on lance une multitude de processus ? La gestion via les PIDs devient fastidieuse et cryptique. Heureusement, il est possible de nommer les processus de manière unique sur un nœud donné.

On pourra ensuite référencer un processus via son nom plutôt que par son PID.

Pour nommer un processus, il suffit de le lancer en passant l’argument name à start_link :

iex> GenServer.start_link(Incrementer, 10, name: :inc) {:ok, #PID<0.182.0>} iex> GenServer.call(:inc, :next) 10 iex> :sys.get_status(:inc) {:status, #PID<0.182.0>, {:module, :gen_server},  [["$ancestors": [#PID<0.140.0>, #PID<0.57.0>],    "$initial_call": {Incrementer, :init, 1}], :running, #PID<0.140.0>, [],   [header: 'Status for generic server inc',    data: [{'Status', :running}, {'Parent', #PID<0.140.0>},     {'Logged events', []}], data: [{'State', 11}]]]} 

Encapsuler la logique de gestion du serveur

Notre code est tout à fait fonctionnel. Pourtant, quand on écrit un serveur de ce type, on préfère généralement fournir une interface publique à l’utilisateur pour gérer le lancement du serveur ainsi que les appels. On souhaite éviter les appels directs au module GenServer ce qui simplifie largement sa compréhension et rend notre module plus naturel pour l’utilisateur final.

Améliorons donc notre module serveur pour proposer une interface plus sexy et complète qui évitera les appels à d’autre module à nos utilisateurs.

Nous allons ajouter trois fonctions start_link, next et set qui vont encapsuler les appels à GenServer.

Voici à quoi ressemble notre fichier modifié :

defmodule Incrementer do   use GenServer    def init(number) do     {:ok, number}   end    def handle_call(:next, _from, current) do     {:reply, current, current + 1}   end    def handle_cast({:set, number}, _state) do     {:noreply, number}   end    def start_link(number) do     GenServer.start_link(__MODULE__, number, name: __MODULE__)   end    def next do     GenServer.call(__MODULE__, :next)   end    def set(number) do     GenServer.cast(__MODULE__, {:set, number})   end end  

Essayons cette nouvelle version dans IEx :

iex> Incrementer.start_link(10) {:ok, #PID<0.199.0>} iex> Incrementer.next() 10 iex> Incrementer.next() 11 iex> Incrementer.set(50) :ok iex> Incrementer.next() 50 

On a maintenant une version qui semble plus aboutie et plus naturelle à utiliser. Notre module inclut tout le nécessaire à sa manipulation et ne nécessite plus de connaître son fonctionnement interne pour pouvoir l’utiliser.

Évidemment on aurait pu aller encore plus loin en séparant le code de l’interface, du métier et de l’implémentation serveur dans différents modules pour éviter le couplage, faciliter l’écriture des tests et éviter de se retrouver avec un module géant qui fait tout si les fonctionnalités venaient à se multiplier.

Et pour la suite ?

Seriez-vous intéressés par un article expliquant le fonctionnement interne d’un GenServer dans lequel nous écririons le nôtre from scratch ? Si oui, faites-le-moi savoir dans les commentaires.

Il reste encore beaucoup de chose à voir concernant OTP, les superviseurs, la gestion des bases de données, la gestion des releases, le scaling automatique, …

J’espère que cet article vous aura éclairé si vous ne connaissiez pas OTP et qu’il vous aura donné envie de creuser le sujet.

Ressources

  • Site officiel d’Elixir (en)
  • Site officiel d’Erlang (en)
  • GenServer behaviour module (en)
  • Erlang/OTP — Open Telecom Platform (en)

À lire aussi

Fresque numérique miniature image
16 avril 2025

Fresque du Numérique

Lire la suite

intelligence artificielle Ouicommit miniature image
17 mars 2025

Ouicommit – L’intelligence artificielle en entreprise, on y est ! 

Lire la suite

Image miniature Hackathon Women in Tech
13 mars 2025

Hackathon Women in Tech :  un engagement pour une tech plus inclusive 

Lire la suite

image miniature les nouveautés Atlassian
26 février 2025

Les nouveautés Atlassian en 2025

Lire la suite

Articles associés

Fresque numérique miniature image
16 avril 2025

Fresque du Numérique


Lire la suite
intelligence artificielle Ouicommit miniature image
17 mars 2025

Ouicommit – L’intelligence artificielle en entreprise, on y est ! 


Lire la suite
Image miniature Hackathon Women in Tech
13 mars 2025

Hackathon Women in Tech :  un engagement pour une tech plus inclusive 


Lire la suite

À propos

  • Qui sommes-nous ?
  • Références
  • RSE
  • Ressources

Offres

  • Applications métier
  • Collaboration des équipes
  • Sécurisation et optimisation du système d’information
  • Transformation numérique

Expertises

  • Développement logiciel
  • DevSecOps
  • Intégration de logiciels et négoce de licences
  • Logiciel de CRM et de gestion
  • UX/UI design
  • Accessibilité Numérique
  • Démarches simplifiées
  • Formations Atlassian

Carrières

  • Pourquoi rejoindre Ouidou ?
  • Nous rejoindre
  • Rencontrer nos collaborateurs
  • Grandir chez Ouidou

SIEGE SOCIAL
70-74 boulevard Garibaldi, 75015 Paris

Ouidou Nord
165 Avenue de Bretagne, 59000 Lille

Ouidou Rhône-Alpes
4 place Amédée Bonnet, 69002 Lyon

Ouidou Grand-Ouest
2 rue Crucy, 44000 Nantes

Ouidou Grand-Est
7 cour des Cigarières, 67000 Strasbourg

  • Linkedin Ouidou
  • GitHub Ouidou
  • Youtube Ouidou
© 2024 Ouidou | Tous droits réservés | Plan du site | Mentions légales | Déclaration d'accessibilité
    Nous contacter