Microservices: kdy dávají smysl a kdy jen přidávají složitost
U větších aplikací časem přirozeně roste nejen množství funkcí, ale i počet souborů, modulů, závislostí a vazeb mezi jednotlivými částmi systému. Orientace v codebase je složitější a i zkušený vývojář potřebuje držet v hlavě víc kontextu než dřív.
V určité chvíli se proto nabízí otázka, jestli už není čas aplikaci rozdělit na několik menších částí.
Na první pohled to dává smysl. Menší služby, menší codebase a jednodušší orientace v jednotlivých částech systému.
Jenže samotné rozdělení ještě neznamená, že se systém skutečně zjednoduší.
Pokud jsou uvnitř aplikace skryté vazby, nejasné odpovědnosti nebo technický dluh, přesun do více služeb tyhle problémy neodstraní. Část složitosti se jen přesune z codebase do komunikace mezi službami, deploymentu a práce s daty.
Proto bych před rozdělením systému řešil hlavně to, jestli je problém opravdu ve velikosti monolitu, nebo spíš v tom, jak se v něm dnes pracuje.
Velký monolit ještě není důvod ho rozdělit
Velká codebase sama o sobě nemusí být problém.
Projekt může mít tisíce souborů a přitom se v něm pořád dá rozumně orientovat, změny jsou předvídatelné a většina úprav zůstává v relativně malé části systému.
Mnohem větší problém nastává ve chvíli, kdy i jednoduchý požadavek vyžaduje projít půl aplikace. Ne proto, že je velká, ale protože není jasné, kde konkrétní chování vzniká, co na něj navazuje a která část systému je skutečným zdrojem pravdy.
A právě tady bych byl s microservices opatrný.
Pokud spolu jednotlivé části aplikace musí při každé změně stejně úzce spolupracovat, rozdělení do více služeb jejich závislosti neodstraní. Jen je přesune z interních volání do API, front, eventů a síťové komunikace.
Sam Newman to ve své knize Monolith to Microservices shrnuje poměrně přesně:
Microservices are not the goal. You don’t ‘win’ by having microservices.
To je podle mě dobré měřítko pro celé rozhodnutí. Microservices nejsou známka vyspělejší architektury ani přirozený další krok ve chvíli, kdy monolit vyroste.
Důležitější otázka je:
Co konkrétně bude po rozdělení systému jednodušší než dnes?
Samostatné nasazování? Škálování jedné části? Izolace provozního rizika? Možnost, aby určitá část systému měla vlastní životní cyklus?
Pokud na to není jasná odpověď a problém je hlavně v tom, že se v codebase špatně orientuje, začal bych nejdřív uvnitř monolitu.
Rozdělením totiž můžeme snadno získat několik menších codebase, ale zároveň složitější systém jako celek.
Cena microservices se přesune mimo samotný kód
Rozdělit aplikaci na několik služeb není ta nejtěžší část. Skutečná cena se začne ukazovat až ve chvíli, kdy spolu ty služby musí spolehlivě fungovat v produkci.
Lokální volání se změní na síťový request. Jedna transakce se rozpadne mezi více systémů. Přibydou timeouty, retry, idempotence, verzování kontraktů nebo potřeba dohledat jeden request napříč několika službami.
Složitější je i debugging. V monolitu často stačí projít jeden request a jeden log. U distribuovaného systému už potřebujete vědět, která služba selhala, co stihla uložit, co se má zopakovat a v jakém stavu zůstaly ostatní části procesu.
Podobné je to s daty. Uvnitř jedné aplikace může spoustu problémů vyřešit databázová transakce. Jakmile ale jednotlivé služby vlastní vlastní data, musíte počítat s tím, že systém může být po určitou dobu v přechodném stavu a některé operace bude potřeba opakovat nebo kompenzovat.
K tomu přibývá provozní režie: deploymenty, monitoring, alerting, logování, tracing, správa konfigurace nebo lokální vývoj s několika závislými službami.
Nic z toho není argument proti microservices. Je to cena, kterou platíme za možnost jednotlivé části systému nasazovat, škálovat a provozovat nezávisle.
Proto je důležité vědět, jestli právě tuhle nezávislost skutečně potřebujeme. Pokud ne, můžeme si velmi snadno přidat distribuovanou složitost bez odpovídajícího přínosu.
Microservices nevyřeší technický dluh
Jedna z věcí, které bych si před rozdělením systému hlídal nejvíc, je motivace celého rozhodnutí.
Pokud se aplikace špatně mění, stejné pravidlo existuje na několika místech a nikdo si není jistý, co všechno konkrétní změna ovlivní, problém pravděpodobně není v tom, že jde o monolit.
Rozdělení takového systému do více služeb může dokonce situaci ještě zhoršit. Stejné nejasnosti zůstanou, jen mezi ně přibudou API kontrakty, eventy a síťová komunikace.
Technický dluh se totiž nerozpustí tím, že ho fyzicky rozdělíme do několika repozitářů.
Pokud například business pravidlo dnes žije na třech místech uvnitř jedné aplikace, rozdělení na služby automaticky neurčí, která služba ho má vlastnit. A pokud to není jasné před rozdělením, po něm bývá rozhodování ještě dražší.
Proto bych nejdřív řešil příčinu zpomalujícího se vývoje a až potom architekturu deploymentu.
Někdy z toho skutečně vyjde, že oddělení určité části systému dává smysl. Jindy se ukáže, že většinu problému vyřeší zjednodušení současného návrhu bez zavedení distribuovaného systému.
Kdy microservices opravdu dávají smysl
Microservices podle mě začínají dávat smysl ve chvíli, kdy jejich nezávislost řeší konkrétní problém, který už se uvnitř monolitu řeší draze.
Typickým příkladem je část systému, která má výrazně jiné nároky na výkon nebo provoz než zbytek aplikace.
Zpracování videa, velké importy, výpočty nebo jiný workload, který se potřebuje škálovat samostatně, mohou být dobrý důvod pro oddělenou službu.
Podobně je to ve chvíli, kdy na jedné části produktu dlouhodobě pracuje samostatný tým a potřebuje vlastní release cyklus. Pokud každá jeho změna vyžaduje koordinaci se zbytkem aplikace, oddělení může začít přinášet skutečnou hodnotu.
Důvodem může být i provozní izolace. Některá část systému může mít jiné požadavky na dostupnost, bezpečnost nebo obnovu po chybě. Pak dává smysl, aby její selhání nebo deployment neovlivňoval celý produkt.
A někdy je důvod technologický. Pokud konkrétní problém výrazně lépe řeší jiný runtime nebo ekosystém, může být samostatná služba jednodušší než tlačit všechno do jednoho stacku.
Například hlavní SaaS aplikace může běžet v PHP, ale zpracování videa nebo machine learning workload může dávat větší smysl v samostatné službě postavené v Pythonu. Důvodem ale není to, že je Python obecně lepší. Jen pro konkrétní problém nabízí vhodnější nástroje a knihovny.
Společné je jedno:
Microservices by měly přinést něco, co uvnitř monolitu reálně chybí.
Nezávislé škálování. Nezávislé nasazování. Vlastní provozní režim. Samostatný tým.
Pokud nic z toho nepotřebujete, je dobré se ptát, jestli za distribuovanou architekturu neplatíte složitostí, která se vám nikdy nevrátí.
Závěr
Microservices nejsou přirozený další krok každého rostoucího projektu.
Jsou způsob, jak získat konkrétní druh nezávislosti — v nasazování, škálování, provozu nebo práci týmů. A za tuhle nezávislost se platí vyšší složitostí systému jako celku.
Proto bych se při rozhodování nedíval na počet souborů ani na to, že monolit už působí „velký“. Důležitější je, co konkrétně dnes nejde dělat dobře a jestli rozdělení tento problém skutečně odstraní.
Pokud potřebujete odděleně škálovat určitou část, izolovat provozní riziko nebo dát samostatnému týmu vlastní životní cyklus, mohou být microservices velmi dobré řešení.
Pokud se ale projekt hlavně špatně mění, nerozumíte jeho vazbám nebo vás brzdí technický dluh, rozdělení systému tyhle problémy samo nevyřeší.
Microservices dávají smysl ve chvíli, kdy řeší konkrétní problém. Ne ve chvíli, kdy jen doufáme, že několik menších služeb bude automaticky znamenat jednodušší systém.