1 UVOD IN DEFINICIJE GraphQL je poizvedovalni jezik, ki omogoča prepro- sto pridobivanje podatkov iz zalednega dela aplikacije. Razvilo ga je podjetje Facebook, in sicer predvsem za interno uporabo. Leta 2015 so ga prvič predstavili javnosti skupaj z objavo specifikacije [11] in referenčne implementacije v programskem jeziku JavaScript [5]. Zdaj je na voljo v večini popularnejših programskih jezikov [6], vendar je šele v zadnjem letu začel pridobi- vati na popularnosti. Kot primer uporabe lahko omenimo podjetje GitHub, ki je novo verzijo svojega apija izdalo s pomočjo GraphQL [8]. Ime GraphQL izvira iz dveh delov: Graph označuje graf; kratica QL pomeni query language oz. po sloven- sko poizvedovalni jezik. Pri GraphQLu si torej pridobi- Prejet 30. oktober, 2018 Odobren 17. december, 2018 vanje podatkov lahko predstavljamo kot premikanje po grafu. Klasičen graf definiramo kot množico vozlišč in povezav: G = (V,E) (1) pri čemer je V množica vozšlišč (vertices) in E množica povezav (edges) [9] [10]. Za lažje razumevanje bomo predstavljeno ilustrirali na grafu na sliki 1. Na ta primer se bomo sklicevali skozi ves članek. Slika 1: Primer grafa Na grafu na sliki 1 opazimo štiri entitete: študente, predmete, predavalnice in predavatelje. To so vozlišča grafa, ki jih označimo s krogi. Vsaka izmed entitet ima neke lastnosti (npr. študent ima definirano ime), ki pa jih zaradi berljivosti nismo dodali na sliko. Študent obiskuje predmete, vsak predmet je predavan v neki predavalnici in ima nekega predavatelja. To so povezave grafa, ki jih označimo s puščicami. Povezave so lahko enosmerne ali dvosmerne. Dvosmerno povezavo med vozliščema A in B definiramo kot povezavo od A do B in hkrati povezavo od B do A. Kot smo prej omenili, pridobivanje podatkov poteka s premikanjem po grafu. Poizvedbo dobimo tako, da si izberemo neko točko in se začnemo premikati po njenih izhodnih povezavah. 44 KAJDIČ, JURIČ Primeri poizvedb: • Vrni ime in priimek vseh študentov, ki obiskujejo predmet 1. • Vrni vpisne številke študentov, ki obiskujejo pred- mete pod mentorstvom predavatelja 1. • Vrni vsa imena predmetov, ki jih predavata preda- vatelja 1 in 2. 2 KAKO DELUJE GRAPHQL V tem odseku bomo zgoraj predstavljeno logiko in poizvedbe pretvorili v notacijo GraphQL. Predstavili bomo tudi sintakso jezika in primer poizvedbe. GraphQL lahko grobo razdelimo na tri dele: poizve- dovanje, definiranje sheme in izvajalno okolje. Vsakega izmed treh bomo na kratko predstavili. 2.1 Poizvedovanje Poizvedovanje je glavni razlog, zakaj se razvijalec odloči za uporabo GraphQL. Pred opisom poizvedovanja bomo pokazali, kako izgleda prava poizvedba na strežnik GraphQL. // poizvedba query { vrniPredmet(id: "1") { studenti { ime priimek } } } // rezultat poizvedbe { "vrniPredmet": { "studenti": [ { "ime": "Janez", "priimek": "Novak" } ] } } Če pogledamo zgornjo poizvedbo, opazimo naslednje lastnosti: • Sintaksa poizvedovanja je izpeljana iz formata JSON [4]. • Izbirnost podatkov: uporabnik si lahko izbira, ka- tera polja naj mu strežnik vrne. • Število poizvedb: ena poizvedba je ponavadi dovolj za pridobitev vseh potrebnih podatkov. • Rezultat poizvedbe: rezultat je enake oblike kot zahteva; uporabnik točno ve, v kakšni obliki bo strežnik vrnil podatke. 2.2 Definiranje sheme Da bi bilo poizvedovanje mogoče, je treba definirati shemo. V shemi je treba definirati vse podatkovne tipe, ki jih bomo uporabljali pri poizvedovanju (v našem pri- meru so to prej omenjene štiri entitete), kot tudi vse ope- racije, ki jih uporabnik lahko uporablja v poizvedbah. Če pogledamo na prejšnji graf, operacije so točke na grafu, na katerih lahko vstopimo v graf (podmnožica točk). GraphQL podpira naslednje skalarne tipe: celo število (Int), decimalno število (float), niz znakov (String), logični izraz (Boolean) in enolični identifikator (ID; obravnavan kot niz znakov). Poleg tega so podprti še seznami (označimo z []), vmesniki (Interface), unije (Union) in naštevni tipi (Enum). Vsi uporabniško de- finirani tipi morajo biti sestavljeni iz osnovnih skalarnih tipov ali kakšnih drugih uporabniško definiranih tipov. Definirati je treba tudi operacije. GraphQL podpira tri vrste operacij: bralne operacije (query), pisalne operacije (mutation) in naročnine (subscription) [3]. Bralna opera- cija je namenjena standardnim poizvedbam za pridobiva- nje podatkov, pisalne operacije so namenjene spreminja- nju podatkov in naročnine so namenjene konstantnemu spremljanju neke vrednosti (npr. cene delnic). Če pogle- damo zgornjo poizvedbo, imamo podatkovni tip študent, ki vsebuje vsaj polji ime in priimek; podatkovni tip predmet, ki vsebuje enolični identifikator predmeta (id), in seznam študentov ter bralno operacijo vrniPredmet, ki zahtevani predmet vrne. Shema bi v notaciji GraphQL izgledala tako: type Student { ime: String priimek: String // ostala polja } type Predmet { id: ID! studenti: [Student] // ostala polja } type Query { vrniPredmet(id: ID!): Predmet // ostale operacije } // ostali tipi in operacije 2.3 Izvajalno okolje Zadnja komponenta GraphQL je izvajalno okolje. To je zaledje GraphQL oz. drugače povedano program, ki sprejema, procesira in vrača poizvedbe (ki so bile prej definirane v shemi). GraphQL je neodvisen od transpor- tnega protokola (transportno agnostičen), kar pomeni, da ga lahko uporabljamo prek poljubnega transportnega protokola (v večini primerov je uporabljen protokol SPLETNE STORITVE Z GRAPHQL 45 HTTP) [1]. Celotna logika pridobivanja podatkov temelji na t. i. razreševalskih funkcijah (resolver functions). Vsaka operacija in vsako polje v podatkovni shemi vse- buje svojo razreševalsko funkcijo, ki jih izvajalno okolje ob prejetju poizvedbe pokliče v določenem vrstnem redu. Razreševalske funkcije mora napisati razvijalec. Da bi res razumeli, kaj se dogaja ob prejetju poizvedbe, bomo ilustrirali celotno dogajanje ob prejetju prej ome- njene poizvedbe z operacijo vrniPredmet [13]. 1) Uporabnik pošlje poizvedbo na strežnik GraphQL prek izbranega transportnega protokola. 2) Izvajalno okolje sprejme poizvedbo, jo najprej sin- taktično preveri (pogleda format poizvedbe in pre- veri, ali v shemi obstaja operacija vrniPredmet). Če obstaja, pokliče njeno razreševalsko funkcijo, ki vrne določeni predmet. Če preverba ne uspe, vrne uporabniku napako. 3) Izvajalno okolje pogleda v shemo, kakšna polja vsebuje entiteta predmet in kakšne polja je zahte- val uporabnik. V našem primeru je zahtevano le eno polje, in sicer polje studenti (ki je tipa seznam študentov). 4) Izvajalno okolje pokliče razreševalsko funkcijo polja studenti, ki vrne vse študente. 5) Izvajalno okolje pogleda v shemo, kakšna polja vsebuje entiteta študent in kakšna polja je zahteval uporabnik. V našem primeru sta zahtevani polji ime in priimek. 6) Izvajalno okolje za vsakega študenta pokliče ra- zreševalski funkciji za pridobivanje imena in pri- imka. 7) Pridobljeni podatki se sestavijo v končni rezultat in vrnejo uporabniku (ponavadi v formatu JSON). 3 PRIMERJAVA GRAPHQL Z ARHITEKTURO REST Večina sodobnih aplikacij za komunikacijo med čelnim in zalednim delom uporablja arhitekturo REST. Kljub razširjeni uporabi ima arhitektura REST številne po- manjkljivosti [14]. GraphQL je bil zasnovan kot odgovor na te pomanjkljivosti: • Pri arhitekturi REST se entitete nahajajo na svojem naslovu. Če to preslikamo na naš primer, bi imeli štiri dostopne točke: /student, /profesor, /predmet in /predavalnica. Uporabnik, ki bi hotel delati zah- tevnejše poizvedbe, bi moral v najslabšem primeru narediti štiri poizvedbe. Pri kakšnih kompleksnejših aplikacijah se ta številka lahko zelo poveča. Ne glede na število entitet se z uporabo GraphQL število zahtev zmanjša na eno samo. • Pri arhitekturi REST se lahko zgodi, da mora neka aplikacija hraniti več vzporednih dostopnih točk, saj nekatere aplikacije še niso bile posodobljene na nove verzije. To pomeni, da ima lahko aplika- cija za neko entiteto več dostopnih točk: starejša verzija /v1/student in verzija /v2/student z novimi funkcinalnostmi. Pri GraphQLu se lahko temu izo- gnemo, saj je dodajanje in spreminjanje polj v po- datkovnih tipih preprosto in neodvisno od njihove implementacije (polje dodamo v shemo in dodamo razreševalsko funkcijo). Dodana polja ne morejo uničiti nobene poizvedbe. Razreševalske funkcije lahko v celoti spremenimo in končni uporabnik tega ne bo opazil, saj bo delal enake poizvedbe, kot jih je delal prej. • S tem ko uporabnik pri poizvedovanju sam izbira, katere podatke hoče pridobiti, se močno zmanjša količina prenesenih podatkov. Pri arhitekturi REST dostopne točke ponavadi vračajo celotne entitete z vsemi polji, ki pa jih aplikacija ponavadi ne potrebuje. To je bila ena izmed motivacij podjetja Facebook pri razvoju GraphQL, saj je prenos po- datkov v nerazvitih predelih sveta velik problem. • GraphQL omogoča t. i. introspekcijo [7]. Vsak strežnik omogoča poizvedovanje po operacijah in podatkovnih tipih, ki so na voljo. Lahko sestavimo poizvedbo, ki vrne mogoče operacije za naslednje poizvedbe. Pri arhitekturi REST mora za to poskr- beti razvijalec z uporabo različnih orodij (najpo- pularnejšo je orodje Swagger) ali kakšnih drugih pristopov. Kljub temu tehnologija GraphQL ne more popolnoma zamenjati alternativ [15]. V nekaterih primerih lahko uporaba GraphQL prinese celo negativne posledice: • Pri zelo preprostih podatkih oz. podatkih z malo relacijami lahko uvedba GraphQL poizvedovanje upočasni. Tipičen primer so aplikacije interneta stvari, ki zbirajo senzorske podatke z vnaprej določeno obliko. Poizvedba REST bo hitrejša, saj procesiranje in tudi definiranje sheme (ki bi bila trivialna) v tem primeru ne bosta potrebna. • Pri uporabi GraphQL je lahko zaradi izbirnosti vsak izhod poizvedbe drugačen. Zato je oteženo predpomnjenje rezultatov poizvedb. V arhitekturi REST je izhod vedno iste oblike. • GraphQL je relativno nova tehnologija, kar se pokaže predvsem v manjšem številu orodij in doku- mentacije. Če gre kaj narobe, bomo z uporabo al- ternativ lažje našli rešitve kot pri uporabi GraphQL. 4 GRAPHQL IN MIKROSTORITVE Pod pojmom mikrostoritve označujemo sodoben način razvoja aplikacij, pri čemer aplikacije razdelimo na manjše komponente – mikrostoritve [2]. Pri tem po- skušamo doseči čim večjo neodvisnost med posame- znimi mikrostoritvami. Pri razvoju se nagibamo k čim večji preprostosti posameznih mikrostoritev: vsaka mi- krostoritev opravlja neko določeno funkcijo. Komuni- kacija poteka izključno prek predefiniranih vmesnikov (REST, čakalne vrste, ad - hoc pristopi...). Ta lastnost se imenuje šibka sklopljenost. Posledično lahko za vsako posamezno mikrostoritev skrbi druga razvijalska ekipa 46 KAJDIČ, JURIČ z drugim razvojnim ciklom. Še več: vsaka mikrostori- tev je lahko napisana v svojem programskem jeziku. Glavna prednost uporabe mikrostoritev je predvsem učinkovitejše skaliranje aplikacij. Aplikacijo lahko ska- liramo glede na najbolj uporabljane funkcionalnosti oz. skaliramo samo tiste mikrostoritve, ki morajo obdelati največ prometa. Primer takšnega skaliranja je skaliranje mikrostoritve katalog izdelkov v neki spletni trgovini. Skaliranje, pri katerem skaliramo le posamezni del apli- kacije z zagonom več instanc te aplikacije, imenujemo horizontalno skaliranje. Nasprotje horizontalnega skali- ranja je vertikalno skaliranje, pri katerem pa skaliramo celo aplikacijo s povečanjem strežniških zmogljivosti (več RAM, hitrejši CPU...). Aplikacija, ki sledi konceptom mikrostoritev, je sesta- vljena iz velikega števila mikrostoritev. Če se postavimo na stran razvijalca zalednega dela aplikacije, opazimo problem. Da bo aplikacija pridobila zahtevane podatke, bo morala narediti veliko zahtev na posamezne mikro- storitve. Pride ravno do problema, ki smo ga omenjali prej. Ta problem lahko rešimo ravno z uvedbo GraphQL. Na prvi pogled imamo dva načina uvedbe: uvedba na posameznih mikrostoritvah ali ustvarjanje nove mikro- storitve, ki bo agregirala več mikrostoritev. Drugi način je veliko boljši od prvega, saj s tem združimo celo- tno aplikacijsko shemo v eno mikrostoritev. Ustvarimo novo dostopno točko, ki omogoča pridobitev poljubnih podatkov iz celotne aplikacije z močjo poizvedovanja GraphQL. Razreševalske funkcije uporabimo kot način pridobivanja podatkov iz preostalih mikrostoritev. Če bi uvajali GraphQL na posameznih mikrostoritvah, bi s tem aplikacijo naredili kompleksnejšo. V arhitekturi mikrostoritev je komunikacija med posameznimi mikro- storitvami ključnega pomena. GraphQL bi to komuni- kacijo upočasnil, saj je pošiljanje poizvedb GraphQL kompleksnejše od pošiljanja poizvedb REST. 5 SKLEP V članku smo predstavili GraphQL, ki je ena od novejših tehnologij za komunikacijo čelnega dela z zalednim. Primerjali smo ga z arhitekturo REST ter predstavili njegove prednosti in slabosti. Na koncu smo se dotaknili tudi arhitekture mikrostoritev, ki je eden od sodobnejših načinov razvoja spletnih aplikacij. Pokazali smo, kako se lahko GraphQL učinkovito uporabi v arhitekturi mi- krostoritev. Glavni cilj pisanja tega članka je bila seznanitev bralca s sodobnimi koncepti spletnih storitev in razvoja aplikacij. Posledično smo v članku nanizali veliko novih pojmov in konceptov, ki jih neizobraženi bralec na tem področju ne bo poznal oz. bodo zanj novi. Zainteresirani bralec se lahko podrobneje seznani s tematiko z bra- njem diplomskega dela z naslovom “Spletne storitve z GraphQL”, ki je javno dostopno na spletni strani Fakul- tete za računalništvo in informatiko [12]. V diplomskem delu se veliko bolj poglobimo v specifike GraphQL in arhitekturo mikrostoritev. Velik del je namenjen tudi programiranju mikrostoritev GraphQL v programskem jeziku Java.