Het gebruiken van shortcuts is heel menselijk. Als je me niet gelooft, kijk dan maar eens naar het olifanten paadje op de afbeelding hieronder. Zou jij niet even een stukje afsnijden?
Vraag je je af wat een gewenst pad is? We hebben het over een een sluiproute, een afsnijweg, een manier om sneller ergens te komen buiten de gebaande wegen om. In onze branche noemen we het meestal een shortcut. Je zou bijvoorbeeld een shortcut kunnen nemen door ergens over een stuk gras te lopen om wat tijd te besparen. Wij mensen zijn geneigd om shortcuts te nemen. Dingen gaan er sneller door en worden en makkelijker van. Dat ligt in onze natuur verankerd. In het Nederlands zeggen we zo mooi: een olifantenpad, omdat olifanten natuurlijk geprogrammeerd zijn om de kortste routes nemen. Zulke olifantenpaadjes zijn uitgebreid gedocumenteerd als fenomeen en worden zelfs op TED podia besproken.
Het gebruik van shortcuts kan er aantrekkelijk uitzien, zelfs tijd besparen, maar het kan je ook wat kosten! Waar ik op doel is het gebruik van shortcuts in de ontwikkeling van software integraties. Wat je in het begin van de ontwikkeling van integraties overslaat kost in veel gevallen later in het proces meer tijd (en dus ook meer geld). Dus pas op voor een kat in de zak!
De juiste en de verkeerde manier van softwareontwikkeling
Wanneer je aan een nieuw stuk code werkt, kun je dat op twee manieren aanpakken. De juiste manier en de verkeerde manier. De juiste manier is iets waarover te discussiëren is. En tot op zekere hoogte hangt het af van je mening over softwareontwikkeling, hoe code hoort te zijn, enz. Maar het hangt ook af van werkwijzen en afspraken die gelden binnen de organisatie waar je voor werkt.
Ook de verkeerde manier is een discutabele term. Er zijn gevallen waarin het snel neerzetten van een stukje code daadwerkelijk voldoende is. Maar die overhaaste aanpak laat vaak niets zien over hoe het concept bewezen of ontkracht kan worden. Het zijn meestal wegwerpprototypes uit de eerste fases. Dat lijkt allemaal niet zo erg, maar in werkelijkheid is de code veel te karig en niet bedoeld voor meer dan een snelle demo.
Minimal Viable Integration (MVI)
In dit blog wil ik het over iets belangrijks hebben. Iets wat wij bij Yenlo ‘minimal viable integration’ (MVI) noemen. Of in goed Nederlands: de minimaal levensvatbare integratie.
Wat ons betreft is er een minimale standaard waar je aan hoort te voldoen als je aan integraties met WSO2 producten werkt. Je wilt bijvoorbeeld niet afdoen aan de security. Maar er spelen meer aspecten een rol.
De basis en risico’s van softwareontwikkeling
Natuurlijk kennen we allemaal de basisregels. Dupliceer geen code, eindpunten en andere ontwikkelingsartefacten. Het kopiëren van code is een reflex die we onder controle moeten houden. Dupliceer geen code zonder er goed over na te denken. Dit zou echt één van de meest fundamentele lessen van alle beginnerscursussen over softwareontwikkeling of configuratie moeten zijn. Als je nog steeds code dupliceert, dan zou je kunnen eindigen met een nachtmerrie aan onderhoud. Bovendien is de kans op fouten groot, veel groter dan bij het normaal gebruik van een functie, bibliotheek of andere manieren van code hergebruiken. Creëer liever individuele functies, diensten en templates om herbruikbare functionaliteiten af te vangen.
Denk na voordat je wat doet!
Ja inderdaad, je zult dingen doordacht aan moeten pakken als je hergebruik en herbruikbaarheid wilt optimaliseren. Weersta de druk om iets snel en afgeraffeld op te leveren. Denk na voordat je iets doet, en zelfs het bespreken met peers zal meerwaarde opleveren voor je team en de betrokkenen bij jouw project. Maar het vraagt meer tijd.
De keerzijde van het verhaal is dat het volledig verlammend kan gaan werken. Dit wordt ook wel analyseverlamming genoemd. Meestal wordt het veroorzaakt door het niet kunnen afbakenen van de gedachten tot een realistische probleemstelling. Je hoeft de grenzen niet te verleggen waar het gaat om complexiteit die buiten de context ligt. Voornamelijk als je dingen gaat manipuleren op een lager niveau (bijv.: veranderen van standaardcomponenten), wordt het steeds moeilijker voor je om te begrijpen hoe mensen jouw werk gebruiken en voor peers worden de veranderingen die je maakt onnavolgbaar. Besef je dat niet iedereen zo briljant is als jij.
Zeven stappen naar een Minimal Viable Integration
Nou, wat is dan deze minimal viable integration waar we het over hebben? Het is de standaard die ervoor zorgt dat het stuk code dat je ontwikkelt geschikt is voor het doel waar het voor dient. Bovendien kan het zaken boven water halen die oorspronkelijk niet beweerd of gevraagd zijn, maar die op lange termijn significante waarde zouden kunnen toevoegen. Vandaar mijn eerdere aanname dat het nemen van shortcuts je duur kan komen te staan. Het volgen van deze zeven stappen vraagt meer van je dan alleen meer tijd, maar het maken van een MVI aanpak is nodig als je de kwaliteit van je integraties wilt verbeteren en de impact van mogelijke fouten wilt minimaliseren. Net als veiligheidsgordels die in auto’s kunnen helpen om letsel te minimaliseren als er iets fout gaat. Het feit dat ze wettelijk verplicht zijn onderstreept duidelijk dat er echt belang bij is.
Hieronder leg ik de zeven verschillende werkwijzen die tot een minimal viable integration leiden in detail uit. Vervolgens benoem ik praktische stappen die je kunt nemen.
Nr. 1 Zorg ervoor dat je op alle situaties rekent
Eén van deze dingen is een keuze van de gepaste mediator. In veel gevallen zijn er meerdere opties om integratielogica met mediators te implementeren. De filter mediator gebruikt bijvoorbeeld een binair IF THEN ELSE, wat afdoende is in situaties waar er tussen twee mogelijkheden gekozen moet worden. Maar bestaat er geen derde optie?
Stel je voor dat je twee afdelingen heb die berichten moeten kunnen ontvangen, afhankelijk van de inhoud van het bericht. Je zou kunnen zeggen dat als het bericht niet voor afdeling 1 is, het dan automatisch naar afdeling 2 moet gaan. Maar wat als er een foutbericht binnenkomt dat voor geen van beide afdelingen bestemd is? De binaire aard van het filter zal het altijd naar één van de twee afdelingen sturen, afhankelijk van de opzet van de code, maar de foutmelding correct afhandelen lukt niet. De oplossing is een extra filter voor foutmeldingen, maar voor je het weet bouw je een verwarrende reeks filters.
In werkelijkheid is er bijna nooit een binair probleem. Andersom gezegd: Er zijn bijna altijd uitzonderingen op binaire regels te noemen. Of op z’n minst mogelijke toekomstige uitzonderingen. Door al direct vanaf het begin de switch mediator te gebruiken, kost de initiële implementatie niet veel meer tijd, maar het zal direct het onderhoud van de code vergemakkelijken.
→ Te nemen stappen: Switch naar switch.
Nr. 2 Verwerk je berichten zorgvuldig
Eén van de eerste lessen die je leert over integratie is dat je moet voorkomen om de ‘state’ te behouden in een integratieservice. Ten eerste beperkt dit de schaalbaarheid. Maar je loopt vervolgens ook risico gegevens te verliezen tijdens een failover. A-B testing en Canary deployments worden complexer en zero-downtime upgrades zouden onmogelijk kunnen worden.
In de praktijk is het verrassend moeilijk om te voorkomen de ‘state’ te behouden,vooral bij integratieservices die langere verwerkingstijden nodig (kunnen) hebben. Er zijn een aantal manieren om zulke integraties robuuster te maken. Ga niet alleen voor lokale opslag. Nooit. Kies liever voor een gedistribueerde objectopslag of een netwerkschijf om je files op te slaan. Gebruik queues wanneer je communiceert over een onbetrouwbaar kanaal. Let op: bij iedere retry vertrouw je erop dat je bericht nog steeds beschikbaar is. Ga er niet vanuit dat je bericht hardnekkig in het geheugen blijft zitten. Er zijn ook tussentijdse states. Als een message path opgebouwd is uit een reeks stappen die niet herhaalbaar zijn, zou je iedere stap bij moeten houden. Overweeg dan om een event pipeline te bouwen om berichtafhandeling te monitoren. Sommige event brokers bieden zelfs de mogelijkheid om verwerking exact op het punt waar dingen mis gingen te herstarten.
Event kanalen hebben daarnaast een extra bonus. Net als onderwerpen maken ze uitbreiding mogelijk. Wanneer je een bestemming zult moeten toevoegen aan je event pipeline, kun je eenvoudig een abonnement op een onderwerp toevoegen. En klaar is Kees!
→ Te nemen stappen: Kies een oplossing voor Message Queues, overweeg een event pipeline
→ Oplossingen: ActiveMQ, RabbitMQ (Queues), Kafka
Nr. 3 Investeer in waarneembaarheid
Ongeacht hoe hard je je best doet om alles kloppend te maken, er zullen dingen mis gaan. Integratie is van nature een verdeelde aanpak en communicatiepaden zijn niet ontworpen op betrouwbaarheid. Pakketten gaan verloren, kabels gaan stuk, poorten in een switch kunnen falen, netwerkopstoppingen kunnen dingen langzamer maken, quota kunnen overschreden worden enz. enz. Al deze dingen lijken maar zelden voor te komen, maar als je op groote schaal werkt, dan zou het aantal gevallen je toch kunnen verbazen. Het is logisch dat je geen tijd wilt besteden aan het analyseren wat er fout ging in de integratie als het probleem aan het netwerk ligt. Aan de andere kant is het veronderstellen dat het probleem aan het netwerk ligt, terwijl in feite de integratie faalt, in bepaalde grensgevallen misschien nog wel erger.
Om bovenop de integratieproblemen te zitten, zul je moeten investeren in waarneembaarheid. Verzamel logbestanden van alle knopen in de verwerkingspijplijn. Registreer cross correlaties. Verzamel statistieken voor analyse van langetermijntrends. Besteed tijd aan een goed werkende log mediator. Bouw aan competenties om je loggegevens snel te kunnen analyseren wanneer dat nodig is. En plan tijd in om je loggegevens regelmatig te inspecteren. Uiteindelijk is voorkomen beter dan genezen.
→ Te nemen stappen: Onderzoek een logoplossing
→ Oplossingen: ELK Stack, Loki, Jaeger
Nr. 4 Opsplitsing in modules
Op het moment dat je de neiging krijgt om een gigantische monitor te bestellen om complexe sequenties te managen is het wellicht tijd om ze op te delen in beheersbare stukken. En als je gaat opdelen, dan het liefst gelijk in stukken die ook in latere integraties gebruikt kunnen worden. De balans hierin vinden zonder door te slaan is best tricky. Modulaire complexiteit kan een uitdaging op zich worden. Het risico zit namelijk in de vorming van oneindige loops veroorzaakt door onderlinge afhankelijkheden. En op vergelijkbare manier zou een event storm kunnen ontstaan die de stream helemaal overspoelt. Gelukkig zijn er recentelijk nieuwe design tools op de markt gekomen om je helpen slim met deze onderwerpen om te gaan.
→ Te nemen stappen: Analyseer je code om te bepalen waar en wanneer opdelen in modules gebruikt kan worden, waarbij zowel prestaties en onderhoud in het oog gehouden worden
→ Oplossingen: Niet van toepassing
Nr. 5 Bezorg onbestelbare post
Als alle andere opties niet werken, zou je ernaar kunnen neigen om maar een foutbericht te laten vallen om de zaak daarmee af te doen. Vaak is dat echter niet het gewenste gedrag. Als er een onnavolgbaar probleem bestaat in de verwerking van een inkomend bericht of bestand (bijv. het bevat ongeldige tekens, heeft te veel tekens voor een bepaalde parameter, mist een verplicht item, heeft inconsistente header of footer, een waarde in een lijst valt buiten de vooraf ingestelde waardes of een array is groter dan verwacht), moet er normaliter iemand naar kijken om het op te kunnen lossen. Soms worden er applicaties gebouwd om scrap berichten af te handelen of verwachte inconsistenties automatisch te corrigeren. Als dat niet gebeurt of niet kan, is het afschuiven naar de stapel onbestelbare post een laatste uitweg. In dat geval hoort er een operator gewaarschuwd te worden om naar het probleem te kijken.
Wees er alert op dat het falen van berichten een domino-effect kan hebben. Als het behouden van de volgorde in een keten belangrijk is, veroorzaakt het falen van één bericht dat een volledige reeks berichten niet verwerkt kan worden. Voornamelijk bij batch processing is gedegen validering van de hele batch misschien een noodzakelijke voorwaarde voordat de verwerking kan starten.
→ Te nemen stappen: Fris je kennis over Enterprise Integratie Patronen op en onderzoek message queues
→ Oplossingen: Regelmatige message queues, incident handling
Nr. 6 Ontwikkel als optimist, test als pessimist
Wanneer het op testen aankomt, zou je niets voor lief moeten nemen. Voor starters: functioneel testen moet volledig zijn. Ieder grensgeval dat je kunt bedenken, zou in je test scripts voor moeten komen. Test niet alleen met de stubs die je gemaakt hebt, maar ook op de daadwerkelijke systemen die je integreert. Zonder uitzondering. En omdat integratie van nature foutgevoelig is, kun je gerust overwegen om met je tests wat chaos te veroorzaken.
Ik weet dat uitgebreid testen enorm veel werk is. Hier kunnen de juiste tools ongelofelijk veel voor je betekenen. Tijdsbesparingen en kwaliteitsverbeteringen bij het testen zijn gegarandeerd. Gebruik bijvoorbeeld een geautomatiseerde conformance scan op je API’s. Of zet een fault injection simulator in. Bedenk dat als je fouten al uitfiltert en opspoort voordat ze problemen veroorzaken, het des te makkelijker is om ze op te lossen. Tegelijkertijd is het zo dat als je met overtuiging duidelijk kunt maken dat een probleem maar beperkte invloed heeft, je geen tijd hoeft te verspillen aan de reparatie om tot een minimum viable integration te komen.
→ Te nemen stappen: Creëer uitgebreide testscripts
→ Oplossingen: Testtools zoals Rest Assured, Postman, SoapUI, maar ook test suites
Nr. 7 Voorkom kortsluiting
Als een service eindeloos herhaald wordt (‘flailing’), kan het verzenden van nog meer berichten het probleem alleen maar erger maken. Zelfs een nieuwe poging na een timeout kan problematisch zijn. Aan de andere kant, een enkele time-out is geen indicatie dat een service ‘flailt’. Het kan een uitdagende taak zijn om zulk gedrag goed te regelen, voornamelijk in grote, gedistribueerde systemen. Op dat punt heb je slimme adapters voor deze diensten nodig, zodat ze niet overspoelen. Een enkel punt in je architectuur dat al het verkeer naar een back-end doorstuurt en genadig omgaat met tijdelijke integratieproblemen. Een API Gateway, bijvoorbeeld.
Een circuit breaker is een geavanceerd algoritme dat intelligent omgaat met tijdelijke integratieproblemen. Het is slim genoeg om een circuit te doorbreken in een knooppunt dat last heeft van ‘flailing’. Het is ook slim genoeg om automatisch opnieuw verbinding te maken als dat weer veilig kan. In feite is het een interactief throttling mechanisme dat de gezondheid van het eindpunt in de gaten houdt. Vooral wanneer jouw minimum viable integration afhankelijk is van componenten van derden, of diensten die nog niet gevechtsklaar zijn, kan een circuit breaker redding bieden.
→ Te nemen stappen: Implementeer een circuit breaker in je API
→ Oplossingen: API Security met 42Crunch, Istio
Neem je tijd en schiet op
Deze 7 stappen verzekeren je ervan dat je op zeker speelt en geen shortcuts neemt waar je later spijt van krijgt. Een shortcut kan je sneller op een bepaald punt brengen, maar niet in de juiste staat. Al het voordeel zou zo in rook op kunnen gaan wanneer je zwakke oplossing datgene dwarsboomt wat je er bovenop probeert te bouwen en waarmee jij jezelf dus eigenlijk op onverwachte momenten in de vingers snijdt.