Kako i zašto koristiti Swift na serveru?

Prednosti i mane u primeni Swifta na serveru, na primeru projekta eFirma za koji sam izabrao Swift kao REST API backend rešenje.

Mladen Kobiljski - 14. Novembar, 2017.

Kada je Apple predstavio Swift, isti je ubrzo postao jedan od najbrže rastućih i lako-prihvaćenih programskih jezika. Ovo nikako ne treba da čudi s obzirom na to da je sam Swift zamišljen kao spoj najboljih osobina drugih programskih jezika, nasleđuje već ostareli Objetive-C i koristi momenat u kom ogroman broj programera razvija aplikacije za iOS i MacOS uređaje, zbog čega im je prelazak na Swift logičan korak napred.

Najveći broj ovakvih aplikacija ima neku potrebu za razmenom podataka preko interneta i ako razvijate svoj serverski API, najčešće korišćena rešenja su bila: Java, PHP, Python, NodeJS, C#… ili neka od generičkih rešenja kao što je Firebase. Naravno, fleksibilnost samostalno razvijenog serverskog API je neuporediva u odnosu na generičko rešenje.

Postavljanjem Swifta kao open-source jezika, gotovo istovremeno smo dobili i mogućnost razvoja Swift aplikacija u okviru Linuxa, a samim tim i kreiranje serverskog API-a u istom jeziku kao i same aplikacije. Ubrzo se pojavilo nekoliko frameworka za razvoj ovakvih programa, a kako je jedan od protagonista Swifta na serveru i veliki IBM, lako je uočiti rezultate saradnje između IBM-a i Apple-a na ovom polju.

Naravno, postavlja se i pitanje da li nam je zaista potreban još jedan jezik za razvoj serverskog dela aplikacija pored već dokazanih i rasprostranjehih sistema i da li je takav sistem dugoročno održiv.

Na ovo pitanje bih pokušao da dam odgovor kao neko ko je za svoj projekat eFirma izabrao Swift kao REST API backend rešenje, i susreo se sa mnogim kako pozitivnim tako i ograničavajućim (bar za sada) faktorima same primene Swifta na serveru.

Zašto baš Swift?

Prva stvar koju će vas svako pitati je, naravno, zašto baš Swift pre svih drugih? Sam sistem eFirma je zamišljen kao lični projekat rešenja problema jednostavnog fakturisanja robe ili usluga i automatskog prosleđivanja dokumenata kako vašem komitentu, tako i knjigovođi.

Primarna ciljna grupa su male firme, preduzetnici, freelanceri, advokatske kancelarije — svi oni koji fakturu kucaju u nekom template-u, odštampaju, pa u nekom trenutku nose knjigovođi. I sistem radi samo to. Nema nepotrebnih funkcionalnosti kako što su magacini, kontni plan, zaposleni, plata — svega onoga što je opterećujuće za malu firmu ili preduzetnika.

Naravno, sistem koristi podatke iz Narodne Banke Srbije, omogućava PayPal integraciju, poseduje automatizaciju slanja dokumenata klijentima koji paušalno plaćaju usluge u jednakom mesečnom iznosu i slično, ali bez nepotrebnog širenja na opcije koje nam nisu neophodne.

Prethodno sam imao iskustva sa NodeJS-om (expressJS framework) kao backend rešenjem i Swiftom na iOS platformi. Iako je JS nezamenjiv kao web frontend, na serveru Swift ima niz prednosti.

Za Swift nam nije potreban nikakav interpreter što ga čini daleko bržim na samom serveru i zauzima neuporedivo manje resursa samog sistema. On je i type-safe jezik te vam se ne može dogoditi da eventualno sabirate STRING i INT vrednost, a da to prođe nezapaženo (što nije redak slučaj kada u JS razvijate REST-API).

Takođe, sve greške vam se javljaju kao “compile time error” za razliku od “runtime error” kada se koriste interpretirani jezici. Kako sam prethodno koristio Swift, a zahteve projekta postavljam sam, izbor je donekle bio pojednostavljen, a ishod izbora unapred poznat.

Swift Server Frameworks

Kada je u pitanju odluka o tome koji Swift server framework koristiti, mogućnosti su: Kitura, Vapor, Perfect i Zewo. Zewo je najmanje zastupljen. Perfect je najobimniji, sa najširim spektrom mogućnosti, ali prilično zatvorenom korisničkom bazom. Kitura i Vapor imaju više nego pristupačnu korisničku bazu sa veoma aktivnim Slack kanalima, gde na svako postavljeno pitanje ili nejasnoću neko već ima spreman odgovor, odnosno ideju o rešenju problema.

Ako posmatramo striktno performanse ova dva frameworka (koje u mom slučaju nemaju nekog značajnog uticaja na funkcionalnost sistema), Kiturin low-level API je nešto brži, dok Vapor poseduje obimniji i svestraniji higher-level API.

Najbolja stvar u svemu ovome je što su i Kitura i Vapor modularni, tako da praktično možete koristiti Kituru za low-level routing i slične operacije, a određene Vapor module kao što su konektori sa bazama podataka za druge. Sama sintaksa Kitura frameworka je izuzetno slična expressJS-u.

Takođe, važno je napomenuti da se programiranje sa Swiftom za iOS, MacOS, tvOS ili watchOS platformu u značajnoj meri razlikuje od razvoja za server pre svega zbog toga što u okviru Linuxa imamo praktično ogoljenu verziju Swifta.

Ako ih poredimo, razvoj za iOS, na primer, podrazumeva nekoliko layera koji se nadograđuju jedan na drugi. Ako u tom slučaju krenemo od baznog koji čini Swift sintaksa (func, var, class…), na njega se nadograđuje Standard Library (tipovi podataka, metode, print(), Int…), zatim Foundation (extended data class Date, Data…).

Nad Foundationom imamo, na primer, Grand Central Dispatch kao low level concurrency framework, na koji se nadovezuju Core Graphics, Core Image…i na kraju UIKit. Zapravo, ova nadogradnja nad Foundationom i predstavlja jednim imenom Cocoa Touch Framework.

Kod Swifta je na serveru drugačija stvar. Sintaksa je istovetna, standardna biblioteka podataka takođe, ali Foundation nije kompletan. Naravno, značajan deo Foundationa i nije potreban na serveru, ali istovremeno se može naići i na neka ograničenja o čemu će malo više reči biti u nastavku.

Izuzetno važna stvar za programiranje na serveru je to što je Grand Central Dispatch integrisan, i što je ceo low-lewel API baziran na njemu. Sama činjenica da je Swift za server ogoljen i da ne morate koristiti masu higher-level API-a ne znači da je server programiranje jednostavno. Potrebno je poznavati kako funkcionišu: sessions, routing, HTTP, templates, forms, baze podataka koje su same po sebi ogromne…

Radno okruženje

Ukoliko razvijamo backend u Swiftu, nameće nam se nekoliko manje ili više boljih i prihvatljivih rešenja za postavku radnog okruženja.

Većina bi, naravno, želela da u Xcode-u otvori novi projekat i krene sa pisanjem serverskog koda, ali u praksi to baš i ne funkcioniše najbolje. Za samo generisanje strukture projekta koristi se Swift Package Manager, odakle možemo generisati Xcode project i nastaviti rad na njemu, ali prilikom prebacivanja samog koda na server može doći do različitih problema uslovljenih razlikama samog Linuxa i MacOS-a.

Kao radno okruženje možemo koristiti i računar sa Linux OS-om. Međutim, velika većina programera koji koriste ili će koristiti Swift za backend su oni koji već koriste Mac, te im se nameću sledeća dva rešenja: ili podići virtuelnu Linux mašinu koristeći neki od virtuelizacionih alata (Parallels ili VMware Fusion) ili, po meni, neuporedivo bolju varijantu — Docker kontejnere.

Sama kontejnerizacija (eng. containerization — ovakvi nazivi se ne mogu izbeći jer nema adekvatnog prevoda) i Docker kao rešenje sami po sebi bi kao kratak uvod zahtevali tekst od nekoliko hiljada reči, te ga ostavljamo za neku drugu priliku.

U kratkim crtama ono praktično podrazumeva da na vašem sistemu pokrenete instancu Linuxa iz određenog image-a u zasebnom kontejneru (zamislite da je kontejner praktično nezavisna aplikacija u sandboxu), dozvolite linuxu pristup vašem kodu tako što mapirate direktorijum gde se on nalazi, mapirate portove za pristup kontejneru i razvijate aplikaciju koju je kasnije lako preneti, takođe u vidu kontejnera na server. Isto tako, pri razvoju treba koristiti i mogućnost da svaki deo serverske aplikacije kao što je sam REST-API, baza podataka i drugi nezavisni delovi budu pojedinačni kontejneri.

Sam Docker poseduje svoj Docker Image Registry koji predstavlja nešto što liči na GitHub za kontejnere gde možete pronaći zvanične verzije image-a sa Swiftom, MySQL-om, Mongo-DB, ili skladištiti vaše verzije kontejnerskih image-a. Sami kontejneri međusobno koriste iste resurse na vašem lokalnom računaru, odnosno serveru, te možete pokrenuti istovremeno na desetine kontejnera koji neće imati preterane implikacije na memorijski sistem.

Kako funkcioniše Swift na serveru?

Najbolji pokazatelj samog funkcionisanja sistema vidljiv je kroz primer servisa za prijavljivanje koji je implementiran na eFirma rešenju. Kod sistema autentifikacije korisnika najčešće primenjena rešenja su ona sa korišćenjem ili cookies ili JWT (JSON Web Tokena).

Neuporedivo bolje rešenje je korišćenje JWT, kako sa bezbednosne strane, tako i zbog toga što na samom serveru ne morate da održavate aktivnu sesiju korisnika s obzirom na to da uz svaki HTTP zahtev serveru stiže i JWT koji u sebi sadrži korisničko ime i ostale potrebne podatke, pa se zna ko je uputio zahtev.

Kako se sistem eFirma zasniva na jednovremenom korisniku, odnosno licenca pokriva jednog korisnika sa jednog uređaja, u slučaju da se isti korisnik prijavi na sistem sa drugog računara, prva konekcija gde je korisnik već prijavljen se automatski gasi i nema pravo pristupa podacima.

To znači da sam morao da upotrebim hibridno rešenje koje omogućava kreiranje tokena na serveru koji se prilikom uspešne prijave na sistem skladišti u bilo local storage ili session storage klijenta, i kao takav prosleđuje server API-u uz svaki zahtev. Na osnovu poslatog tokena, server proverava stanje aktivne sesije i identifikuje korisnika kojem pripada dati token. Na ovaj način, samo prilikom prijave šaljemo podatke o korisničkom imenu i lozinci, a svaki naredni put samo token, koji se menja čim se sledeći put prijavimo na sistem. U Swiftu to izgleda ovako:

Prva stvar koju treba da uradimo je da integrišemo neophodne zavisne klase (engl. dependencies) u Package.swift potrebne za našu aplikaciju.

U main.swift fajlu, prvi korak je da unesemo zavisne klase i u startu definišemo ko ima pravo da pristupa našem API-u.

Nakon toga, potrebno je definisati konekciju sa izabranom bazom podataka ili više njih. Za to koristimo Vapor MySQL abstraction layer koji je nešto jednostavniji i lakši za korišćenje od Kiturinog rešenja.

Takođe je potrebno i definisati nekoliko pomoćnih funkcija koje će vršiti enkripciju podataka, ukloniti neželjene karaktere i iščitavati parametre iz samog HTTP zahteva.

Pre definisanje samog REST-API end-point-a, odnosno rute, moguće je aktivirati i data logger, a potrebno je i integrisati BodyParser() middleware koji će se izvršavati za svaku definisanu rutu.

Kada smo definisali pomoćne funkcije i aktivirali middleware, pristupamo definisanju zadatih end-point-a našeg REST-API-a.

Na kraju je neophodno i pokrenuti server na određenom portu.

Naravno, za potpuno funkcionisanje sistema je potrebno i postaviti bazu podataka sa odgovarajućim tabelama i poljima određenih karakteristika.

Generalno gledajući, u nešto više od 100 linija koda sa sve komentarima smo napravili autorizacioni REST-API u Swiftu koji savršeno i sigurno funkcioniše i koji se lako može primeniti na bilo koju web, iOS, Android ili bilo koju drugu aplikaciju.

Ostale mogućnosti Swifta na serveru

Primena Swifa za REST-API servise je idealna jer se dobija brzo i sigurno rešenje koje nema velike zahteve za serverskim resursima i po različitim ispitivanjima, a pre svega zahvaljujući samoj prirodi jezika ne zaostaje za rešenjima koja koriste Google-ov Go programski jezik (koji je namenski i pravljen za web), a daleko nadmašuje NodeJS, PHP, Java, Python, C#….

Isto tako lako Swift na serveru se može koristiti i za razvoj web crawlera ili nekog Content Management Sistema tipa WordPressa i slično. Takođe, svaki Swift framework koristi neki vid templating engine-a (Stencil, Leaf, Mustache) koji vam omogućavaju lako generisanje i serviranje HTML fajlova ka korisniku sa dinamički dodatim podacima koje je obezbedio Swift.

Swift se uz pomoć templating engine-a može koristiti čak i za pravljenje web strana, ali to nikako nije njegova svrha i neke stvari jednostavno ne treba previše komplikovati (iako Kitura sadrži i StaticFileServer). Ako razvijate svoje aplikacije za neku od Apple-ovih platformi, Swift kao backend rešenje je savršeno, ali ne i svemoguće.

Nedostaci

Kada sam krenuo u razvoj Swift backenda za eFirma servis nije bilo nikakvih ograničenja. Sama dokumentacija nije bila preterano obimna, ali i tu stvari polako dolaze na svoje mesto. Nešto literature se da pronaći na internetu. Pre svega mislim na Server Side Swift knjigu Paula Hudsona (Kitura), i nešto tutorijala sa Ray Wenderlich sajta (Vapor). Slack kanali su nezamenljivi.

Ono što nisam mogao da uradim u Swiftu je generisanje .pdf dokumenata na serveru jer još uvek ne postoji neki pdfKit koji radi pod Linuxom, a eFirma sistem treba da generiše pdf fakturu ili predračun, a zatim dokument prosledi klijentu, odnosno knjigovođi.

Takođe, Narodna banka Srbije vam omogućava pristup podacima o registrovanim firmama u zemlji, ali da biste dobili te podatke morate koristiti SOAP protokol i neki xml parser. Pogađate, nema xml parsera, te vam ne preostaje ništa drugo nego da taj deo koda napišete u expressJS-u ili nečemu sličnom. Da ne bude zabune, naknadno je Perfect framework uvrstio i Swift xml parser, ali za moj projekat je taj deo već bio odrađen.

Ako samo na kratko sagledate ostalo što je u projektu na www.efirma.rs urađeno u Swiftu, lako ćete zaključiti da je Swift prilično fleksibilan i moćan u primeni i na serveru.

swift na serveru

www.efirma.rs — aplikacija kod koje je gotovo celokupan backend urađen u Swiftu.

Ono što dodatno doprinosi fleksibilnosti ovakvog backend rešenja je njegova implementacija uz pomoć već pomenutih Docker kontejnera — u konkretnom slučaju: efirma API, Database, NBS data parser, invoice-scheduling, NGINX, kao pojedinačnih entiteta koji se lako mogu individualno skalirati i premeštati sa servera na server ako za tim ima potrebe.

I pored toga što za neke operacije na serveru i dalje postoje ili su jedino moguća neka druga rešenja, i još uvek je neznatan broj gotovih i primenjenih backend aplikacija razvijenih u Swiftu, taj trend će zasigurno imati uzlaznu putanju. Posebno imajući u vidu da će programerima koji rade na Apple platformama biti lako da razvijaju i serverski deo sistema za svoje aplikacije.

Korisni linkovi: