Azure Functions iliti kako kupiti njivu preko SMS-a

Imao sam dva skroz dijametralno suprotna use case-a gde sam želeo da dobijem brza obaveštenja (po mogućstvu SMS) o “ponudama” (oglasima). Npr. hteo sam da kupim njivu, ali sam provalio da mi je OK da čekam da se njiva pojavi na doboš preko banke. Drugi use case je da sam hteo da kupim mobilni telefon. U prvom slučaju, nisam želeo da obilazim sajt banke svaki dan za nešto što se pojavi svaka 3 meseca, a u drugom sam želeo jako brza obaveštenja kad se u ponudi pojavi jako jeftin telefon (koji se obično proda za 2-3h od objavljivanja).
Rešio sam da oba problema rešim na isti način, koristeći što više iz Azure stack-a što je moguće. Ne, možda se nismo razumeli, stvarno sam overarchitect-ovao što je više moguće, koristeći što novije i što hipsterskije tehnologije.

<cinizam>

da se ne lažemo, sve ove tehnologije su stare koliko i računarstvo, samo su implementacije drugačije…

</cinizam>

Ovo nije bilo uzaludno iz dva razloga: prvo, naučio sam dosta novih stvari, a i ovakvo rešenje bi imalo smisla za neke veće sisteme, gde skaliranje stvarno može biti problem (ali ne, definitivno ne i za kupovinu njive).
Jedini constraint koji sam imao je da želim da isprobam Azure Functions – serverless ekvivalent AWS-ovim Lambdama. Međutim, bilo je tu par problema, pa evo da prođemo sve natenane.

Šta su Azure funkcije

(preskočite ovaj pasus ako ste upoznati sa Azure funkcijama, ovo je samo uvod)

Azure Functions (ili Azure funkcije, po naški), su Microsoft-ov odgovor na AWS Lambde. Način da se implementira stateless arhitektura što, u zavisnosti od vaše biznis logike, može dosta da uštedi para (plaćate samo izvršavanje funkcija, ali ne i ceo VM koji zvrji prazan ostatak vremena). Slanje SMS-a i skrejpovanje sajtova je baš dobar primer. Ako želite više da znate o Azure funkcijama, krenite odavde.

<cinizam>

Opet moram da se umešam. Nema ništa inherentno stateless u stateless arhitekturi, samo vi ne morate da brinete o tome. Sviđa mi se kako je to sročeno ovde: Serverless = “someone else is responsible for these servers going down”

</cinizam>

Ukratko objašnjenje je da vi pišete samo jednu funkciju (u jeziku u kom hoćete, dosta ih je podržano), i definišete šta je:

  • trigger (kada se funkcija startuje, može biti npr. tajmer, može biti kad stigne nešto na message queue…)
  • input (šta vam je ulaz u funkciju, može biti npr. blob, Document DB dokument…)
  • output (šta je izlaz iz funkcije; može biti sve što i input, ali i razne druge stvari, kao što su HTTP request, slanje mail-a, ili ono što ćemo mi ovde iskoristiti – slanje SMS-a preko Twilio platforme

Za sva ova tri gorepomenuta (trigger, input i output) niste ograničeni na samo jedan, već ih može biti i više od jednog (npr. output je u našem slučaju i Table Storage i Twilio SMS, videćete kasnije), i svi su definisani kao argumenti u toj vašoj funkciji koju pišete.

Ima tu još par gremlina na koje sam naletao, ali ih i dosta brzo rešavao uz pomoć dokumentacije i SO-a, tipa kako da dodaš nove nuget pakete, kako da funkcije dele zajednički kod, a pošto Azure funkcije koriste web apps ispod, sve ostala pitanja su dobila automatski odgovor (kao kako da postavim environment variable, kako da upload-ujem fajl FTP-om, a čak i Kudu radi sa adrese https://<function_app_name>.scm.azurewebsites.net).

Overall, rešenje je bilo prilično očigledno – skrejpuj sajt unutar Azure Functions-a, sačuvaj to negde i pošalji SMS. Al’ ne lezi vraže…

Gde čuvati state sistema u stateless arhitekturi

Prvi problem ovde je bio gde čuvati state sistema, tj. gde čuvati već obrađene njive, odnosno telefone. Od ponuđenih opcija, Azure Functions je nudio Document DB i Table Storage, ali ne i npr. SQL. Hteo sam nešto jeftino i lightweight, pa sam se odlučio za Azure Table Storage (Document DB mi je bio preskup za ovu namenu, mada mislim da bi on bio bolji izbor kad bi ovo trebalo da skalira).

Jedna zanimljiva stvar na koju treba paziti i koja može da vas ujede je da Azure Functions može pokrenuti vašu funkciju više puta konkurentno. Mislite o tome ako vam treba atomičnost. Prost primer je da se dve funkcije pokrenu paralelno, u funkcijama se skrejpuju iste ponude, onda se provere u obe da li postoje u Table Storage-u, i pošto ne postoje, da se pošalje SMS dva puta iz obe. Pazite se!

Kako merge-ovati ponude

OK, sada kad znamo gde je state, kako Azure Function-u da kažemo da ne želimo da ubacimo novi red ako postojeći već postoji. Ispostavlja se da tako nešto ne postoji (što ima logike pošto Azure Functions radi samo sa nekim ulaznom i izlaznom komponentom, ne sa hipotetičkim ulazom koji zadovoljava neki query). Tu sam morao da zasučem rukave i da napravim upit koji će proveriti da li postoji već takva ponuda u Azure Table Storage-u. Da se razumemo, nije ovo teško, nego nije bilo u duhu Azure funkcija. Što se tiče primarnog ključa u Table Storage-u, on podržava dva odvojena entiteta – PartitionKey i RowKey (pretpostavljam da su značenja jasna iz imena). Nekako mi je bilo logično da za PartitionKey postavim sajt sa koga skidam ponudu, tj. tip ponude (njive, telefoni, avioni, kamioni…), a da RowKey dobijem od sajta i da on bude specifičan za datu ponudu.

Ima ovde još jedna bitna stvar vredna pomena. Azure funkcije rade po principu da, ukoliko ste naveli neki output (Table Storage, u našem slučaju), on nije opciona stvar, već Azure očekuje da prosledite novi red i tačka. U ovom slučaju, mi želimo red samo ako ponuda ne postoji već u tabeli. Srećom, Azure funkcije podržavaju ovo, i to tako što output funkcije nije objekat T koji se upisuje u tabelu, već ICollector<T>. Ovakav napravljena kolekcija dozvoljava da krajnji izlaz bude i 0 redova, ali i više od jednog reda!

Decoupling različitih tipova ponuda

Osnovna ideja je da možemo da imamo različite tipove ponuda (njive, telefoni) koje prolaze kroz sistem. Malo bi glupo bilo da sva skrejpovanja svih sajtova budu u istoj Azure funkciji. Takođe, period skrejpovanja za mobilne telefone (npr. 15 minuta) nije isti kao i za njive (max. jednom dnevno). Naravno, kad god je ovakav decoupling u pitanju, uvek je odgovor naš dobar drugar message queue. Azure Functions podržava taj scenario (queue može da bude trigger), a Microsoft-ovo rešenje se zove Azure Service Bus. Naravno, format ponuda koje ćemo trpati u queue će biti JSON (zato što je XML so 1990s). Sada je iz aviona jasno rešenje – napraviti N različitih Azure funkcija, gde će svaka da skrejpuje po jedan tip sajtova sa ponudama, trigger će im biti tajmer, a output će im biti Azure Service Bus. JSON koji pumpaju u Service Bus može da bude kakav god dictionary, ali mora da ima ključeve “partition” i “id” u njemu. Sa druge strane queue-a je funkcija kojoj je trigger Service Bus, a kojoj je zadatak da čita ponudu iz Service Bus-a, proveri da li zadata ponuda postoji već u Table Storage-u, a ima dva output-a – jedan je opet Table Storage (opcioni, i ima ga samo ako se pojavi nova ponuda koja treba da se upiše), a drugi je, takođe opcioni Twilio SMS servis.

Krajnje rešenje

Posle svih ovih problema, evo i šematski prikaz kako izgleda ovaj moj overarchitect-ovani Frankenštajn:

Jeste ružno, ali i treba da bude ružno. Inače, cela ova zezancija košta oko 1$ mesečno (plus 1$ za Twilio SMS servis), što je mnogo manje nego da sam uzeo VM na koji bih npr. potrpao neki Python plus Mongo. Nisam ekspert, a nisam ni probao AWS Lambdu da bih dao bolji pregled i imao bolju referentnu tačku, ali evo stvari koje treba da se unaprede, po meni:

  • Podrška za SQL (ima nas matorih koji bi ipak koristili SQL) i druga NoSQL rešenja
  • Podrška za lokalno testiranje (to danas nije moguće)
  • Twilio SMS servis nije radio, morao sam “ručno” da ga dodajem (ovaj OOB je imao problema) – loš prvi experience za Micorosft i Azure.

Ako vas zanima neki detalj o ovome što sam pričao, ceo source code je dostupan na GitHub-u. Ako vas ne zanima nijedan detalj i sve vam je jasno, možete uvek otići na ovaj link da vidite moj broj telefona i da mi me pozovete i zahvalite na kristalno jasnom opisu Azure funkcija:)

Evo i za kraj kako to izgleda na portalu:

Posted in Programiranje | Tagged , , | Leave a comment

Kapilarni prsti neminovnosti

Ovo je još jedan tekst u navali tekstova na koji ćete naići ovih dana koji pokušavaju da analiziraju gde smo to “mi”, građani pogrešili. Još jedan u nizu koji ćete čitati klimajući sa odobravanjem, dok prelistavate share-ove vašeg malog bubble-a ljudi, bubble-a koji ste napravili na Facebook-u, Twitter-u, dok jedete vaše organske salate i vegeterijanske poslastice. I pitate se kako je opet krezubi Sale Nađ iz Malog Iđoša uspeo da nas nadglasa, ovaj put ovako efektno. Pa prosto, zato što je Sale u pravu!, a evo i u čemu je stvar.

Prva greška u razmišljanju je da smo mi iznad Saleta i da je naš izbor edukovaniji, pametniji, lepši…nije. A ovo često radimo, neverovatan je unconscious bias koji viđam u mom balonu, pre svega (a i ja to ovde radim, sa namerom karikiranja). To nas dovodi do druge stvari na koju smo potpuno slepi, a to je da su naše potrebe na Maslovljevoj hijerarhiji potreba mnogo zadovoljenije od jadnog Saleta Nađa – dok mi razmišljamo o kupovini čia semenki i povređenoj vladavini prava u Hercegovačkoj, Sale ne zna šta će jesti sutra – ali bukvalno! To je taj odnos između long-term i short-term razmišljanja. Možda Saša Janković i jeste dobar long-term izbor za Srbiju, ali 55% ljudi očigledno ne zanima taj dugoročni izbor, već ti prsti neminovnosti, neminovnosti sutrašnjeg dana. I ako tako pogledamo, Saša Janković (pa čak ni Beli) tom napaćenom Saletu nije ništa ponudio. Sale Nađ se batrga i davi u okeanu neizbežnosti, voda mu ulazi u nos i uši, očajnički u ropcu traži malo kiseonika, a jedino što čuje kroz huk vode u ušima je “Za vladavinu prava. Za građanske slobode. Da se ne plašite. Da Vam vratim osmeh na lice”. E, pa jebeš to, Saletu to ne treba! Kao da stojiš pored davljenika i umesto da mu daš ruku, ti mu pričaš da svi učesnici plivanja u moru moraju da prođu obavezni kurs prve pomoći. Ono što Saletu treba je jedan lep dogovor između Vučić D.O.O i njega, gde će on za glas toj narastajućoj masi da obezbedi još par dana oduška od svoje short-term neminovnosti, a možda i više. Gde će on zauzvrat dobiti tu kapilaru, taj glas, i proslediti onda taj kapilarni glas dalje, a znamo šta kapilare rade – donose preko potrebni kiseonik tkivu koje odumire. A Saletu davljeniku treba kiseonik. A da se ne lažemo, SNS više nije stranka, nije čak ni koalicija stranaka, to je uigrano preduzeće, sistem, uigrana narastajuća masa koja baš i zavisi od tog tkiva koje odumire.

I zato, svi vi pametni građani i građanke, krem ovoga društva, koji ste ogorčeni na glupog Saleta, dok pijuckate vaš kapućino na Obilićevom vencu, razmišljajući o tome kako malo smanjiti kredit za stan i broj kilograma, ne ljutite se na Saleta Nađa – on nije kriv, on je uradio šta je morao i uradio je najpametnije za njega. Ne gledajte ga sa visine na toj Maslovljevoj piramidi potreba, samo zato što je on na njoj niže. Da parafraziram onu dečiju pesmicu: “Lako je intelektualcu da se sokoli, dok traži građanska prava, njega ne boli”. Ti ćeš sledeće nedelje da protestuješ šetnjom kroz grad sa decom, pa posle malo u Mek, a on će žutu patku možda videti jedino na stočnoj pijaci u Bačkoj Topoli dok bude prodavao rezani duvan.

I nemojte mi počinjati sa tim da je Sale sam kriv – nije. Sale je jednostavno ispod 50-og percentila životnog standarda i da nije on, bio bi neko drugi. Možemo diskutovati da li je sistem podešen tako da Sale nema izbora, ili zašto nam je ta medijana životnog standarda tako niska, ili zašto nije bilo kandidata koji bi obećao možda malo kraduckanja u zamenu za malo poboljšavanja sistema, ali jedno je definitivno – sistem nećemo promeniti bez Saleta Nađa (a ne vidim da autistični Saša Radulović i Saša Janković imaju taj kapacitet i tu harizmu). Izvinjavam se što ovde ne ulazim u predloge rešenja, ali to je tema za poseban tekst.

Na kraju, jedino što me plaši je da se kapilare ne prošire previše i ta narastajuća masa ne dobije previše kiseonika. I eto nama onda metastaza. I da Srbija onda ne završi kao Sale Nađ iz istoimene pesme.

Posted in Politika | 2 Comments