Ihmiset haluavat luokitella ohjelmointikielet paradigmoiksi. On olemassa olio-kieliä (OO), välttämättömiä kieliä, toiminnallisia kieliä jne. Tästä voi olla apua selvitettäessä, mitkä kielet ratkaisevat samanlaisia ongelmia ja minkä tyyppisiä ongelmia kielen on tarkoitus ratkaista.
Kummassakin tapauksessa paradigmalla on yleensä yksi 'pää' painopiste ja tekniikka, joka on kyseisen kielten perheen liikkeellepaneva voima:
OO-kielillä se on luokka tai esine keinona kapseloida tila (data) manipuloimalla kyseistä tilaa (menetelmiä).
Toiminnallisilla kielillä se voi olla toimintojen manipulointi itse tai muuttumattomia tietoja siirretty toiminnosta toimintoon.
Sillä aikaa Eliksiiri (ja Erlang ennen sitä) luokitellaan usein toiminnallisiksi kieliksi, koska niillä on toiminnallisille kielille yhteisiä muuttumattomia tietoja, sanoisin, että ne edustavat erillinen paradigma monista toiminnallisista kielistä . Ne ovat olemassa ja ne hyväksytään OTP: n olemassaolon vuoksi, joten luokittelisin heidät sellaisiksi prosessisuuntautuneet kielet .
Tässä viestissä kerromme prosessikeskeisen ohjelmoinnin merkityksen käytettäessä näitä kieliä, tutkitaan eroja ja samankaltaisuuksia muihin paradigmoihin, tarkastellaan vaikutuksia sekä koulutukseen että käyttöönottoon ja lopetetaan lyhyellä prosessisuuntautuneella ohjelmointiesimerkillä.
Aloitetaan määritelmästä: Prosessipainotteinen ohjelmointi on paradigma, joka perustuu Viestintä peräkkäisistä prosesseista , alun perin paperilta Tony Hoare vuonna 1977. Tätä kutsutaan myös yleisesti näyttelijä samanaikaisuuden malli. Muita kieliä, joilla on jonkin verran yhteyttä tähän alkuperäiseen teokseen, ovat Occam, Limbo ja Go. Virallinen paperi käsittelee vain synkronista viestintää; useimmat näyttelijämallit (mukaan lukien OTP ) käyttää myös asynkronista viestintää. Asynkronisen viestinnän päälle on aina mahdollista rakentaa synkroninen viestintä, ja OTP tukee molempia muotoja.
Tämän historian aikana OTP loi järjestelmän vikasietoista laskentaa varten viestimällä peräkkäisiä prosesseja. Vikasietoiset palvelut johtuvat ”anna sen epäonnistua” -lähestymistavasta, jossa kiinteät virheet palautetaan esimiehinä ja että toimijamalli sallii hajautetun käsittelyn. 'Anna sen epäonnistua' voidaan verrata 'estää sen epäonnistumasta', koska ensimmäinen on paljon helpompi mukauttaa ja OTP: n on osoitettu olevan paljon luotettavampi kuin jälkimmäinen. Syynä on se, että vikojen estämiseen tarvittavat ohjelmointitoimet (kuten Java-tarkistetun poikkeusmallin mukaan) ovat paljon mukana ja vaativia.
Joten prosessisuuntautunut ohjelmointi voidaan määritellä a paradigma, jossa prosessirakenne ja järjestelmän prosessien välinen viestintä ovat ensisijaisia huolenaiheita .
Kohdekeskeisessä ohjelmoinnissa tietojen ja toiminnan staattinen rakenne on ensisijainen huolenaihe. Mitä menetelmiä vaaditaan oheisen datan manipuloimiseksi, ja mitä tulisi olla objektien tai luokkien välisten yhteyksien välillä. Siten UML: n luokkakaavio on erinomainen esimerkki tästä kohdennuksesta, kuten kuvasta 1 näkyy.
Voidaan todeta, että yleinen kritiikki objektisuuntautuneelle ohjelmoinnille on, että näkyvää ohjausvirtaa ei ole. Koska järjestelmät koostuvat suuresta joukosta luokkia / objekteja, jotka on määritelty erikseen, vähemmän kokeneen henkilön voi olla vaikea visualisoida järjestelmän ohjausvirtaa. Tämä pätee erityisesti järjestelmiin, joissa on paljon perintöä ja jotka käyttävät abstrakteja rajapintoja tai joilla ei ole vahvaa kirjoittamista. Useimmissa tapauksissa siitä tulee tärkeä kehittäjä muistaa suuri osa järjestelmärakenteesta tehokkaaksi (millä luokilla on mitä menetelmiä ja mitä käytetään millä tavoin).
Kohdekohtaisen kehityksen lähestymistavan vahvuus on, että järjestelmää voidaan laajentaa tukemaan uudentyyppisiä objekteja, joilla on rajoitettu vaikutus olemassa olevaan koodiin, kunhan uudet objektityypit vastaavat nykyisen koodin odotuksia.
Monet toiminnalliset ohjelmointikielet käsittelevät samanaikaisuutta eri tavoin, mutta niiden ensisijaisena painopisteenä on muuttumaton data, joka kulkee toimintojen välillä, tai toimintojen luominen muista toiminnoista (korkeamman tason funktiot, jotka tuottavat toimintoja). Suurimmaksi osaksi kielen painopiste on edelleen yksi osoiteavaruus tai suoritettava tiedosto, ja tällaisten suoritettavien tiedostojen välinen viestintä hoidetaan käyttöjärjestelmäkohtaisella tavalla.
Esimerkiksi, Scala on toimiva kieli rakennettu Java-virtuaalikoneelle. Vaikka se voi käyttää Java-palveluja viestintään, se ei ole luonnollinen osa kieltä. Vaikka se on Spark-ohjelmoinnissa käytetty yleinen kieli, se on jälleen kirjasto, jota käytetään yhdessä kielen kanssa.
Toiminnallisen paradigman vahvuus on kyky visualisoida järjestelmän ohjausvirta, kun otetaan huomioon ylätason toiminto. Ohjausvirta on selkeä siinä, että kukin toiminto kutsuu muita toimintoja ja välittää kaikki tiedot yhdestä seuraavaan. Toiminnallisessa paradigmassa ei ole sivuvaikutuksia, mikä helpottaa ongelman määrittämistä. Puhtaiden toiminnallisten järjestelmien haasteena on, että ”sivuvaikutuksilta” vaaditaan jatkuvaa tilaa. Hyvin suunnitelluissa järjestelmissä tilan pysyminen hoidetaan ohjausvirran ylimmällä tasolla, jolloin suurin osa järjestelmästä on sivuvaikutukseton.
Elixir / Erlangissa ja OTP: ssä viestinnän primitiivit ovat osa virtuaalikonetta, joka suorittaa kielen. Kyky kommunikoida prosessien ja koneiden välillä on rakennettu ja keskeinen osa kielijärjestelmää. Tämä korostaa viestinnän merkitystä tässä paradigmassa ja näissä kielijärjestelmissä.
Vaikka eliksiirikieli on pääasiassa toimiva kielellä ilmaistun logiikan kannalta sen käyttö on prosessisuuntautunut .
Tässä viestissä määritelty prosessikeskeisyys on suunnitella ensin järjestelmä olemassa olevien prosessien muodossa ja miten ne kommunikoivat. Yksi pääkysymyksistä on se, mitkä prosessit ovat staattisia ja mitkä ovat dynaamisia, jotka syntyvät pyynnöstä pyynnöille, jotka palvelevat pitkäkestoista tarkoitusta, joilla on yhteinen tila tai osa järjestelmän jaettua tilaa, ja mitkä prosessin ominaisuudet järjestelmä on luonnostaan samanaikainen. Aivan kuten OO: lla on erityyppisiä objekteja ja toiminnallisilla on erityyppisiä toimintoja, prosessikeskeisellä ohjelmoinnilla on tyyppisiä prosesseja.
Sellaisenaan, prosessisuuntautunut suunnittelu on prosessityyppien joukon tunnistaminen, jota tarvitaan ongelman ratkaisemiseksi tai tarpeen täyttämiseksi .
Ajan osa tulee nopeasti suunnitteluun ja vaatimuksiin. Mikä on järjestelmän elinkaari? Mitkä mukautetut tarpeet ovat satunnaisia ja mitkä jatkuvasti? Missä järjestelmän kuormitus on ja mikä on odotettu nopeus ja tilavuus? Vasta sen jälkeen, kun tämäntyyppiset näkökohdat on ymmärretty, prosessisuuntautunut suunnittelu alkaa määritellä kunkin prosessin toiminnan tai suoritettavan logiikan.
Tämän luokittelun merkitys koulutukselle on, että koulutusta ei pitäisi aloittaa kielisyntaksilla tai 'Hello World' -esimerkkeillä, vaan järjestelmätekniikan ajattelu ja suunnittelun painopiste prosessin allokoinnissa .
Koodaamiseen liittyvät huolenaiheet ovat toissijaisia prosessisuunnittelulle ja allokoinnille, jotka käsitellään parhaiten korkeammalla tasolla, ja niihin liittyy monialaista ajattelua elinkaaresta, laadunvalvonnasta, DevOpsista ja asiakkaiden liiketoiminnan vaatimuksista. Kaikissa Elixir- tai Erlang-kursseissa on (ja yleensä) oltava OTP, ja niiden tulisi olla prosessisuuntautuneita alusta alkaen, ei 'Nyt voit koodata Elixirissä, joten tehkäämme samanaikaisuutta' -tyyppisenä lähestymistapana.
Adoptio tarkoittaa, että kieltä ja järjestelmää sovelletaan paremmin ongelmiin, jotka edellyttävät viestintää ja / tai tietojenkäsittelyn jakelua. Yhden tietokoneen yhden työmäärän aiheuttavat ongelmat ovat vähemmän mielenkiintoisia tässä tilassa, ja niihin voidaan ehkä paremmin puuttua toisella kielellä. Pitkäikäiset jatkuvan prosessoinnin järjestelmät ovat tämän kielen ensisijainen kohde, koska sillä on vikasietoisuus rakennettu alusta asti.
Dokumentoinnissa ja suunnittelutyössä voi olla erittäin hyödyllistä käyttää graafista merkintää (kuten kuva 1 OO-kielille). Ehdotus Elixirille ja prosessisuuntautuneelle ohjelmoinnille UML: stä olisi sekvenssikaavio (esimerkki kuvassa 2), joka näyttää ajalliset suhteet prosessien välillä ja tunnistaa, mitkä prosessit ovat mukana pyynnön palvelemisessa. Elinkaaren ja prosessirakenteen kaappaamiseen ei ole UML-kaaviotyyppiä, mutta se voidaan esittää yksinkertaisella laatikko- ja nuolikaaviona prosessityypeille ja niiden suhteille. Esimerkiksi kuva 3:
Lopuksi käymme läpi lyhyen esimerkin prosessin suuntautumisen soveltamisesta ongelmaan. Oletetaan, että meidän tehtävämme on tarjota järjestelmä, joka tukee maailmanlaajuisia vaaleja. Tämä ongelma valitaan siten, että monet yksittäiset toiminnot suoritetaan purskeina, mutta tulosten yhdistäminen tai yhteenveto on toivottavaa reaaliajassa ja saattaa nähdä merkittävän kuormituksen.
Aluksi voimme nähdä, että jokaisen äänten antaminen on liikenteen puhkeaminen järjestelmään monista erillisistä syötteistä, sitä ei ole järjestetty ajallaan ja sillä voi olla suuri kuormitus. Tämän toiminnan tukemiseksi haluaisimme suuren määrän prosesseja, jotka kaikki keräävät nämä syötteet ja välittävät ne edelleen keskeisempään taulukkoprosessointiprosessiin. Nämä prosessit voisivat sijaita lähellä jokaisen maan populaatioita, jotka tuottavat ääniä, ja siten tarjota matalan viiveen. He säilyttävät paikalliset tulokset, kirjaavat syötteensä välittömästi ja välittävät ne taulukkomääritykseen erissä kaistanleveyden ja yleiskustannusten vähentämiseksi.
Aluksi voimme nähdä, että jokaisella lainkäyttöalueella on oltava prosesseja, joilla seurataan äänestyksiä, joissa tulokset on esitettävä. Oletetaan tässä esimerkissä, että meidän on seurattava tuloksia kullekin maalle ja jokaisessa maassa maakunnittain / osavaltioittain. Tämän toiminnan tukemiseksi haluamme vähintään yhden prosessin maata kohden, joka suorittaa laskennan ja säilyttää nykyiset kokonaissummat, ja toisen joukon kullekin osavaltiolle / provinssille kussakin maassa. Tämä olettaa, että meidän on pystyttävä vastaamaan maan ja osavaltion / provinssin kokonaissummiin reaaliajassa tai matalalla viiveellä. Jos tulokset saadaan tietokantajärjestelmästä, voimme valita toisen prosessin allokoinnin, jossa kokonaismäärät päivitetään ohimenevillä prosesseilla. Erillisten prosessien käytön etuna näissä laskennoissa on, että tulokset tapahtuvat muistinopeudella ja ne voidaan saavuttaa pienellä viiveellä.
Lopuksi voimme nähdä, että paljon ja paljon ihmisiä tarkastelee tuloksia. Nämä prosessit voidaan jakaa osiin monin tavoin. Saatamme haluta jakaa kuormituksen sijoittamalla prosessit kussakin maassa, joka vastaa maan tuloksista. Prosessit voisivat tallentaa välimuistiin laskentaprosessien tulokset laskentaprosessien kyselyn kuormituksen vähentämiseksi ja / tai laskentaprosessit voivat työntää tuloksia säännöllisin väliajoin asianmukaisiin tulosprosesseihin, kun tulokset muuttuvat huomattavasti, tai laskentaprosessi muuttuu tyhjäkäynniksi, mikä osoittaa hidastunutta muutosnopeutta.
Kaikissa kolmessa prosessityypissä voimme skaalata prosessit toisistaan riippumatta, jakaa ne maantieteellisesti ja varmistaa, ettei tuloksia koskaan menetetä aktiivisen kuittauksen avulla tiedonsiirroista prosessien välillä.
Kuten keskusteltiin, olemme aloittaneet esimerkin prosessin suunnittelusta, joka on riippumaton kunkin prosessin liiketoimintalogiikasta. Tapauksissa, joissa liiketoimintalogiikalla on erityisiä vaatimuksia tietojen yhdistämiselle tai maantieteelliselle sijainnille, mikä voi vaikuttaa prosessin allokointiin iteratiivisesti. Tähänastinen prosessisuunnittelumme on esitetty kuvassa 4.
Erillisten prosessien käyttö äänten vastaanottamiseksi sallii jokaisen äänen vastaanottamisen muista äänistä riippumatta, kirjaamisen saatuaan ja erittelyn seuraaviin prosesseihin, mikä vähentää merkittävästi näiden järjestelmien kuormitusta. Suurelle määrälle dataa kuluttavalle järjestelmälle datan määrän vähentäminen prosessikerrosten avulla on yleinen ja hyödyllinen malli.
Suorittamalla laskennan erillisessä joukossa prosesseja voimme hallita näiden prosessien kuormitusta ja varmistaa niiden vakauden ja resurssitarpeet.
Sijoittamalla tulosesityksen erilliseen prosessijoukkoon me molemmat hallitsemme kuormitusta muuhun järjestelmään ja sallimme prosessien skaalaamisen dynaamisesti kuormitusta varten.
Lisätään nyt joitain monimutkaisia vaatimuksia. Oletetaan, että kullakin lainkäyttöalueella (maassa tai osavaltiossa) ääntenlaskenta voi johtaa suhteelliseen tulokseen, voittaja-kaikki-tulokseen tai ei tulosta, jos ääniä ei anneta riittävästi kyseisen lainkäyttöalueen väestöön nähden. Jokaisella lainkäyttöalueella on määräysvalta näihin näkökohtiin. Tämän muutoksen myötä maiden tulokset eivät ole yksinkertainen yhteenveto äänestystuloksista, vaan osavaltioiden / provinssien tulosten yhteenveto. Tämä muuttaa prosessin allokoinnin alkuperäisestä vaatimukseksi, että osavaltio / provinssi -prosessin tulokset syötetään maakohtaisiin prosesseihin. Jos äänten keräämisen ja osavaltion / provinssin ja maakunnan prosessien välillä käytetty protokolla on sama, aggregaatiologiikkaa voidaan käyttää uudelleen, mutta tarvitaan erillisiä tuloksia pitäviä prosesseja ja niiden kommunikaatiopolut ovat erilaiset, kuten kuvassa esitetään 5.
Viimeistele esimerkki tarkistamalla esimerkin toteutus Elixir OTP: ssä. Asioiden yksinkertaistamiseksi tässä esimerkissä oletetaan, että Phoenixin kaltaista verkkopalvelinta käytetään varsinaisten verkkopyyntöjen käsittelemiseen, ja nämä verkkopalvelut pyytävät yllä määritettyä prosessia. Tämän etuna on yksinkertaistaa esimerkkiä ja pitää keskittyminen Elixiriin / OTP: hen. Tuotantojärjestelmässä näiden erillisillä prosesseilla on joitain etuja, ja se erottaa huolenaiheet, mahdollistaa joustavan käyttöönoton, jakaa kuorman ja vähentää viiveitä. Koko lähdekoodi testeineen löytyy osoitteesta https://github.com/technomage/voting . Lähde on lyhennetty tässä viestissä luettavuuden vuoksi. Jokainen alla oleva prosessi sopii OTP-valvontapuuhun varmistaakseen, että prosessit käynnistetään uudelleen epäonnistumisen yhteydessä. Katso lähteestä lisätietoja tästä esimerkin näkökohdasta.
Tämä prosessi vastaanottaa ääniä, kirjaa ne pysyvään varastoon ja erittää tulokset aggregaattoreille. VoteRecoder-moduuli käyttää Task.Supervisoria lyhytkestoisten tehtävien hallintaan jokaisen äänen tallentamiseksi.
defmodule Voting.VoteRecorder do @moduledoc ''' This module receives votes and sends them to the proper aggregator. This module uses supervised tasks to ensure that any failure is recovered from and the vote is not lost. ''' @doc ''' Start a task to track the submittal of a vote to an aggregator. This is a supervised task to ensure completion. ''' def cast_vote where, who do Task.Supervisor.async_nolink(Voting.VoteTaskSupervisor, fn -> Voting.Aggregator.submit_vote where, who end) |> Task.await end end
Tämä prosessi yhdistää äänet lainkäyttöalueella, laskee tuloksen tälle lainkäyttöalueelle ja välittää äänten yhteenvedot seuraavalle ylemmälle prosessille (ylemmän tason lainkäyttöalue tai tuloksen esittäjä).
defmodule Voting.Aggregator do use GenStage ... @doc ''' Submit a single vote to an aggregator ''' def submit_vote id, candidate do pid = __MODULE__.via_tuple(id) :ok = GenStage.call pid, {:submit_vote, candidate} end @doc ''' Respond to requests ''' def handle_call {:submit_vote, candidate}, _from, state do n = state.votes[candidate] || 0 state = % votes: Map.put(state.votes, candidate, n+1) {:reply, :ok, [%{state.id => state.votes}], state} end @doc ''' Handle events from subordinate aggregators ''' def handle_events events, _from, state do votes = Enum.reduce events, state.votes, fn e, votes -> Enum.reduce e, votes, fn {k,v}, votes -> Map.put(votes, k, v) # replace any entries for subordinates end end # Any jurisdiction specific policy would go here # Sum the votes by candidate for the published event merged = Enum.reduce votes, %{}, fn {j, jv}, votes -> # Each jourisdiction is summed for each candidate Enum.reduce jv, votes, fn {candidate, tot}, votes -> Logger.debug '@@@@ Votes in #{inspect j} for #{inspect candidate}: #{inspect tot}' n = votes[candidate] || 0 Map.put(votes, candidate, n + tot) end end # Return the published event and the state which retains # Votes by jourisdiction {:noreply, [%{state.id => merged}], % votes: votes} end end
Tämä prosessi saa äänet aggregaattorilta ja tallentaa nämä tulokset palvelupyyntöihin tulosten esittämistä varten.
defmodule Voting.ResultPresenter do use GenStage … @doc ''' Handle requests for results ''' def handle_call :get_votes, _from, state do {:reply, {:ok, state.votes}, [], state} end @doc ''' Obtain the results from this presenter ''' def get_votes id do pid = Voting.ResultPresenter.via_tuple(id) {:ok, votes} = GenStage.call pid, :get_votes votes end @doc ''' Receive votes from aggregator ''' def handle_events events, _from, state do Logger.debug '@@@@ Presenter received: #{inspect events}' votes = Enum.reduce events, state.votes, fn v, votes -> Enum.reduce v, votes, fn {k,v}, votes -> Map.put(votes, k, v) end end {:noreply, [], %state } end end
Tässä viestissä tutkittiin Elixir / OTP: n potentiaalia prosessikeskeisenä kielenä, verrattiin tätä olio- ja toiminnallisiin paradigmoihin ja tarkasteltiin tämän vaikutuksia koulutukseen ja adoptioon.
Viesti sisältää myös lyhyen esimerkin tämän suuntauksen soveltamisesta esimerkkiongelmaan. Jos haluat tarkistaa kaikki koodit, tässä on linkki esimerkkiin GitHub jälleen, vain ettei sinun tarvitse vierittää sitä etsimällä.
Tärkein takeaway on tarkastella järjestelmiä kokoelmana kommunikoivista prosesseista. Suunnittele järjestelmä prosessisuunnittelun näkökulmasta ensin ja loogisen koodauksen näkökulmasta toiseksi.
Elixir on toiminnallinen ohjelmointikieli, joka on rakennettu Erlang VM: ään. OTP on prosessikeskeinen ohjelmointikehys, joka on olennainen osa Erlangia ja Elixiriä.
Prosessipainotteinen kehitys keskittyy ensinnäkin järjestelmän prosessirakenteeseen ja toiseksi järjestelmän toiminnalliseen logiikkaan.
Aloita koulutuksella tai etsinnällä, joka keskittyy OTP: hen ja prosessinhallintaan ja sitten Elixirin syntaksiin ja toiminnallisiin näkökohtiin. Vältä harjoittelua, joka alkaa hello world koodausesimerkillä ja pääsee OTP: lle vasta puolivälissä.
Elixir / OTP: n luotettavuus- ja samanaikaisuusnäkökohdat ovat päätä ja hartioita kilpailevien pinoiden yläpuolella, vaatii vähemmän ohjelmoijataitoa taitoa varten ja sen suorituskyky on parempi laatikosta kuin Ruby on Rails tai Node.
Elixir / OTP ovat pitkäkestoisia prosesseja tai prosesseja, jotka vaativat moniydinsuorituskykyä. He keskittyvät enemmän matalaan viiveeseen kuin suureen läpäisykykyyn. Ne eivät ole vahvoja vaativille yhden ytimen suoritussovelluksille tai sovelluksille, joita ajetaan harvoin erä- tai komentoriviympäristössä.