Une introduction aux applications Elixir

Dans mes articles précédents, nous avons discuté de divers termes Elixir et écrit une quantité considérable de code. Cependant, nous n’avons pas discuté de la façon de structurer et d’organiser votre code afin qu’il soit facile à gérer et à publier.. 

Les applications sont très courantes pour Erlang et Elixir et sont utilisées pour construire des composants réutilisables qui se comportent comme des unités autonomes. Une application peut avoir ses propres arborescence de supervision et configuration, et elle peut compter sur d’autres applications disponibles localement ou sur un serveur distant. Globalement, travailler avec des applications n’est pas si complexe et les personnes venues du monde de Ruby, par exemple, y trouveront de nombreux concepts familiers..

Dans cet article, vous apprendrez quelles sont les applications, comment elles peuvent être créées, comment spécifier et installer des dépendances et comment fournir des valeurs d'environnement. À la fin de l'article, nous allons pratiquer et créer une calculatrice basée sur le Web.. 

J'utiliserai Elixir 1.5 dans cet article (il a été publié il y a quelques mois), mais tous les concepts expliqués devraient également s'appliquer à la version 1.4.

Applications?

Certains pourraient faire valoir que le terme "application" n'est pas très approprié car, dans Erlang et Elixir, il désigne en fait un composant ou un code comportant de nombreuses dépendances. L'application elle-même peut être utilisée comme dépendance car dans le monde Ruby, nous l'appellerions un "joyau".

Dans l’ensemble, les applications sont très courantes dans Elixir et vous permettent de fabriquer des composants réutilisables tout en offrant une gestion simple des dépendances. Ils consistent en un ou plusieurs modules avec zéro ou plusieurs dépendances et sont décrits par le fichier de ressources de l'application. Ce fichier contient des informations sur le nom de l'application, sa version, ses modules, ses dépendances et d'autres éléments. Vous pouvez créer le fichier de ressources manuellement, mais il est beaucoup plus facile de le faire avec l'outil de mixage qui prépare également une structure de dossiers correcte pour vous.. 

Voyons maintenant comment créer une nouvelle application Elixir!

Nouvelle application

Pour créer une nouvelle application, il suffit d’exécuter la commande suivante:

mélanger nouveau nom_app

Nous pouvons également fournir le --souper drapeau pour créer un superviseur vide pour nous. Créons une nouvelle application appelée Échantillon par ici:

mélanger un nouvel échantillon

Cette commande va créer un échantillon répertoire pour vous avec une poignée de fichiers et de dossiers à l'intérieur. Laissez-moi vous guider rapidement à travers eux:

  • config dossier contient un seul fichier config.exs comme vous pouvez le deviner, cela fournit une configuration pour l’application. Au départ, il contient des commentaires utiles, mais pas de configuration. Notez, en passant, que la configuration fournie dans ce fichier est limitée à l'application elle-même. Si vous chargez l’application en tant que dépendance, sa config.exs sera effectivement ignoré.
  • lib est le dossier principal de l'application qui contient un sample.ex fichier et un échantillon dossier avec un application.ex fichier. application.ex définit un module de rappel avec un début / 2 fonction qui crée un superviseur vide.
  • tester est le dossier contenant les tests automatisés pour l'application. Nous ne discuterons pas des tests automatisés dans cet article.
  • mix.exs est le fichier qui contient toutes les informations nécessaires sur l'application. Il y a plusieurs fonctions ici. À l'intérieur de projet fonction, vous fournissez le nom de l'application (en tant qu'atome), sa version et son environnement. le application Cette fonction contient des informations sur le rappel du module d'application et les dépendances d'exécution. Dans notre cas, Exemple.Application est défini comme le rappel du module d’application (qui peut être traité comme le point d’entrée principal) et doit définir début / 2 une fonction. Comme déjà mentionné ci-dessus, cette fonction a déjà été créée pour nous par le mélanger outil. Enfin, le deps fonction liste les dépendances au moment de la construction.

Les dépendances

Il est très important de faire la distinction entre les dépendances d'exécution et de construction. Les dépendances au moment de la construction sont chargées par le mélanger outil lors de la compilation et sont essentiellement compilés dans votre application. 

Ils peuvent être récupérés depuis un service tel que GitHub, par exemple, ou depuis le site Web hex.pm, un gestionnaire de paquets externe stockant des milliers de composants pour Elixir et Erlang. Les dépendances d'exécution sont démarrées avant le démarrage de l'application. Ils sont déjà compilés et disponibles pour nous.

Il y a deux façons de spécifier des dépendances au moment de la construction dans un mix.exs fichier. Si vous souhaitez utiliser une application du site Web hex.pm, dites simplement:

: dependency_name, "~> 0.0.1"

Le premier argument est toujours un atome représentant le nom de l'application. Le second est l'exigence, une version que vous souhaitez utiliser. Elle est analysée par le module Version. Dans cet exemple, ~> signifie que nous souhaitons télécharger au moins la version 0.0.1 ou plus haut mais moins que 0.1.0. Si on dit ~> 1.0, cela signifie que nous aimerions utiliser la version supérieure ou égale à 1,0 mais moins que 2.0. Il y a aussi des opérateurs comme ==, >, <, > =, et <= disponible.

Il est également possible de spécifier directement un : git ou un :chemin option:

: gettext, git: "https://github.com/elixir-lang/gettext.git", balise: "0.1" : local_dependency, chemin: "path / to / local_dependency"

Il y a aussi : github raccourci qui nous permet de ne fournir que le nom du propriétaire et un repo:

: gettext, github: "elixir-lang / gettext"

Pour télécharger et compiler toutes les dépendances, exécutez:

mélanger deps.get

Cela installera un client Hex si vous n'en avez pas et ensuite vérifier si l'une des dépendances doit être mise à jour. Par exemple, vous pouvez spécifier Poison-une solution pour analyser JSON-en tant que dépendance, comme ceci:

 defp deps : poison, "~> 3.1"] end

Puis lancez:

mélanger deps.get

Vous verrez une sortie similaire:

Exécution de la résolution de dépendance… Résolution de dépendance terminée: poison 3.1.0 * Récupération de poison (paquet Hex) Vérification du paquet (https://repo.hex.pm/tarballs/poison-3.1.0.tar) Paquet récupéré

Poison est maintenant compilé et disponible sur votre PC. De plus, un mix.lock Le fichier sera créé automatiquement. Ce fichier fournit les versions exactes des dépendances à utiliser lors du démarrage de l'application. 

Pour en savoir plus sur les dépendances, exécutez la commande suivante:

mélanger les dépanneurs

Comportement encore

Les applications sont des comportements, tout comme GenServer et les superviseurs, dont nous avons parlé dans les articles précédents. Comme je l'ai déjà mentionné ci-dessus, nous fournissons un module de rappel à l'intérieur du mix.exs déposer de la manière suivante:

 def application do [mod: Sample.Application, []] end

Exemple.Application est le nom du module, alors que [] peut contenir une liste d'arguments à transmettre au début / 2 une fonction. le début / 2 la fonction doit être implémentée pour que l'application puisse démarrer correctement.

le application.ex contient le module de rappel qui ressemble à ceci:

defmodule Sample.Application utilise Application def start (_type, _args) enfants = [] opts = [stratégie:: un_pour_one, nom: Sample.Supervisor] Supervisor.start_link (enfants, opts) end end

le début / 2 la fonction doit soit retourner : ok, pid (avec un état optionnel comme troisième élément) ou : erreur, raison.

Une autre chose à noter est que les applications n’ont pas vraiment besoin du module de rappel. Cela signifie que la fonction d'application à l'intérieur du mix.exs Le fichier peut devenir vraiment minimaliste:

def application do [] end

Ces applications sont appelées applications de la bibliothèque. Ils n'ont pas d'arborescence de supervision mais peuvent quand même être utilisés comme dépendances par d'autres applications. Un exemple d’application de bibliothèque serait Poison, que nous avons spécifié en tant que dépendance dans la section précédente..

Lancer une application

Le moyen le plus simple de démarrer votre application consiste à exécuter la commande suivante:

iex -S mix

Vous verrez une sortie similaire à celle-ci:

Compilation de 2 fichiers (.ex), exemple d'application générée

UNE _construire répertoire sera créé à l'intérieur du échantillon dossier. Il contiendra .faisceau fichiers ainsi que d'autres fichiers et dossiers.

Si vous ne souhaitez pas démarrer un shell Elixir, une autre option consiste à exécuter:

course à pied

Le problème, cependant, est que l’application s’arrêtera dès que le début fonction termine son travail. Par conséquent, vous pouvez fournir le --no-stop clé pour garder l'application en marche aussi longtemps que nécessaire:

mélanger - sans arrêt

La même chose peut être obtenue en utilisant le élixir commander:

elixir -S mix run --no-stop

Notez cependant que l'application s'arrête dès que vous fermez le terminal où cette commande a été exécutée. Cela peut être évité en démarrant votre application en mode détaché: 

elixir -S mix run --no-halte - détaché

Environnement d'application

Parfois, vous voudrez peut-être que l'utilisateur d'une application définisse un paramètre avant que l'application ne soit réellement démarrée. Ceci est utile lorsque, par exemple, l'utilisateur doit pouvoir contrôler le port d'écoute d'un serveur Web. De tels paramètres peuvent être spécifiés dans l'environnement de l'application qui est un simple stockage clé-valeur en mémoire.. 

Pour lire certains paramètres, utilisez le fetch_env / 2 fonction qui accepte une application et une clé:

Application.fetch_env (: sample,: some_key) 

Si la clé ne peut pas être trouvée, un :Erreur l'atome est retourné. Il y a aussi un fetch_env! / 2 fonction qui génère une erreur à la place et get_env / 3 qui peut fournir une valeur par défaut.

Pour stocker un paramètre, utilisez put_env / 4:

Application.put_env (: exemple,: clé,: valeur)

La quatrième valeur contient des options et ne doit pas nécessairement être définie.

Enfin, pour supprimer une clé, utilisez le delete_env / 3 une fonction:

Application.delete_env (: sample,: key)

Comment fournissons-nous une valeur pour l'environnement lorsque vous démarrez une application? Eh bien, ces paramètres sont définis à l'aide du --erl clé de la manière suivante:

iex --erl "-sample key value" -S mix

Vous pouvez alors facilement récupérer la valeur:

Application.get_env: sample,: key # =>: value

Que se passe-t-il si un utilisateur oublie de spécifier un paramètre lors du démarrage de l'application? Eh bien, il est fort probable que nous devions fournir une valeur par défaut pour de tels cas. Vous pouvez faire cela à deux endroits: à l’intérieur du config.exs ou à l'intérieur du mix.exs fichier.

La première option est la préférée car config.exs est le fichier destiné à stocker diverses options de configuration. Si votre application a beaucoup de paramètres d’environnement, vous devez absolument vous en tenir à config.exs:

utiliser Mix.Config config: sample, key:: value

Cependant, pour une application plus petite, il est acceptable de fournir des valeurs d’environnement directement mix.exs en peaufinant la fonction d'application:

 def application do [extra_applications: [: logger], mod: Sample.Application, [], env: [# <==== key: :value ] ] end

Exemple: Création d'un serveur de calcul Web

Bon, pour voir les applications en action, modifions l'exemple déjà traité dans les articles sur GenServer et Supervisors. Il s’agit d’une calculatrice simple qui permet aux utilisateurs d’effectuer diverses opérations mathématiques et d’obtenir le résultat assez facilement.. 

Ce que je veux faire est de rendre cette calculatrice basée sur le Web, afin que nous puissions envoyer des demandes POST pour effectuer des calculs et une demande GET pour saisir le résultat..

Créer un nouveau lib / calc_server.ex fichier avec le contenu suivant:

defmodule Sample.CalcServer utilise GenServer def start_link (valeur_initial) do GenServer.start_link (__ MODULE__, valeur_initial, nom: __MODULE__) end def (valeur_initial) lorsque is_number (valeur_initial) fait : ok, valeur_initial et initialise : stop, "La valeur doit être un entier!" end def add (numéro) genServer.cast (__ MODULE__, : add, nombre) end def résultat do GenServer.call (__ MODULE__,: result) end def handle_call (: result, _, state) do : reply, state, state end def handle_cast (operation, state) do case operation do : add, number -> : noreply, state + number _ -> : stop, "non implémenté", état end fin def terminate (_reason, _state) do IO.puts "Le serveur s'est arrêté" fin fin

Nous ajouterons uniquement le support pour le ajouter opération. Toutes les autres opérations mathématiques peuvent être introduites de la même manière, je ne les énumérerai donc pas ici pour rendre le code plus compact..

le CalcServer utilise GenServer, alors nous obtenons child_spec automatiquement et peut le démarrer à partir de la fonction de rappel comme ceci:

 def start (_type, _args) do children = [Sample.CalcServer, 0] opts = [stratégie:: un_pour_one, nom: Sample.Supervisor] Supervisor.start_link (enfants, opts) end

0 voici le résultat initial. Ce doit être un nombre, sinon CalcServer se terminera immédiatement.

Maintenant, la question est de savoir comment ajouter du support Web? Pour cela, nous aurons besoin de deux dépendances tierces: Plug, qui servira de bibliothèque d'abstraction, et Cowboy, qui agira comme un serveur Web. Bien sûr, nous devons spécifier ces dépendances à l’intérieur du mix.exs fichier:

 defp deps [: cowboy, "~> 1.1", : plug, "~> 1.4"] end

Nous pouvons maintenant démarrer l'application Plug sous notre propre arbre de supervision. Ajustez la fonction de démarrage comme ceci:

 def start (_type, _args) do children = [Plug.Adapters.Cowboy.child_spec (: http, Sample.Router, [], [port: Application.fetch_env! (: sample,: port)]), Sample.CalcServer , 0] #… end

Ici nous fournissons child_spec et mise Sample.Router pour répondre aux demandes. Ce module sera créé dans un instant. Ce que je n’aime pas dans cette configuration, c’est que le numéro de port est codé en dur, ce qui n’est pas très pratique. Il se peut que je veuille le modifier au démarrage de l'application, stockons-le plutôt dans l'environnement:

Plug.Adapters.Cowboy.child_spec (: http, Sample.Router, [], [port: Application.fetch_env! (: Sample,: port)])

Maintenant, fournissez la valeur de port par défaut à l’intérieur du config.exs fichier:

config: échantillon, port: 8088

Génial! 

Qu'en est-il du routeur? Créer un nouveau lib / router.ex fichier avec le contenu suivant:

defmodule Sample.Router utilise Plug.Router plug: correspond à plug: dispatch end

Nous devons maintenant définir quelques itinéraires pour effectuer l’ajout et extraire le résultat:

 get "/ result" do conn |> ok (to_string (Sample.CalcServer.result)) end post "/ add" do fetch_number (conn) |> Sample.CalcServer.add conn |> ok end

Nous utilisons obtenir et poster macros pour définir le /résultat et /ajouter itinéraires. Ces macros vont définir le Connecticut objet pour nous. 

D'accord et numéro de récupération sont des fonctions privées définies de la manière suivante:

 defp numéro_fetch (conn) do Plug.Conn.fetch_query_params (conn) .params ["numéro"] |> String.to_integer end defp ok (conn, données \\ "OK") ne pas send_resp conn, 200, data end

fetch_query_params / 2 renvoie un objet avec tous les paramètres de la requête. Nous ne sommes intéressés que par le numéro que l'utilisateur nous a envoyé. Tous les paramètres sont initialement des chaînes, nous devons donc le convertir en entier.

send_resp / 3 envoie une réponse au client avec le code d'état fourni et un corps. Nous n'effectuerons aucune vérification d'erreur ici, donc le code sera toujours 200, ce qui signifie que tout va bien.

Et ça y est! Maintenant, vous pouvez lancer l’application de l’une des façons énumérées ci-dessus (par exemple, en tapant iex -S mix) et utiliser le boucle outil pour effectuer les demandes:

curl http: // localhost: 8088 / result # => 0 curl http: // localhost: 8088 / add? number = 1 -X POST # => OK curl http: // localhost: 8088 / result # => 1

Conclusion

Dans cet article, nous avons discuté des applications Elixir et de leur objectif. Vous avez appris à créer des applications, à fournir divers types d’informations et à répertorier les dépendances à l’intérieur du mix.exs fichier. Vous avez également vu comment stocker la configuration dans l'environnement de l'application et appris différentes manières de démarrer votre application. Enfin, nous avons vu des applications en action et créé un calculateur simple basé sur le Web..

N'oubliez pas que le site Web hex.pm répertorie plusieurs centaines d'applications tierces prêtes à être utilisées dans vos projets. Assurez-vous donc de parcourir le catalogue et de choisir la solution qui vous convient.! 

J'espère que vous avez trouvé cet article utile et intéressant. Je vous remercie de rester avec moi et jusqu'à la prochaine fois.