In mijn vorige artikelen hebben we verschillende Elixir-termen besproken en een flinke hoeveelheid code geschreven. Wat we echter niet hebben besproken, is hoe je je code kunt structureren en ordenen, zodat deze gemakkelijk kan worden onderhouden en vrijgegeven.
Toepassingen zijn heel gebruikelijk voor Erlang en Elixir en worden gebruikt om herbruikbare componenten te bouwen die zich gedragen als zelfstandige eenheden. Eén applicatie kan zijn eigen supervisiestructuur en configuratie hebben en kan vertrouwen op andere applicaties die lokaal of op een externe server beschikbaar zijn. Al met al is het werken met applicaties niet zo ingewikkeld, en mensen die bijvoorbeeld uit de wereld van Ruby komen, zullen veel vertrouwde concepten vinden.
In dit artikel leert u welke toepassingen zijn, hoe ze kunnen worden gemaakt, hoe afhankelijkheden kunnen worden gespecificeerd en geïnstalleerd en hoe u omgevingswaarden kunt opgeven. Aan het einde van het artikel zullen we wat oefenen en een webgebaseerde rekenmachine maken.
Ik gebruik Elixir 1.5 in dit artikel (het is een paar maanden geleden uitgebracht), maar alle toegelichte concepten moeten ook van toepassing zijn op versie 1.4.
Sommigen beweren misschien dat de term "applicatie" niet erg geschikt is omdat het in Erlang en Elixir eigenlijk een component of een code met een aantal afhankelijkheden betekent. De applicatie zelf kan ook als afhankelijkheid worden gebruikt - in Ruby-wereld zouden we het een "edelsteen" noemen.
Al met al zijn applicaties heel gebruikelijk in Elixir en kunt u herbruikbare componenten vervaardigen, terwijl u ook eenvoudig afhankelijkheidsbeheer kunt bieden. Ze bestaan uit een of meerdere modules met nul of meer afhankelijkheden en worden beschreven door het toepassingsresourcebestand. Dit bestand bevat informatie over de naam van de toepassing, de versie, de modules, afhankelijkheden en een aantal andere zaken. U kunt het bronbestand handmatig maken, maar het is veel gemakkelijker om dit te doen met de mix-tool die ook een correcte mappenstructuur voor u zal voorbereiden.
Laten we dus eens kijken hoe we een nieuwe Elixir-toepassing kunnen maken!
Als u een nieuwe toepassing wilt maken, hoeft u alleen maar de volgende opdracht uit te voeren:
meng nieuwe app_name
We kunnen ook de --sup
vlag om een lege supervisor voor ons te creëren. Laten we een nieuwe applicatie maken genaamd Monster
op deze manier:
meng nieuwe sample --sup
Met deze opdracht maakt u een monster map voor u met een handvol bestanden en mappen erin. Laat me je snel door ze leiden:
start / 2
functie die een lege supervisor creëert.project
functie, geeft u de naam van de app (als een atoom), versie en omgeving op. De toepassing
functie bevat informatie over de afhankelijkheden van de callback en runtime van de toepassingsmodule. In ons geval, Sample.Application
wordt ingesteld als de callback van de toepassingsmodule (die kan worden behandeld als het hoofdinvoerpunt) en moet a definiëren start / 2
functie. Zoals hierboven al vermeld, was deze functie al voor ons gemaakt door de mengen
tool. Ten slotte, de deps
functie lijsten bouwtijd afhankelijkheden.Het is heel belangrijk om een onderscheid te maken tussen runtime en build-time afhankelijkheden. Bouwtijd afhankelijkheden worden geladen door de mengen
tool tijdens de compilatie en zijn in principe gecompileerd in uw applicatie.
Ze kunnen worden opgehaald van een service zoals GitHub, of van de hex.pm website, een externe pakketbeheerder die duizenden componenten voor Elixir en Erlang opslaat. Runtime-afhankelijkheden worden gestart voordat de toepassing start. Ze zijn al gecompileerd en beschikbaar voor ons.
Er zijn een aantal manieren om afhankelijkheden in de build-time op te geven in a mix.exs het dossier. Als u een toepassing van de hex.pm website wilt gebruiken, hoeft u alleen maar te zeggen:
: afhankelijkheidsnaam, "~> 0.0.1"
Het eerste argument is altijd een atoom dat de naam van de toepassing vertegenwoordigt. De tweede is de vereiste, een versie die u wilt gebruiken - deze wordt geparseerd door de module Versie. In dit voorbeeld, ~>
betekent dat we tenminste de versie willen downloaden 0.0.1
of hoger maar minder dan 0.1.0
. Als we het zeggen ~> 1.0
, dit betekent dat we de versie groter dan of gelijk aan willen gebruiken 1.0
maar minder dan 2.0
. Er zijn ook operatoren zoals ==
, >
, <
, > =
, en <=
beschikbaar.
Het is ook mogelijk om direct een te specificeren : git
of a :pad
keuze:
: gettext, git: "https://github.com/elixir-lang/gettext.git", tag: "0.1" : local_dependency, path: "path / to / local_dependency"
Er is ook een : github
snelkoppeling waarmee we alleen de naam van de eigenaar en een repo kunnen opgeven:
: gettext, github: "elixir-lang / gettext"
Ga als volgt te werk om alle afhankelijkheden te downloaden en te compileren:
mix deps.get
Hiermee installeert u een Hex-client als u er geen hebt en controleert u vervolgens of een van de afhankelijkheden moet worden bijgewerkt. U kunt bijvoorbeeld Poison-een oplossing voor het parseren van JSON-als een afhankelijkheid als deze specificeren:
defp-deps do [: poison, "~> 3.1"] eindigen
Voer dan uit:
mix deps.get
Je ziet een vergelijkbare output:
Afhankelijkheidsresolutie uitvoeren ... Afhankelijkheidsresolutie voltooid: vergif 3.1.0 * Vergif ophalen (Hex-pakket) Pakket controleren (https://repo.hex.pm/tarballs/poison-3.1.0.tar) Pakket opgehaald
Poison is nu gecompileerd en beschikbaar op uw pc. Wat meer is, a mix.lock bestand wordt automatisch aangemaakt. Dit bestand biedt de exacte versies van de afhankelijkheden die moeten worden gebruikt wanneer de toepassing wordt opgestart.
Voer de volgende opdracht uit om meer te weten te komen over afhankelijkheden:
mix help-deps
Toepassingen zijn gedragingen, net zoals GenServer en supervisors, waar we het in vorige artikelen over hadden. Zoals ik hierboven al vermeldde, bieden we een callback-module in de mix.exs bestand op de volgende manier:
def applicatie do [mod: Sample.Application, []] end
Sample.Application
is de naam van de module, terwijl []
kan een lijst met argumenten bevatten om door te geven aan de start / 2
functie. De start / 2
functie moet worden geïmplementeerd om de applicatie correct te laten opstarten.
De application.ex bevat de callback-module die er als volgt uitziet:
defmodule Sample.Application gebruikt Application Defstart (_type, _args) do children = [] opts = [strategy:: one_for_one, name: Sample.Supervisor] Supervisor.start_link (children, opts) end end
De start / 2
functie moet terugkeren : ok, pid
(met een optionele status als het derde item) of : error, reason
.
Een ander ding dat het vermelden waard is, is dat applicaties de callback-module helemaal niet nodig hebben. Het betekent dat de toepassingsfunctie binnen de mix.exs bestand kan echt minimalistisch worden:
def application do [] end
Dergelijke applicaties worden genoemd bibliotheek applicaties. Ze hebben geen supervisiestructuur, maar kunnen nog steeds worden gebruikt als afhankelijkheden van andere applicaties. Een voorbeeld van een bibliotheektoepassing zou Poison zijn, die we in de vorige sectie als een afhankelijkheid hebben opgegeven.
De eenvoudigste manier om uw toepassing te starten, is door de volgende opdracht uit te voeren:
iex -S mix
U ziet een vergelijkbare uitvoer als deze:
Compileren van 2 bestanden (.ex) Gegenereerde voorbeeldapp
EEN _bouwen map wordt aangemaakt binnen de monster map. Het zal bevatten .balk bestanden en enkele andere bestanden en mappen.
Als je geen Elixir-shell wilt starten, kun je het volgende doen:
mix run
Het probleem is echter dat de toepassing stopt zodra de begin
functie beëindigt zijn taak. Daarom kunt u de --no-halt
sleutel om de applicatie zo lang als nodig te laten draaien:
mix run --no-halt
Hetzelfde kan worden bereikt met behulp van de elixer
commando:
elixer -S mix run --no-halt
Merk echter op dat de toepassing zal stoppen zodra u de terminal sluit waar deze opdracht werd uitgevoerd. Dit kan worden voorkomen door uw toepassing in een losgekoppelde modus te starten:
elixir -S mix run --no-halt --vrijstaand
Soms wilt u misschien dat de gebruiker van een toepassing een parameter instelt voordat de app daadwerkelijk wordt opgestart. Dit is handig wanneer de gebruiker bijvoorbeeld moet kunnen bepalen naar welke poort een webserver moet luisteren. Dergelijke parameters kunnen worden opgegeven in de toepassingsomgeving die een eenvoudige sleutelwaardeopslag in het geheugen is.
Gebruik de. Om een parameter te lezen fetch_env / 2
functie die een app en een sleutel accepteert:
Application.fetch_env (: sample,: some_key)
Als de sleutel niet kan worden gevonden, :fout
atoom is teruggekeerd. Er zijn ook een fetch_env! / 2
functie die in plaats daarvan een foutmelding geeft en get_env / 3
die een standaardwaarde kan bieden.
Gebruik om een parameter op te slaan put_env / 4
:
Application.put_env (: sample,: key,: value)
De vierde waarde bevat opties en hoeft niet te worden ingesteld.
Tenslotte, om een sleutel te verwijderen, gebruik de delete_env / 3
functie:
Application.delete_env (: sample,: key)
Hoe bieden we een waarde voor de omgeving bij het starten van een app? Welnu, dergelijke parameters worden ingesteld met behulp van de --Erl
toets op de volgende manier in:
iex --erl "-sample key value" -S mix
U kunt dan de waarde eenvoudig ophalen:
Application.get_env: sample,: key # =>: value
Wat als een gebruiker vergeet om een parameter op te geven bij het starten van de applicatie? Welnu, hoogstwaarschijnlijk moeten we een standaardwaarde voor dergelijke gevallen opgeven. Er zijn twee mogelijke plaatsen waar u dit kunt doen: binnen de config.exs of in de mix.exs het dossier.
De eerste optie is de voorkeur omdat config.exs is het bestand dat eigenlijk bedoeld is om verschillende configuratie-opties op te slaan. Als uw toepassing veel omgevingsparameters heeft, moet u dit zeker doen config.exs:
gebruik Mix.Config config: sample, key:: value
Voor een kleinere toepassing is het echter vrij goed om de waarden van de omgeving direct in te voeren mix.exs door de applicatiefunctie aan te passen:
def application do [extra_applications: [: logger], mod: Sample.Application, [], env: [# <==== key: :value ] ] end
Oké, om applicaties in actie te zien, laten we het voorbeeld aanpassen dat al in mijn GenServer- en Supervisors-artikelen werd besproken. Dit is een eenvoudige rekenmachine waarmee gebruikers verschillende wiskundige bewerkingen kunnen uitvoeren en het resultaat vrij gemakkelijk kunnen ophalen.
Wat ik wil doen is deze calculator webbased maken, zodat we POST-verzoeken kunnen verzenden om berekeningen uit te voeren en een GET-verzoek om het resultaat te pakken.
Maak een nieuw lib / calc_server.ex bestand met de volgende inhoud:
defmodule Sample.CalcServer gebruikt GenServer def start_link (initial_value) do GenServer.start_link (__ MODULE__, initial_value, name: __MODULE__) end def init (initial_value) wanneer is_number (initial_value) do : ok, initial_value end def init (_) do : stop, "De waarde moet een geheel getal zijn!" end def add (number) do GenServer.cast (__ MODULE__, : add, number) end def result do genServer.call (__ MODULE__,: result) end def handle_call (: result, _, state) do : reply, state, state end def handle_cast (operation, state) do case operatie do : add, number -> : noreply, state + number _ -> : stop, "Not imported", state end end def terminate (_reason, _state) do IO.puts "The server terminated" end end
We zullen alleen ondersteuning toevoegen voor de toevoegen
operatie. Alle andere wiskundige bewerkingen kunnen op dezelfde manier worden geïntroduceerd, dus ik zal ze hier niet vermelden om de code compacter te maken.
De CalcServer
maakt gebruik van GenServer
, dus we krijgen child_spec
automatisch en kan het starten vanuit de callback-functie als volgt:
def start (_type, _args) do children = [Sample.CalcServer, 0] opts = [strategy:: one_for_one, name: Sample.Supervisor] Supervisor.start_link (children, opts) end
0
hier is het eerste resultaat. Het moet een nummer zijn, anders CalcServer
zal onmiddellijk worden beëindigd.
Nu is de vraag hoe we webondersteuning toevoegen? Om dat te doen, hebben we twee afhankelijkheden van derden nodig: Plug, die zal fungeren als een abstractie-bibliotheek, en Cowboy, die zal fungeren als een echte webserver. Uiteraard moeten we deze afhankelijkheden binnen de mix.exs het dossier:
defp-deps doen [: cowboy, "~> 1.1", : plug, "~> 1.4"] eindigen
Nu kunnen we de plug-applicatie starten onder onze eigen supervisiestructuur. Tweak de start-functie als volgt:
def start (_type, _args) do children = [Plug.Adapters.Cowboy.child_spec (: http, Sample.Router, [], [port: Application.fetch_env! (: sample,: port)]), Sample.CalcServer , 0] # ... einde
Hier bieden wij child_spec
en instellen Sample.Router
om te reageren op verzoeken. Deze module wordt in een oogwenk aangemaakt. Wat ik echter niet leuk vind aan deze opstelling, is dat het poortnummer hard gecodeerd is, wat niet echt handig is. Ik zou het misschien willen aanpassen bij het starten van de applicatie, dus laten we het in plaats daarvan opslaan in de omgeving:
Plug.Adapters.Cowboy.child_spec (: http, Sample.Router, [], [port: Application.fetch_env! (: Sample,: port)])
Geef nu de standaardpoortwaarde op in de config.exs het dossier:
config: sample, poort: 8088
Super goed!
Hoe zit het met de router? Maak een nieuw lib / router.ex bestand met de volgende inhoud:
defmodule Sample.Router gebruikt Plug.Router plug: match plug: dispatch end
Nu moeten we een aantal routes definiëren om het toevoegen uit te voeren en het resultaat op te halen:
download "/ result" do conn |> ok (to_string (Sample.CalcServer.result)) end post "/ add" do fetch_number (conn) |> Sample.CalcServer.add conn |> ok end
Wij gebruiken krijgen
en post
macro's om het te definiëren /resultaat
en /toevoegen
routes. Die macro's zullen de conn
object voor ons.
OK
en fetch_number
zijn privé-functies op de volgende manier gedefinieerd:
defp fetch_number (conn) do Plug.Conn.fetch_query_params (conn) .params ["number"] |> String.to_integer end defp ok (conn, data \\ "OK") do send_resp conn, 200, data end
fetch_query_params / 2
retourneert een object met alle queryparameters. We zijn alleen geïnteresseerd in het aantal dat de gebruiker naar ons verzendt. Alle parameters zijn in eerste instantie strings, dus we moeten deze naar integer converteren.
send_resp / 3
stuurt een antwoord op de klant met de verstrekte statuscode en een hoofdtekst. We zullen hier geen fouten controleren, dus de code zal altijd zijn 200
, wat betekent dat alles in orde is.
En dit is het! U kunt de toepassing nu starten op een van de hierboven genoemde manieren (bijvoorbeeld door te typen iex -S mix
) en gebruik de Krul
hulpmiddel om de verzoeken uit te voeren:
krul http: // localhost: 8088 / result # => 0 krul http: // localhost: 8088 / add? number = 1 -X POST # => OK krul http: // localhost: 8088 / result # => 1
In dit artikel hebben we Elixir-toepassingen en hun doel besproken. U hebt geleerd hoe u toepassingen kunt maken, verschillende soorten informatie kunt bieden en afhankelijkheden kunt weergeven in de lijst mix.exs het dossier. U hebt ook gezien hoe u de configuratie in de omgeving van de app kunt opslaan en hebt een aantal manieren geleerd om uw toepassing te starten. Ten slotte hebben we applicaties in actie gezien en een eenvoudige webgebaseerde calculator gemaakt.
Vergeet niet dat de hex.pm website vele honderden applicaties van derden bevat die klaar zijn voor gebruik in uw projecten, dus zorg ervoor dat u door de catalogus bladert en de oplossing kiest die bij u past!
Hopelijk vond je dit artikel nuttig en interessant. Ik dank u dat u bij mij bent gebleven en tot de volgende keer.