Jak nedělat mikroservisy II

Petr Jůza
OpenWise Tech blog
Published in
7 min readMar 4, 2021

Tímto článkem volně navazuji na “první díl” a rád bych se opět vrátil k několika tématům týkajících se realizace mikroservis, zejména z pohledu, kdy nezačínáme na zelené louce, ale máme stávající aplikaci (monolit) a chceme přejít na mikroservisní architekturu.

Začnu dobrou zprávou — Martin Fowler ve svém článku MonolithFirst píše:

I’ve noticed a common pattern:

1. Almost all the successful microservice stories have started with a monolith that got too big and was broken up

2. Almost all the cases where I’ve heard of a system that was built as a microservice system from scratch, it has ended up in serious trouble.

Pattern: Strangler application

Jedním z hlavním důvodů je ten, že již znáte danou business doménu, samotnou aplikaci jste určitě nenapsali napoprvé, ale prošla evolucí menších či větších změn, doménový model je vyladěný a stabilní (nebo aspoň víte, kde jsou jeho slabiny), znáte funkční a nefunkční požadavky a máte už prostě nějakou zkušenost, na které můžete stavět — dokážete si už sami mnohem lépe představit, jak takový monolit “říznout”, kde jsou ty vhodné hranice mezi jednotlivými službami. Tyto informace jsou k nezaplacení.

Škálovatelnost?

Už jsem se k tomu rozepisoval v prvním článku, ale je nutné si opravdu umět odpovědět Proč mikroservisy? A pokud si už odpovíte, tak být k sobě dostatečně upřímný, tj. potlačit technické ego a porovnat “co mě to přinese” vs “co mě to bude stát”. Nejčastěji slyším důvod škálovatelnosti, ale upřímně si myslím, že vaše potřeby v pohodě pořeší i dobře napsaný monolit. Více mě už dávají smysl organizační důvody, kdy agilní vlna prochází i velkými společnostmi u nás a díky tomu sílí tlak na vlastnictví za vybrané businessové funkčnosti, kdy každý tým se stává zodpovědný za své dodávky od zadání až po provoz v produkci. V použití různých programovacích jazyků v “normálních” firmách jsem také spíše skeptický, i když si dokážu představit, že různé domény pořeším lépe vhodným výběrem programovacího jazyka. Musím si ale být vědom toho, že se mně zvýší provozní nároky a sníží se schopnost spolupráce mezi jednotlivými týmy (sdílení know-how, sdílené knihovny, sdílení lidí apod.)

Každý při své činnosti sleduje své vzory a pro mě je např. Netflix dlouhodobě vzorem a inspirací jak dělat mikroservisy (zejména v době, kdy Netflix byl průkopníkem tohoto přístupu před pár lety). Ale je potřeba umět kriticky porovnat potřeby Netflixu a mého projektu. To není jen o škálovatelnosti, to je o všech hlediscích, které potřebujeme při realizaci mikroservis zvážit — pokud plánuji mít vyšší jednotky služeb, pak nepotřebuji investovat do celého aparátu CI/CD, provozu, monitoringu atd. tolik, jako když těch služeb mám vyšší desítky nebo stovky. ServiceMesh (např. Istio) je určitě hodně užitečná věc a mě jako vývojáři velice pomůže s řadou věcí, které bych jinak musel řešit na úrovni samotného kódu, ale nastavení a vyladění Istio není nic jednoduchého, je to přidání dalšího stupně komplexity do již tak složitého (distribuovaného) prostředí.

Principles Of Microservices

A co na to data?

Mikroservisy by měly být na sobě co nejvíce nezávislé, aplikační, datová a ani jiná závislost není žádoucí. Z tohoto důvodu je nutné oddělit data pro jednotlivé služby, tedy co mikroservisa tak ideálně zcela samostatný datový zdroj. Toto je cílový stav, který nelze vždy dosáhnout hned v prvním kroku, zvláště když začínáme od monolitu.

Decentralizovaná data

Často lidé předpokládají, že mikroservisy rovná se NoSQL databáze. Ano, je to asi ideální stav, zvláště když chceme škálovat nebo z pohledu využití zdrojů je NoSQL mnohem více “lightweight”, ale nikde není řečeno, že nelze použít klasické relační databáze. Minimálně pro první tranzitní kroky od monolitu k mikroservisám.

Mikroservisy představují distribuované prostředí a díky tomu musíme mít neustále před sebou omezení daná CAP teorémem a tzn. eventuální (případnou) konzistencí (našel jsem i pěkný článek od Dagiho k tomuto tématu). Jedná se o změnu myšlení, nelze se vždy spolehnout jako u monolitu na to, že data uložíme v lokální transakci a budeme mít potvrzené konzistetní uložení dat.

Jednotlivé mikroservisy se mezi sebou vzájemně různě volají a dohromady skládají nějakou business funkčnost. Kromě potřeby umět tyto volání technicky sledovat (logging, monitoring, tracing), tak často potřebujeme i audit těchto volání, vědět businessově co se přesně stalo a v jakém pořadí, tj. potřebuji sledovat a někde evidovat posloupnost změn resp. událostí, které se staly, např. uživateli se změnila adresa.

Nejen kvůli auditům zjistíme, že je někdy výhodnější se na jednotlivé datové operace dívat pohledem atomických událostí s určitým významem než jen jako “bezejmenné” CRUD operace. Na konkrétní události mohou reagovat nezávisle na sobě jiné služby, pokud daná událost nese i konkrétní význam, pak mohu odlišit “změnu adresy jako její opravy” a “změnu adresy z důvodu stěhování” a zareagovat pokaždé jinak. Aby mohly tímto způsobem mikroservisy mezi sebou komunikovat, tak nezbytným předpokladem je přítomnost nějakého “message brokeru” umožňující asynchronní komunikaci. Technologicky buď ActiveMQ Artemis jako typický představitel message brokeru nebo Apache Kafka, která má blíže k datové streamovací platformě (pozn. i když existuje průnik ve funkčnosti ActiveMQ vs Kafka, tak přesto existuje několik zásadních rozdílů, které popisují následující články RabbitMQ vs. Kafka nebo When to use RabbitMQ or Apache Kafka?).

CQRS data stores

Toto už má blízko k přístupům jako CQRS a event-sourcing, které jsou oblíbené v mikroservisní architektuře. Tyto koncepty se lépe vysvětlují než realizují, nejčastější chyby jsou většinou dvě — první se zdá být nechtěné vytvoření závislosti mezi mikroservisami přes jednotlivé události. Je to asynchronní, přes samostatné události, ale přesto často vznikne přímá vazba mezi změnami v jedné službě, samotnou událostí a následně i cílovými službami, které událost konzumují. Je nutné k návrhu, publikování a správě událostí přistupovat podobně jako u API — mít funkční governance resp. metodické postupy jak postupovat při realizaci určitých požadavků a mít nástroj pro evidenci a správu těchto událostí. Druhá nejčastější chyba se týká toho, že jak je vše řízeno událostmi a to vše asynchronně, tak se v tom za chvíli nikdo nevyzná, kde a kdo všechno vlastně daný business požadavek řeší. Proto jsem třeba v tomto kontextu více zastáncem orchestrace než choreografie.

A hlavně je zase nutné říci, že si musím dostatečně zdůvodnit použití těchto přístupů, protože mě to opět přináší další úroveň složitosti a rizika. Nejčastějšími důvody jsou složitý doménový model, kdy rozdělením pohledu na zápis a čtení dat se mě řada věcí zjednoduší a pak performance důvody, např. pokud potřebuji data mnohem více číst než zapisovat. A nikdo nás nenutí používat CQRS pro všechno a všude, jen tam, kde to dává smysl, ideálně v rámci vybraného bounded kontextu.

Potřeba reportingu generuje další požadavky na samotná data, často potřebujeme data z více datových zdrojů seskupit, připravit pohledy přes více business domén, napočítat agregovaná data.

Od datového monolitu k mikroservisám

Máme dvě mikroservisy, které si potřebují sdílet data. Těch možností jak to udělat je celá řada, nás teď ale více zajímají ty možnosti, které nám pomohou na naší cestě od monolitu k mikroservisám. Tedy nabídnou nám rychlé řešení než se postupně dopracujeme k cílovému řešení, kdy budeme mít zcela oddělené mikroservisy a každá bude mít svoje data, na sobě zcela nezávislá.

Lze využít následující integrační strategie:

  • Shared tables
  • Database view
  • Database materialized view
  • Database trigger
  • Transactional code
  • ETL tools
  • Data virtualization
  • Event sourcing
  • Change data capture (CDC)

První tři možnosti jsou velice podobné, jen se liší v úrovni, na které se data sdílí. Jsou to jednoduchá a rychlá řešení, bohužel většinou mají dvě zásadní nevýhody — vytváří silnou vzájemnou závislost a možné performance problémy.

Další dvě možnosti (triggery, funkce/procedury) mají tu výhodu, že se již jedná o oddělené databáze a dávají flexibilitu v tom, jaká data a jakým způsobem se budou sdílet. Nicméně pořád jsme se nezbavili silné závislosti.

ETL a virtualizace jsou přístupy, které bych osobně spíše použil pro řešení reportingu resp. datových skladů, kdy budu mít zcela nezávislé datové úložiště, kam budu přesouvat data z jednotlivých zdrojů, abych nad nimi mohl dělat souhrnné pohledy.

Event-sourcing jsem již zmiňoval, toto je již celkem pokročilá a netriviální technika, která nás posunuje k “opravdovým” mikroservisám.

Pro Change data capture použiji definici z jednoho článku na toto téma:

Change data capture (CDC) is a process that captures changes made in a database, and ensures that those changes are replicated to a destination.

V této kategorie je velice populární nástroj Debezium. CDC se nehodí jen “k tupému přesunu dat”, ale je velice užitečným pomocníkem při implementaci Outbox patternu.

Debezium Architecture

Zde pro další studium doporučuji knížku Microservice database migration guide (volně ke stažení v PDF).

--

--