Services in alle Maten: van Macro naar Nano

Posted on: 27/11/2017 by: Koen Vanderkimpen

Eén van de grote hypes in de wereld van de software architectuur, is het gebruik van MicroServices. Vaak is het "micro" aspect echter een beetje overkill, en maakt men eerder "MiniServices". Deze blog werpt een blik op welke maten en gewichten van services er zoal bestaan.

 

We zullen het daarbij hebben over een aantal categorieën of grootteordes van services: microservices, miniservices en macroservices. En dan zetten we er ook nog zogenaamde nanoservices langs, die eigenlijk (nog?) geen officiële definitie hebben... Al deze architecturele paradigma's zijn in zekere mate geïnspireerd door de reeds langs gekende Service-Oriented Architecture (SOA).

We kunnen nu wel al iedereen gerust stellen: er bestaat geen "one size fits all"; alle groottes van services zullen nuttig zijn in de portfolio van applicaties, diensten en componenten die een IT-bedrijf zal ondersteunen. Deze heterogeniteit wordt ook vooropgesteld door Gartner en ze noemen dit de MultiGrained Mesh App and Service Architecture (MG-MASA). Het belangrijkste daarbij is dat alle componenten hierin zo onafhankelijk mogelijk ontwikkelbaar zijn, terwijl ze toch vlot met elkaar kunnen communiceren en integreren.

Met uitzondering van de dubieuze term "nanoservices", zullen we van klein naar groot gaan: we zullen eerst grondig definiëren wat microservices nu eigenlijk zijn, alvorens de "grotere" services te bespreken.

MicroServices: van Startup tot Web Scale speler

Microservices zijn onafhankelijke diensten met een sterk beperkte scope. Ze volgen rigoureus een aantal architecturale principes waardoor ze extreem wendbaar ( agile ) zijn en ook heel flexibel schaalbaar. Ze zijn zo ontkoppeld mogelijk van andere applicaties en diensten, doch communiceren er ook mee. Deze schijnbare tegenstrijdigheid wordt doorgaans opgevangen door een Event-Driven Architecture, dewelke losgekoppelde communicatie in gedistribueerde systemen ondersteunt.

Definitie: MicroService

Microservices zijn sterk ingekapselde, losgekoppelde, onafhankelijk ontplooibare en schaalbare applicatiecomponenten met een sterk beperkte scope. De microservices architectuur is gebaseerd op een combinatie van SOA en DDD en heeft drie belangrijke objectieven: wendbaarheid bij de ontwik-keling, flexibiliteit bij de uitrol en precieze schaalbaarheid.

De beperkte scope en bijgevolg doorgaans kleinere codebase is ook voor een stuk wat een microservice zo wendbaar maakt: ze kunnen door een klein devops team worden ontwikkeld en onderhouden en meermaals daags opnieuw uitgerold worden met veranderende features. Een grote applicatie die aldus ondersteund wordt door een groep van microservices, zal constant online blijven maar continu kleine veranderingen ondergaan en dus voortdurend evolueren. Op deze manier zorgt de microservices architectuur dus voor extreem door-gedreven Continuous Delivery. Andere aspecten van deze architectuur zijn het principe van "single responsability" (wat op hetzelfde neerkomt als de beperkte scope), extreme schaalbaarheid en - indien de microservice gepersisteerde data beheert, exclusieve toegang (= "eigenaarschap" of ownership) tot zijn database (of andere persistentie-oplossing), in het kader van de loskoppeling.

Microservices zijn op de kaart gezet door toepassing bij een aantal grote en bekende bedrijven als Twitter, Airbnb, Disney, Dropbox, GE, Goldman Sachs, en uiteraard ook grote Cloud spelers  zoals Amazon en Google. Microservices, volgens de strikte definitie, zijn echter bij veel middelgrote bedrijven nog zeldzaam, al evolueren sommige miniservices (zie verder) na een tijdje wel tot een groepje microservices, doordat aparte functionaliteiten worden afgesplitst.

"Reuse": Een anti-patroon ?

Hergebruik wordt vaak gezien als de heilige graal van ontwikkeling: alles wat we reeds bouwden, zouden we moeten kunnen hergebruiken indien de functionaliteit voorkomt in iets anders dat we gaan bouwen.

We moeten echter goed gaan opletten op welk niveau we gaan hergebruiken, zeker als we met de architectuur richting microservices evolueren. Vermits deze zo onafhankelijk mogelijk van elkaar moeten zijn, kan het soms contraproductief zijn om code te gaan hergebruiken tussen meerdere microservices. Dergelijk hergebruik creëert afhankelijkheden en verplichtingen die de wendbaarheid van een microservice doen afnemen.

Waar vinden we dan wel nog herbruikbaarheid? Op het niveau van de APIs. Deze dienen te worden behandeld als een volwaardig eindproduct, dat kan worden gebruikt in tal van andere toepassingen en eventueel zelfs extra-muros.

Ze worden qua aangeboden functionaliteit opzettelijk en strikt beperkt gehouden: normaal gezien wordt er slechts één specifieke feature ondersteund, of één bounded context binnen een domein (voor meer info over deze term, kijk naar Domain Driven Design (DDD)). Doordat ze zo klein zijn en slechts één functionaliteit hebben, en apart uitrolbaar zijn, vormen microservices dus de ultieme vorm van flexibele schaalbaarheid. Een applicatie bestaande uit vele microservices kan optimaal gebruik maken van beschikbare resources: elke component die iets meer gebruikt wordt dan een andere, kan apart opgeschaald worden. Componenten die minder vaak nodig zijn, worden omlaag geschaald.

De onafhankelijkheid van een microservice beperkt zich best niet tot fysiek afzonderlijke deployments. Ook gedeelde libraries kunen namelijk worden beschouwd als een te grote afhankelijkheid tussen verschillende microservices. Op deze manier wordt hergebruik soms een anti-patroon ("anti-pattern"). Het gaat dan natuurlijk wel om de code die het domein implementeert, er is weinig op tegen om utilitaire bibliotheken, zoals een logging raamwerk, te hergebruiken in meerdere microservices.

Microservices vinden we dan ook vooral terug bij bedrijven die op grote schaal ("web scale") actief zijn, en dusdanig grote volumes moeten ondersteunen dat elke uitgespaarde percent benodigde resources een enorme kostenreductie tot gevolg heeft. We denken dan aan grote mediaspelers als Netflix, winkels als Amazon en Zalando, en uiteraard ook de Twitters, Facebooks en Googles van deze tijd.

MicroServices: een andere "ph-waarde": van ACID naar BASE

In de wereld van transacties is het ACID-model reeds lang ingeburgerd. Het stelt voorwaarden waaraan transacties moeten voldoen: Atomicity, Consistency, Isolation and Durability. Dit komt erop neer dat de gegevens ten allen tijde overal overeenkomen, correct zijn, goed bewaard worden, nooit verloren kunnen gaan, etc.

Voor gedistribueerde systemen is dit echter moeilijk, omdat deze sterke consistentie inhoudt dat men (voor de zelfde prijs) op minstens één van twee andere vlakken zal moeten inbinden: beschikbaarheid of partition tolerance (tolerantie voor netwerkfalen tussen de componenten). Microservices hebben hier per definitie last van, vanwege hun gedistribueerde natuur. Dit principe noemt men het CAP-theorema.

Bij gebruik van deze architectuur raadt men dan ook een alternatief aan: BASE. Dit wil zeggen: Basically Available, Soft state, Eventually consistent. In dit model geeft men een stuk consistentie op om het geheel beschikbaarder en schaalbaarder te kunnen maken.

Puur technisch is het echt niet altijd gemakkelijk om met eventual consistency om te gaan; men zal dit model eerdere moeten doortrekken op business niveau. Een aanpak gebaseerd op (business) Events zal doorgaans nauwer aansluiten bij dit model.

Maar doordat het een nieuwe, moderne manier van ontwikkelen is, zijn microservices ook enorm populair bij startups. Deze hebben vaak het voordeel dat ze van nul kunnen beginnen met hun technologie, zonder enige legacy, en dat ze met kleine teams en een frisse bedrijfscultuur werken. Een Devops en Agile cultuur zijn namelijk sterke vereisten om microservices te kunnen ontwikkelen; hun architectuur verschuift namelijk de complexiteit weg van de binnenkant van een monoliet, maar recht naar de onderlinge interacties tussen vele (gedistribueerde) applicatiecomponten. Het voordeel  van microservices voor de startups is weliswaar dat ze op technologisch vlak een stuk robuuster zijn bij eventuele plotse groei, en ze de wendbaarheid ook goed kunnen gebruiken om snel in te spelen op marktopportuniteiten (er zijn reducties in ontwikkelingstijd tot 75% geschat).

APIs en Microservices

Microservices worden vaak beschouwd als de ideale manier om een API te implementeren. Dit is inderdaad een goede werkwijze, maar dan met één hele belangrijke caveat: de onafhankelijkheid van de microservice moet gewaarborgd blijven. Het scheiden van een API en zijn implementatie is een fundamenteel principe bij SOA, dat nog belangrijker wordt in de context van de extreme vereisten die aan een microservices architectuur worden gesteld.

Een API moet behandeld worden als een product. Geregeld worden APIs namelijk naar buiten toe opengesteld (en soms zelfs effectief gemonetariseerd), en op zijn minst dienen ze als interface naar andere software componenten (en dus andere ontwikkelteams) toe. Ze hebben dus nood aan een rigoureus change management.

De microservices daarentegen moeten snel kunnen evolueren en aangepast kunnen worden. Soms worden ze zelfs volledig vervangen (ze zijn disposable: het verlies blijft beperkt doordat ze kleiner zijn). Sowieso zal één microservice ook nooit een volledige API implementeren (tenzij als het echt om een miniscuul klein product zou gaan): achter de API bevindt zich doorgaans een hele batterij van componenten, waarbij verschillende microservices de effectieve business logica zullen uitvoeren. Andere componenten zijn bijvoorbeeld een Event bus, maar ook en vooral een API bemiddelingstool ("mediation"), die ervoor zal zorgen dat de calls naar de API bij de correcte afhandeling terecht komen, en op die manier de microservice loskoppelen van de ondersteunde API. Deze tools zijn doorgaans vervat binnen de context van een API Gateway of API Management suite.

De Pragmatische Aanpak: MiniServices

De term "microservice" wordt vaak misbruikt om eender welke kleine en herbruikbare dienst te benoemen. Maar zoals we hierboven zagen, voldoet dit nog lang niet aan de eigenlijke definitie, en is het ook zo dat een microservice zijn eigen API best niet zelf publiceert.

De categorie die dan ook door vele "enterprise niveau" bedrijven het vaakst wordt toegepast, is eigenlijk de miniservice, ook al zijn deze bedrijven snel om te zeggen dat ze een "microservices architectuur" gebruiken. De architecturen lijken dan ook wel enigszins op elkaar en enkel de grootte en scope (en het uiteindelijke doel) verschillen hier nog.

Miniservices implementeren geen volledige applicatie, en zullen normaal gezien ook slechts een stukje van een extern gerichte API invullen. Ze focussen zich op de functionaliteiten verband houdende met een bepaald subdomein in de applicatie. Wanneer we de principes van Domain Driven Design (DDD) volgen: een miniservice kan een volledig domein implementeren, maar eventueel ook een niet al te kleine "bounded context", of alles wat ertussen ligt.

Miniservices zijn onafhankelijk van elkaar te ontwikkelen en uit te rollen, waardoor er een flexibiliteit ontstaat: doordat men werkt met kleinere componenten die losjes gekoppeld zijn en die men daardoor apart kan ontwikkelen en laten evolueren, kan men iets sneller de functionaliteiten veranderen via een meer agile ontwikkelingsmethode, en men kan dus sneller op de bal spelen. Hierdoor kan men beter ten dienste staan van de business, die sneller zijn product kan laten aanpassen aan veranderende marktomstandigheden. Dit komt dus neer op een afgezwakte versie van de vereisten voor microservices.

Wat data betreft, heeft men opties: in sommige gevallen kan men ervoor opteren om de database die onder een microservice staat, nog bruikbaar te houden voor andere applicaties en diensten; soms is dit gewoon gemakkelijker en efficiënter dan de data via de API bloot te stellen, of op een moderne gedistribueerde manier te verspreiden zoals dit bij microservices gebeurt. Men moet hier echter mee oppassen en het beperken, want hierdoor creëert men quasi onherroepelijk afhankelijkheden. Beter is het om wat de data betreft toch eerder richting microservices architectuur te evolueren en de miniservice exclusieve toegang tot zijn data te geven.

Volgens een schatting van Gartner zal tegen 2019 90% van de organisaties het microservices paradigma te disruptief vinden en eerder gebruik maken van miniservices.

Er zijn, ten slotte, nog een aantal andere alternatieven voor een microservices aanpak, naast miniservices. Zo bestaan er PaaS platformen waarbij er sterk visueel kan worden ontwikkeld (met zogenaamde "zero coding") en men op die manier de productiviteit kan laten stijgen. En voor het implementeren van business processen kan men nog gebruik maken van BPMS suites, die ook hier vaak een sterke automatisatie toelaten op een meer grafische manier.

Old School: de MacroService

Macroservices representeren de traditionele SOA aanpak, die nu sterk in vraag wordt gesteld door de IT industrie. Via een macroservice brengt men een volledige applicatie of dienst online, in één grote deployment; deze ondersteunt dan een typische request/response API. Het woord service kan dan in hoofdzaak worden aangewend doordat men de applicatie of dienst heeft ge-"service enabled". Er wordt m.a.w. een SOAP of (beter!) een REST API aangeboden aan de buitenwereld om met de macroservice te interageren. Vaak is het ook zo dat deze applicatie bovenop een database gebouwd wordt, via dewelke nog wordt geïntegreerd met andere toepassingen.

Momenteel is het sterk afgeraden om op deze manier een nieuwe applicatie op te bouwen, maar deze systemen hebben echter wel nog af en toe hun nut! Vele bedrijven hebben namelijk een bepaald arsenaal aan zogenaamde "legacy" toepassingen, die vaak nog belangrijke taken verrichten, en die te groot en te complex (en dus te duur) zijn geworden om ze volledig te vervangen door een moderne variant die dezelfde kernfunctionaliteit encapsuleert. Wat men dan op z'n minst kan doen om deze applicaties beter te kunnen integreren met de nieuwere generatie componenten, is ze te "service enablen". Dit betekent dat men er een laag rond bouwt die een moderne (REST) API zal aanbieden waarmee andere toepassingen dan gemakkelijker verbinding kunnen maken. De laag zal ervoor zorgen dat de buitenwereld niets hoeft te merken van de soms rare zaken die met de legacy toepassing kunnen misgaan, of van de esoterische programmeertalen en protocols die erin worden gebruikt.

Let wel dat er steeds moet worden afgewogen om zulke applicaties toch niet te re-engineeren. Wanneer het echt om de core business van een bedrijf gaat, en men wil kunnen inspelen op b.v. een veranderende markt (m.a.w. wanneer men wendbaarder, meer agile, wil zijn), kan het op langere termijn soms voordeliger zijn om ze te vervangen door een meer op microservices geïnspireerde architectuur. Het gebruik van MacroServices is eerder geschikt voor applicaties die minder vaak veranderingen dienen te ondergaan, maar die wel via een API moeten kunnen worden aangesproken. Soms kan het ook gaan om aangekochte software, die dus inherent niet onder de eigen controle zit en niet kan ge-reëngineered worden.

De Toekomst is hier: NanoServices

NanoServices kan twee zaken betekenen. Enerzijds is het een door sommige mensen aangewende term om op een anti-patroon bij het bouwen van microservices te duiden: namelijk een te sterke beperking van de scope, op een manier die zinloos is. Het komt er dus op neer dat men goed moet nadenken over de bounded context en het te verwachten aparte gebruik van de diensten die deel uitmaken van een applicatie.

Anderzijds kan men de term ook officieus gebruiken als bijnaam voor de zogenaamde Serverless diensten, of nog Function Platform as a Service (FPaaS). Via deze platformen kan men hele kleine stukjes code uitrollen zonder zich iets te moeten aantrekken van onderliggende infrastructuur, en deze aan te bieden via een kleine API. De granulariteit van de manier van ontplooien zorgt er daarbij voor dat men vaak nog kleiner kan gaan dan bij microservices. Uiteraard moet men erover blijven waken dat het zin blijft hebben om de functionaliteit zodanig in stukjes te kappen, wil men niet in het gelijknamig benoemde anti-pattern terechtkomen.

Serverless Architecture maakt momenteel grote furore in de public cloud en er zijn ook al enkele open source oplossingen om een dergelijk platform in het eigen datacenter op te zetten.

Besluit: Wat moeten we nu Bouwen?

De microservices architectuur is een zeer goede blauwdruk voor software ontwikkeling. De voordelen van wendbaarheid en flexibiliteit zijn een must voor bedrijven die goed willen kunnen inspelen op de markt en de klant, en een robuuste portfolio aan herbruikbare services willen uitbouwen.

De vereisten om aan échte microservices ontwikkeling te doen zijn echter erg hoog, en het is niet altijd noodzakelijk om zulke extreme flexibiliteit en schaalbaarheid te behalen. Vaak is het voldoende om de microservices architectuur eerder benaderend na te streven via het bouwen van zogenaamde miniservices. In de meeste gevallen is dit dan ook de aangewezen manier van werken.

Wanneer legacy toepassingen moeten worden ontsloten en er geen grote nood is aan het wendbaarder maken van deze applicaties, maar wel een ontsluiting via een goede API, dan kan het voldoende zijn om een services laag rond deze applicaties te bouwen en er op die manier een zogenaamde macroservice van te maken, die vanaf dan vlot mee kan integreren met de rest van de portfolio. Denk echter goed na of toekomstige wendbaarheid en flexibiliteit echt wel van minder belang zijn, alvorens dergelijke technische schuld (technical debt) te betonneren.

NanoServices, ten slotte, indien het niet om het anti-patroon gaat, zijn zeker iets waar men mee mag beginnen experimenteren. Het voordeel om zich niets meer van infrastructuur te hoeven aan te trekken en de beschikbaarheid en schaalbaarheid op een quasi magische manier cadeau te krijgen, is niet te onderschatten. In een toekomstige blog wordt er zeker nog teruggekomen op deze Serverless Architectures.