Mijn eerste ervaring met Hashicorp Consul was toen ik wat onderhoud aan een bescheiden Springboot applicatie toegeschoven kreeg waar mijn overijverige voorganger dit framework aan had toegevoegd. Met Consul kun je een gedistribueerde en gerepliceerde applicatie-omgeving opzetten en draaien (lees: microservices). Alleen hadden we maar één service en één instantie, dus was de inzet volstrekt overbodig en alleen ingebouwd ter lering en vermaak, zo bleek. Tja, microservices. Je zou denken dat ze inmiddels wel het hypestadium voorbij zijn. Toch kom ik ze nog regelmatig prominent tegen in de must-have vaardigheden voor projecten die niet echt de schijn hebben van een hyper-scaler waar je beslist microservices voor nodig hebt. Maar ik kan het mis hebben.
De eerste stap en tegelijk de grootste uitdaging naar een microservices architectuur is er een van slim opknippen. Hoe breek ik mijn probleemdomein logisch, behapbaar en toekomstbestendig op in individuele services? Maar dat vertellen ze je niet, ook niet in het verder verhelderende boek Spring Microservices in Action (Manning). Het is een technische tekst over allerhande ondersteunende diensten en abstracties die je nodig hebt om als programmeur onbezorgd te kunnen coderen. Stel dat ik straat en postcode wil valideren voor een willekeurige Europees adres. Daarvoor roep ik een service aan die op basis van het land mijn verzoek doorsluist naar een andere service. Ik wil in mijn client-code niet zelf de IP-adressen of wachtwoorden bij hoeven houden van al die services. En het moet stabiel zijn: ik heb geen zin in handmatige retries. Om mij als verwende programmeur daarin te faciliteren voorzien volwassen microservice-omgevingen in tenminste het volgende:
- Resilience strategieën. Fouten binnen één service mogen niet het hele landschap tot stilstand brengen. Zo heb je circuit breakers, die als een aardlekschakelaar de toegang tot een trage service afsluiten. Het bulkhead pattern zorgt dat instanties van eenzelfde service in afgescheiden pools draaien, zoals de waterdichte compartimenten van een schip voorkomen dat één scheur in de romp niet het hele schip laat zinken. Denk even niet aan de Titanic…
- Service discovery wil zeggen dat je één loket hebt waar service-instanties zichzelf aanmelden. Clients zoeken die services op via datzelfde loket en de load balancer kan meteen verbinding leggen met een beschikbare instantie.
- Service routing zorgt o.a. voor een centrale afhandeling van rechten; wie ben je en wat mag je zien/aanpassen? Ook bovenstaand voorbeeld van de postcode validatie valt onder service routing: op basis van de inhoud van een bericht wordt het naar de juiste service gestuurd.
- Last but not least: je hele deployment proces moet geautomatiseerd zijn en beschreven in code. Je moet zonder handmatige stappen een volledig nieuwe omgeving neer kunnen zetten. Dan ontkom je bijna niet aan virtualisatie en containerisation.
Niet toevallig levert Netflix vier populaire frameworks om dat alles mogelijk te maken: Eureka voor discovery, Hystrix voor resilience, Ribbon voor load balancing en Zuul voor routing. Ze werken allemaal prima samen met Springboot en de Cloud plugin(s). De materie is niet triviaal, maar de integratie leunt vooral op annotaties en vergt weinig code. Achter de schermen wordt je zoveel complexe logica uit handen genomen dat je wel gek zou zijn om dit zelf te implementeren.
Het lijkt veel en dat is het ook. Heb je dit allemaal nodig om microservices te doen? Moet je weer een boek van 500 pagina’s doorploegen? Ik vrees van wel. Microservices zijn nu eenmaal niet simpel en zonder deze essentiële diensten zijn ze helemaal een recept voor ellende. En als je toch zeker weet dat je wel zonder die Netflix (of vergelijkbare) stack kunt omdat je maar vier services hebt die dezelfde Oracle DB bevragen, dan heb je ook niet te maken met een microservices architectuur en moet je het ook niet zo noemen.