Otisnuli smo se prema pučini i očekivali da će naš pothvat biti brz i jednostavan, praćen sjajnim i vedrim nebom. Ali na vidiku opazih kako se naoblačilo i da moramo mijenjati kurs.
Hoće li ta nevera izvući iz nas nemoguće, nešto što mirno more nikada ne bi?
Trebamo li zaploviti među oblake ili se boriti protiv struje?
Je li prekasno za radikalan manevar – ravno prema gore?
Početak našega putovanja
Sve je započelo, kao što to obično biva, kao mali projekt. Vrijeme je prolazilo, ali stvari se nisu odvijale onako kako smo planirali – svaka prepreka vodila nas je prema Cloud-native aplikaciji.
Još uvijek vjerujem kako se to pokazalo ispravnom odlukom.
Godina je 2018. i dobio sam novi radni zadatak – započeti novi projekt velike tvrtke za grijanje, ventilaciju i klimatizaciju (HVAC) u Europi. Isprva je to bio vrlo mali tim s malim ciljem: trebali smo implementirati novi modul za sustav koji je postojao godinama. Klijent je imao čvrstu reputaciju u izgradnji HVAC sustava, širom svijeta su radili instalaciju opreme i softvera.
No, nedostajala im je sistemska integracija – sve je bilo upakirano u paket desktop aplikacija s pripadajućim bazama podataka i datotekama. Sinkronizacija je podrazumijevala proces uvoza / izvoza koji je bio nezgrapan (svaki po svom modulu). Svaki tim je radio zaseban projekt i rijetko su komunicirali. Nije bilo centralnog mjesta za pohranu podataka i nije se pratilo što se točno događa u sustavu. Također suradnja između zasebnih područja poslovnih procesa je bila skoro nepostojeća.
Trebali smo započeti projekt, koji je inicijalno zamišljen kao proof-of-concept (POC), odnosno postaviti podatke u Cloud i izraditi web aplikaciju. Plan je bio da se web aplikacija instalira na lokalni laptop inženjera koji putuju uokolo i konfiguriraju svoje sustave na mjestima instalacije.
To je bila ideja.
Ali sve će se to uskoro promijeniti …
Plovidba niz vjetar ili inicijalni razvoj na principu “čiste arhitekture”
Za početak smo se odlučili za Microsoft .NET Core, kao naš glavni razvojni okvir, jer smo ga već koristili za brzi razvoj rješenja i njihov prijenos na svaku platformu u Cloud-u. Svi smo dobro poznavali Microsoft Stack. Imajte na umu da u to vrijeme nije bilo Azure DevOps-a, nije bilo Azure Cloud pattern-a, a mikroservisi nisu još doživjeli značajan uzlet.
Koristili smo principe “čiste arhitekture” (clean architecture) za izgradnju projekata i koncentrirali se na dizajn po principu three-layered architecture. Radili smo na kvaliteti koda pisanjem unit testova, usvojili static code analysis i koristili različite alate za suradnju i vođenje dokumentacije. Kao tim smo već imali pozadinu u primjeni različitih rješenja, a napredovali smo zahvaljujući inicijativi za primjenom nama poznatih i dobro uhodanih pattern-a.
Our initial application diagram based on the Clean architecture
Iz današnje perspektive, izgledalo je možda prejednostavno i neprikladno u ovom slučaju, no trebali smo krenuti od nekuda. Putem smo rado prigrlili nove ideje i poboljšanja u kodu. Bez obzira na to koliko je arhitektura dobro dizajnirana – uvijek je dobro vratiti se postojećem kodu, pogledati ga iz drugog kuta i pokušati ga poboljšati.
Rješavanje cross-cutting problematike
Uz sve to, trebali smo izgraditi pouzdanu strukturu podataka kako bi ovaj sustav funkcionirao, a ubrzo je postalo jasno kako sve treba integrirati ako ćemo to raditi u Cloud-u. Cross-cutting problemi kao što su provjere autentičnosti, autorizacije, skupljanje logova i auditing ubrzo su postali iznimno važni da bi se obrađivali samo lokalno – samo na jednom stroju.
Odlučili smo se koristiti OpenConnect i OAuth standarde zbog sigurnosnih razloga jer smo tražili dobro usvojena i prihvaćena rješenja za sve ono što smo namjeravali graditi. Usuglasili smo se za Identity Server 4 i brzo kreirali single-sign-on za naše rješenje. Dobro dizajniran sustav za adresiranje provjere autentičnosti JWT tokena, fleksibilan, uz mogućnost proširenja za ostale pružatelje sigurnosti i prikladan za nas jer ga podupire ASP.NET.
Authentication of clients via Identity Server 4
Kasnije smo implementirali vlastiti sustav autorizacije kao proširenje sigurnosnog rješenja temeljenog na identitetu jer niti Azure AD niti druga rješenja nisu bila dovoljna za naše potrebe. Budući da smo modelirali kontrolu pristupa zasnovanu na atributima, zajedno s RBAC-om, bilo je potrebno stvoriti zaseban servis koji će nam pružiti zaštićeni kontekst, zajedno s modelima podataka i REST API sučeljem za pristup njegovoj funkcionalnosti.
Prilikom audita smo koristili serverless computing odnosno Azure funkciju koja obrađuje zahtjeve servisa i pretvara ih u unose baze podataka. U početku su servisi bili usko povezani s funkcijom zbog pružanja podataka, no ubrzo smo shvatili da se to neće dobro skalirati i ne će biti pouzdano rješenje, ali morali smo pričekati neko bolje vrijeme kada će se to optimizirati.
Kada nam je trebala suradnja s drugim rješenjima koristili bi posebne servise ili Azure funkcije bez poslužitelja ili Azure Logic Apps, ako je tijek obrade uključivao više koraka i trebalo ga je grafički prikazati i konfigurirati. No, kao i kod ostalih funkcionalnosti, osjećali smo da nešto nedostaje. Rješenje koje smo osmislili nije odgovaralo poslovnim potrebama osim ako distribuiranu arhitekturu ne dignemo na novu razinu.
Serverless computing used for addressing cross-cutting concerns
Klijentske web aplikacije
Istodobno smo razvijali aplikacije za web klijente izgrađene na paradigmi Single-Page Application (SPA), implementirane na Angular 4 i TypeScript, i povezivali ih sa pozadinom putem sučelja REST API. Imali smo sreće da su svi involvirani bili dio istog tima jer smo dijelili znanje i osjećaj kvalitete koda. Klijentske aplikacije koje sadrže standardnu funkcionalnost konstruirane su kao NPM biblioteke za ponovnu upotrebu iz projekata specifičnih za domenu. Projekti bazirani na Angular-u interno su strukturirani podmodulima – slijedeći modularni pristup u izgradnji SPA-a. Ubrzo smo predstavili SignalR kao našu implementaciju komunikacije u stvarnom vremenu putem WebSockets-a između klijenata i poslužitelja.
Modular approach in building SPAs in Angular
A broj backend servisa i klijentskih aplikacija je nastavio i dalje rasti…
Odabir prave Cloud platforme
Do sada nismo bili koncentrirani na rješavanje poslovnih zahtjeva jer su se razvijali s nama ili ponekad nisu bili jasno definirani. Stoga su zahtjevi za kvalitetom postali naš stup u određivanju onoga što će uskoro postati – platforma u Cloud-u. Naš se početni projekt razvio u potpuno razvijenu distribuiranu platformu. Adresirali smo autentifikaciju, obavijesti, upravljanje korisnicima, upravljanje modulima i auditom – a sve s odvojenim poslovnim servisima koji komuniciraju s klijentima putem HTTP-a.
Nakon predstavljanja ove platforme i uvjeravanja da je potrebno ići prema distribuiranoj arhitekturi koja se sastoji od servisa i aplikacija, koristeći Microsoft Azure kao ciljanu platformu u Cloud-u i uvodeći DevOps proces, sve je postajalo jasnije. Počeli smo učiti i koristiti mnoge PaaS resurse koji su nam dostupni na Azureu i naviknuli se, ne samo na planiranje, implementaciju i testiranje koda, već i na razmišljanje o infrastrukturi, čimbenicima QoS-a poput skalabilnosti, sigurnosti, dostupnosti i elastičnosti. Bilo je zabavno zaroniti u PowerShell kako bi pratili logove aplikacija i konfigurirali servise putem portala, stvorili ARM predloške, osigurali resurse i koristili Azure Monitor da bi vidjeli kako se servisi ponašaju u radu.
Definiranje DevOps procesa
Ovo je bilo vrijeme kada se Azure DevOps pojavio kao nasljednik VSTS-a, a mi smo ga rano usvojili. Ubrzo smo stvorili svoje prve CI / CD procese omotane oko DevOps projekata koje smo povezali s našim Git repozitorijima. Build pipelines koriste grupe zadataka koje se izvode na CI trigerima koji su povezani s izvornim kodom zasnovanim na GitFlow-u. Ubrzo su se pojavili release pipeline-i koji nastavljaju s build artefaktima kako bi ih rasporedili na različitim okruženjima. U početku smo započeli s mnogim projektima koji su konfigurirani za svaki naš Git repozitorij, ali postupno je postalo teško održavati ovu strukturu. Kasnije smo prešli na jedan projekt za cijelu platformu i natjerali naše pipeline-e da dijele svoje grupe zadataka, varijable i artefakte.
Our first Azure DevOps CI/CD process workflow
Počeli smo s Azure App Service slot-ovima, ali ubrzo smo shvatili da jednostavno imamo previše servisa, a naš App Plan dosegao je maksimalno ograničenje servisa. Trenutno imamo test, QA, i18n i preview okruženja na našem Dev / Test Azure-u – ovisno o namjeni (razvojni tim, QA tim, tim za lokalizaciju, prodaja). Ovo je dobar primjer implementacijske strukture za razvoj i testiranje, osim što je sve statički provizionirano na uslugama Azure App Services. To će se promijeniti kada započnemo s kontejnerizacijom.
Broj servisa i dalje raste, a taj rast održavamo zahvaljujući DevOps infrastructure-as-a-code. Kreirali smo ARM templates za stvari koje provizioniramo, a neke resurse dinamički stvaramo u kodu. Također izvodimo svakodnevne zadatke održavanja koji provjeravaju stanje servisa, konzistentnost baze podataka i nadzora razmjene poruka.
Modularna arhitektura
Kako vrijeme odmiče, nadodaju se novi projekti s različitim poslovnim potrebama, a mi smo usvojili modularnu arhitekturu s projektima izgrađenim s nekoliko servisa, aplikacija i serverless kodom. Bilo je jasno da ne možemo izdržati isključivo pružajući Service-Oriented Architecture (SOA), bilo nam je potrebno nešto “veće”.
Kako je svaki servis trebao znati kontekst upravljanja korisnicima (koji je na zasebnom servisu), a koji je trebao biti ovjeren i ovlašten, servisi su počeli imati reference na druge servise, koje smo spakirali u NuGet pakete. Iako dijeljene biblioteke općenito nisu loša praksa, ne postoji odvojeni deployment– čak ni razvoj, budući da svaka promjena u referentnoj biblioteci uzrokuje grešku servisa. Nismo mogli nastaviti raditi na novim funkcionalnostima jer smo si stvorili daljnje nedosljednosti, a smjer je bilo teško držati jer su postojeći projekti nasljeđivali iz bazne biblioteke. Na nekim projektima je to bilo jednostavnije, ali ih nije bilo lako za pratiti.
Both applications and services introduce new challenges
Bili smo zabrinuti i zbog toga što bi nam se mogao dogoditi raspad sustava zbog jednog jedinog neispravnog servisa (single point of failure). S obzirom da su svi servisi i aplikacije koristili izravnu međusobnu komunikaciju, svaki kvar pojedine komponente uzrokovao je kvar cijelog sustava. Ništa nismo mogli učiniti da popravimo situaciju. Bilo je izazovno pronaći problem na pravom mjestu, jer je svaki servis sadržavao log, neovisno o servisu koji je pružio dio funkcionalnosti (primjerice ako se pojave sigurnosni problemi, pogreška se širi na različite servise koji su ju koristili) .
No, prekretnica se zaista dogodila kad smo se trebali koristiti multitenancy. Budući da je pružanje podataka koji se odnose na različite partnere (organizacije, područne urede ili tvrtke) značilo da su podaci vertikalno podijeljeni, bilo je očito da se komunikacija između servisa (i aplikacija) mora optimizirati i staviti u rigorozan redoslijed. Nije bilo neophodno samo pružiti podatke, već ih pružiti precizno kome i kada su potrebni.
Stoga, mikroservisi su nam se nametnuli kao idealno riješenje na našem putu.
Čeka nas novo odredište na ovom putovanju i novo neispričano poglavlje u ovoj jedinstvenoj storiji.