Omvang en complexiteit bepalen de moeilijkheidsgraad van softwareontwikkeling.

23-02-2016

Dit artikel is gepubliceerd in Automatiseringsgids
Complexiteit is aan te pakken door het systeem op te delen. Maar in de praktijk gaat het steeds fout. In een microservicesarchitectuur worden modulariteit en ontkoppeling afgedwongen, zegt Martin van Amersfoorth. Deze aanpak lijkt zeer geschikt voor het bouwen van grootschalige softwaresystemen.

In softwareland bestaat soms de neiging om alles wat nieuw en radicaal anders is, direct te omarmen als het volgende wondermiddel voor alle problemen. Dat geldt ook voor microservices, een nieuwe benadering voor het ontwikkelen van bedrijfssoftware. Ook in Nederland kunnen we er niet omheen. Het komt ons tegemoet via tweets en blogartikelen, afkomstig van softwaregoeroes tot praktijkmensen. En er gaat tegenwoordig geen conferentie voorbij zonder dat het m-woord valt. Neem J-Fall, de jaarlijkse conferentie voor Java professionals in Nederland van afgelopen oktober: bijna de helft van alle sessies ging erover. Wat zijn microservices, welke problemen lossen zij op, en wat maakt deze benadering zo radicaal anders dan de vorige? Waarom zouden we die willen omarmen? En als we er aan willen beginnen, wat zijn dan de valkuilen?

Complexiteit

Er zijn twee factoren die het bouwen van software zo moeilijk maken: omvang en complexiteit. Daardoor is het succes van een softwareproject in hoge mate afhankelijk van de manier waarop hiermee wordt omgegaan. Ten eerste, hoe om te gaan met grootschaligheid? Anders gesteld, hoe eet je een olifant? Het antwoord is iedereen bekend: je snijdt ‘m in stukken, je roept al je vrienden erbij en je eet ‘m samen op, één hap tegelijk. De menselijke maat is belangrijk en die geldt evengoed voor het bouwen van software van enige omvang: je deelt het systeem op in stukken, je stelt projectteams samen en begint met bouwen, één module tegelijk. Het probleem van complexiteit lost men op door het systeem op de juiste manier op te delen. De juiste manier betekent: modules zodanig functie hebben, en daarnaast dat ze goed te combineren zijn met andere modules. Zodat het resulterende systeem als geheel precies doet wat de business vraagt. Deze benadering is niet nieuw. Want we zijn in deze industrie al langer gewend om in onze ontwerpen de principes van modulariteit en ontkoppeling toe te passen. Het probleem is echter dat het telkens misgaat zodra het ontwerp de tekentafel verlaat. De modules worden samengevoegd tot één grote monolitische applicatie, met alle gevolgen van dien:

Onderhoudbaarheid
Eenmaal samengevoegd is er geen goede manier om modulariteit en ontkoppeling blijvend af te dwingen. Met als gevolg dat ontwikkelaars de principes straffeloos kunnen omzeilen. De resulterende spaghetticode zorgt ervoor dat het systeem moeilijk is te onderhouden.

Marktintroductietijd
Naarmate de monolitische applicatie groeit, duurt het langer voordat de verschillende stadia naar productie zijn doorlopen.

Robuustheid
Er is een continuïteitsrisico: fouten worden niet geïsoleerd, want een fout in één module kan een storing van het systeem als geheel tot gevolg hebben.

Voor bedrijven zoals bijvoorbeeld Netflix is het essentieel dat hun systemen continu in de lucht zijn. Ook dat ze die systemen kunnen blijven vernieuwen, en dat die vernieuwing snel bij de eindgebruikers terechtkomt. Daarom heeft men bij Netflix een aantal jaar geleden besloten om zijn monolitische applicatie om te bouwen naar iets wat men nu een microservicesarchitectuur kan noemen.

Heterogeniteit

In een microservicesarchitectuur worden modulariteit en ontkoppeling systematisch afgedwongen. Modules worden niet samengevoegd tot een monoliet, hun heterogeniteit wordt juist benadrukt, door ze in hun eigen proces te runnen. Daarnaast worden modules met elkaar gecombineerd via (bij voorkeur) losse, asynchrone communicatie. Elke module is een microservice, die je kunt begrijpen als een klein, zelfstandig, maar combineerbaar programma, met een eigen interface (dat kan een gebruikersinterface zijn, een API of beide), eigen applicatielogica en een eigen database. Het krijgt de verantwoordelijkheid voor slechts één bedrijfsfunctie, niet meer en niet minder. Voor een webshop zou je bijvoorbeeld kunnen denken aan de microservices ‘Catalogus’, ‘Zoeken’, ‘Bestellen’ en ‘Afrekenen’. Voor meer complex gedrag worden microservices gecombineerd. Het is echter niet mogelijk om direct in elkaars code te wroeten. De verantwoordelijkheid van microservices is afgebakend, en de ene microservice zal de diensten van een andere microservice moeten aanspreken via een aanroep van de API. Microservices zijn beter onderhoudbaar dan monolieten, omdat je te maken hebt met kleine programma’s. En kleine programma’s zijn makkelijker te doorgronden. Naast het beperkte aantal regels code heb je ook te maken met alleen de voor die microservice relevante libraries en niet met de dependency hell die je soms bij monolieten ziet. ‘Microservices passen in je hoofd’ is een andere manier om aan te geven dat met deze technologie de menselijke maat wordt gerespecteerd, waardoor het werk voor de ontwikkelaar weer overzichtelijk wordt gemaakt. Doordat microservices in hun eigen proces draaien is het tevens mogelijk om ze onafhankelijk uit te rollen. Het veranderen c.q. verbeteren van een bedrijfsfunctie heeft daarom slechts impact op in beginsel één microservice. In sporadische gevallen kunnen dat er wel meer zijn, maar de uitrol van één microservice heeft zeker geen impact op het systeem als geheel. Updates van verschillende microservices hoeven elkaar niet te doorkruisen, en daarom is ook geen gecoördineerde inspanning nodig om de wijzigingen door de verschillende stadia naar productie te brengen. De marktintroductietijd van microservices is dan ook veel gunstiger dan die van monolieten.

Microservice-architecturen zijn robuuster dan monolieten, omdat zelfstandig opererende services fungeren als bulkheads in geval van een storing. Dit betekent dat de haperende microservice de fout niet propageert naar de rest van het systeem, waardoor het systeem als geheel nog naar behoren kan blijven functioneren. De voor die microservice relevante bedrijfsfunctie is dan even niet beschikbaar, maar de continuïteit van de dienstverlening is wel gegarandeerd. en bouwen van grootschalige softwaresystemen. Betere continuïteit van de dienstverlening en een kortere doorlooptijd voor het uitrollen van nieuwe features zijn immers goed voor de business. Het is ook een aantrekkelijk alternatief voor ontwikkelaars, omdat het veel van de bekende frustraties rondom moeilijk onderhoudbare software wegneemt. Microservices maken het werk voor ontwikkelaars dus leuker. Alleen is het wel een valkuil om te denken dat het werk daarmee ook makkelijker wordt. We hebben immers te maken met gedistribueerde systemen. In het nieuwe programmeermodel moet daarom rekening worden gehouden met asynchrone communicatie, het optreden van storingen en eventual consistency. Dat maakt het werk voor de ontwikkelaar lastiger en betekent bovendien ook extra overhead. Een gedistribueerd systeem is tevens lastiger om uit te rollen en in de lucht te houden. Het blijven hanteren van een strikte scheiding tussen ontwikkelaars (development) en beheerders (operations) is een andere valkuil. Ontwikkelaars zullen rekening moeten houden met de infrastructuur waar hun programma’s terechtkomen. En beheerders zullen moeten kunnen omgaan met een infrastructuur die in toenemende mate programmeerbaar is. Kortom, geen scheiding maar convergentie: een DevOps-cultuur. Een laatste valkuil is de idee dat een microservicesarchitectuur goedkoper is. De versimpeling van programma’s zal naar verwachting wel productiviteitswinst opleveren, maar die zal teniet worden gedaan door de premie die je betaalt voor de overhead van gedistribueerde systemen. Daarnaast vereist een DevOps-cultuur ook DevOps skills. Voorzover niet aanwezig, zal moeten worden uitgegaan van extra kosten voor werving of opleiding.

Principiële keuze

Microservices zijn een benadering voor het ontwerpen en bouwen van grootschalige softwaresystemen. Een benadering die niet per se nieuw is, want de basisrecepten modulariteit en ontkoppeling worden al veel langer toegepast. Het radicale aan microservices is dat men daarin veel verder gaat, door het systematisch afdwingen van modulariteit en ontkoppeling. Die principiële keuze levert een aantal onmiskenbare voordelen op, met name qua robuustheid, onderhoudbaarheid en marktintroductietijd. Voordelen die echter niet behaald kunnen worden zonder de inzet van een reeks ondersteunende technologieën en bijbehorende vaardigheden ten aanzien van gedistribueerde systemen en programmeerbare infrastructuur. En daar zitten ook meteen de valkuilen: onvoldoende beheersing van die technologieën zorgt ervoor dat microservices voor velen nog een brug te ver zijn.

Grondprincipes Microservices

Service-oriëntatie
Net als bij SOA gaat het bij microservices om opdeling van een systeem in services, in lijn met businessfuncties. Het verschil is de manier waarop services worden verpakt, namelijk in kleine, zelfstandige componenten.

Shared-nothing
Een microservice is een zelfstandig programma, beschikt over eigen resources en is daarom geïsoleerd van andere microservices. Een hoge mate van isolatie maakt een systeem veerkrachtig.

Single responsibility
Een microservice heeft slechts één afgebakende verantwoordelijkheid, niet meer en niet minder. Er zijn geen trade-offs nodig en daarom kan die ene taak optimaal worden uitgevoerd.

Modulariteit
De mate van zelfstandigheid en combineerbaarheid van softwarecomponenten. Bij een microservicesarchitectuur wordt uitgegaan van een modulair ontwerp, omdat het makkelijker te realiseren is en ook beter onderhoudbaar.

Ontkoppeling
De mate waarin de ene component is afgestemd op de interne werking van een andere component. Microservices zijn in hoge mate ontkoppeld, omdat componenten in hun eigen proces draaien en alleen zijn te benaderen via een laag van indirectie, namelijk een API.

Robuustheidsprincipe (Postel’s Law)
Bij het leveren respectievelijk aanroepen van services geldt: wees streng in wat je teruggeeft, maar wees tolerant in wat je accepteert. Hierdoor kan het systeem evolueren, met behoud van stabiliteit.

DevOps
Het geautomatiseerd inrichten, uitrollen en in de lucht houden van software. DevOps is mede gericht op continuous delivery: het vermogen om software hoogfrequent en met een korte doorlooptijd te kunnen opleveren, inclusief bouwen, testen en installeren.

Polyglot programming/persistence
Het fysiek scheiden van componenten heeft als bijkomend voordeel dat deze kunnen worden gerealiseerd met de technologie naar keuze: de beste programmeertaal, de meest adequate libraries, de meest geschikte database.

Martin van Amersfoorth
Solution Expert Maatwerk Software