Sitemap

12. Complexiteit

Complexiteit in data integraties

Door de oorlog in Oekraïne zijn de energieprijzen flink gestegen. Bakkerijen, ook die van Aleksandra, hebben hier behoorlijk last van. Alle machines en ovens gebruiken nu eenmaal behoorlijk veel energie. Daarnaast is het een competitieve markt met veel aanbieders.

Het is aan Aleksandra steeds vernieuwend te zijn en daarbij ook op de kosten te letten. Ze heeft daarvoor nieuwe zuinige machines aangeschaft om haar assortiment uit te breiden. Daarnaast heeft ze een productieplanningssysteem dat per product de medewerker kan inplannen.

Een groter assortiment, meer afnemers en meer applicaties. Nu begint het echt ingewikkeld te worden. De processen zullen strak geregeld moeten worden, zodat er geen bottlenecks ontstaan en ze nog te volgen zijn voor het personeel.

Press enter or click to view image in full size

Complexe integraties

Wanneer de business cases ingewikkelder worden en de datavolumes en het aantal applicaties groeien, worden de data integraties ook complexer. In deze gevallen zul je zeker een strategie moeten hebben om met de complexiteit om te gaan.

Maar zelfs als het bedrijf nog klein is en use cases simpel lijken, kan een integratie snel veranderen van dit:

Press enter or click to view image in full size

in dit:

Press enter or click to view image in full size
Dataflow art

Waar komt die complexiteit vandaan? En wat kun je ertegen doen?

Vaak wordt een integratie ingewikkeld doordat de integratie niet vanuit data en architectuurprincipes wordt opgesteld, maar vanuit applicaties en use cases. Dit is ook logisch, omdat:

  • de business meestal de opdrachtgever is voor het bouwen van integraties
  • de business realiteit ingewikkeld is en niet makkelijk 1 op 1 in programmatuur kan worden gevat
  • het vaak de opdracht is data op een plek te krijgen en de verschillen tussen applicaties te overwinnen

Praktijkvoorbeeld

In de praktijk gaat het bijvoorbeeld zo:

Businessopdracht:“We hebben een nieuwe webshop waar op eenvoudige wijze taarten en gebak kan worden besteld. De webshop heeft alle informatie van artikelen nodig om te tonen op webshop. We moeten het centrale systeem aan deze nieuwe webshop koppelen”.

De analyse

Het centrale systeem bevat het volledige assortiment, alle artikelen staan hier in. Echter, de functie van het systeem is vooral gericht op logistiek en productie. Dus er staan allerlei gegevens in over de artikelnummers, de leveranciers en grootte van verpakkingen. Dagelijkse wordt het totale artikelbestand als CSV geëxporteerd naar een FTP-server.

De webshop heeft meer een commercieel doel. Het idee is om de taarten mooi te tonen met goede foto’s. Daarnaast is het wettelijk verplicht informatie over ingrediënten en allergenen te vermelden. Deze informatie staat niet het centrale systeem, maar in de cloud-applicatie met recepten.

Aleksandra geeft aan dat er behoorlijk wat haast bij is. Er wordt eigenlijk van uitgegaan dat de website in 6 weken live is. De bouwers van de website zijn met wat dummy data namelijk al maanden bezig. Ze hebben inmiddels een API en verwachten de data in JSON-format. Alleen de integratie moet nog gebouwd worden.

De bouw

Er zijn drie systemen:

  1. ERP systeem: Data in CSV formaat.
  2. Recepten, ingrediënten en allergenen: Data in XML formaat.
  3. Webshop: Data in JSON formaat.

Doordat er niet echt standaarden en architectuurprincipes zijn, ligt de verantwoordelijkheid voor het overbruggen van de verschillen volledig in de integratielaag en daarmee in feite bij de ontwikkelaar van de integratie.

De ontwikkelaar geeft daarbij wel aan dat hij wijzigingen liever per artikel wilt ontvangen, omdat de webshop het ook zo ontvangt. Maar dat is lastig, omdat het ERP-systeem al op de nominatie staat om uit te faseren en het zou teveel tijd en geld kosten om het aanpassen. Er is maar zes weken tijd. De leverancier van de ERP-applicatie doet altijd eeuwen over aanpassingen.

Goed dan bouwen we het wel op de volgende manier:

Press enter or click to view image in full size

Dit lijkt nog redelijk recht-toe-recht-aan, maar in de praktijk loopt de bouwer tegen wat problemen aan:

  • er moet worden bijgehouden welk bestand en artikel is verrijkt, dus er zal een header moeten worden gezet
  • het systeem met ingrediënten heeft een maximum van 50 requests per minuut. Het assortiment bevat 800 producten. In dit geval dienen de gesplitste artikelen te worden gethrottled
  • het systeem met ingrediënten maakt gebruik van oAuth2, dus daarvoor moeten tokens worden bewaard en andere zaken worden geregeld
  • soms zijn artikelen niet te vinden in ingrediëntensystemen. Dan dient hiervoor een foutafhandeling te worden gebouwd
  • de verrijkte data en de oorspronkelijk data moeten worden samengevoegd. De oorspronkelijke data kan niet worden gestuurd naar de receptenapplicatie en moet tijdelijk worden bewaard
  • de webshop geeft soms een HTTP OK (200 code), maar er zit dan toch een error in het bericht. Daar zal speciale foutafhandeling voor nodig zijn die de inhoud van de response checkt
  • het idee is bij te houden welke artikelen zijn verstuurd, zodat daar geen discussie over ontstaat. Voor het versturen dienen de responses van de webshop gelogd te worden
  • er zal nog een aparte integratie gebouwd moeten worden voor de plaatjes
  • enzovoorts

Elke keer als er een bewerkingsstap wordt toegevoegd, bijvoorbeeld splitsen, verrijken of transformeren, dan heeft dat impact op de performance van het integratieplatform en de complexiteit van de integratie. Elke toevoeging en wijziging zal dus goed overwogen moeten worden.

Totaal complex

Het totaal van alle integraties vormt de complexiteit van de integratielaag.

De complexiteit wordt bepaald door:

  1. het aantal integraties
  2. het aantal stappen per integratie
  3. het intern of extern plaatsvinden van de bewerkingsstappen (latency)
  4. afhankelijkheden (andere systemen/flows)
  5. het aantal verwerkte bytes (= aantal berichten * gemiddelde berichtgrootte)
  6. batch of realtime
  7. het exchange pattern (Fire-And-Forget of Request-Reply)
  8. de integration patterns (splits, loops, verrijkingen etc)
  9. queueing and throttling
  10. de grootte van een bericht

De complexiteit heeft invloed op hoe makkelijk het is om de integratie te wijzigen en te testen. Ook heeft het invloed om oorzaken bij problemen te vinden als het fout gaat, vertraging in de verwerking en de performance van de specifieke integratie of het platform in zijn geheel.

In productie

De start van de ontwikkeling van de integratie verliep moeizaam. Ten eerste duurde het verkrijgen van informatie, zoals referenties voor de FTP server, wat lang. Daarnaast moest de firewall aangepast worden. Ook is de mapping met de webshop al een paar keer aangepast. Maar na verschillende testen kan hij na 8 weken in productie (de release van de webshop was 2 weken uitgesteld omdat de webshop nog niet klaar was).

Op productie lopen we direct tegen een probleem aan, omdat een veld voor allergenen niet in het bericht zit en voor een beperkt aantal producten wel nodig is. Goed, tijd voor weer een nieuwe release. De integratie doet functioneel precies wat het moet doen. Fijn weekend.

Maar zaterdagochtend wordt er gebeld. Het artikelbestand is ‘s morgens om 4 uur klaargezet, maar is niet aangekomen bij de webshop!

Na een onderzoek blijkt dat het artikelbestand te groot is en dat het hele middleware systeem een “Out of Memory Exception” kreeg. Gelukkig draait de machine in Azure, dus kan het geheugen vergroot worden. Jammer is dat daardoor ook de orders vertraagd zijn, want die lopen op dezelfde server. Ook de foto’s gebruiken veel geheugen en vertragen andere integraties. Besloten wordt de foto’s via een ander systeem te laten lopen. Er is weer rust in de tent.

Hoe problemen op te lossen?

Integratieproblemen kunnen op drie manieren worden opgelost:

  1. ontwerp
  2. meten
  3. isoleren en schalen

Ontwerp

Voorkomen is beter dan genezen. Door een integratie goed te ontwerpen kunnen veel problemen later in productie worden voorkomen. Een ontwerp maak je op basis van architectuurprincipes en ontwikkelrichtlijnen. Een voorbeeld is dat het integratieplatform alleen tekstdata (CSV, XML, JSON) verwerkt, geen binaire data (zoals foto’s). Binaire data kan lastig worden bewerkt, en zip-bestanden, foto’s en video’s zijn vaak groot. Het kan dan beter zijn deze via een ander platform, zoals MFT (Managed File Transfer), te laten lopen. Een aanvullende richtlijn zou kunnen zijn dat XML en JSON te prefereren zijn boven andere tekstformaten.

Get Raymond Meester’s stories in your inbox

Join Medium for free to get updates from this writer.

Het gebrek aan ontwerp zorgt dat er vaak per use case gewerkt wordt. Elke integratie (per ontwikkelaar / per use case) is anders. Door standaarden zijn de integraties voor iedereen makkelijker te volgen. Ontwikkelaars, ook nieuwe, staan dan voor minder verrassingen, omdat elke integratie hetzelfde is opgebouwd.

Daarnaast is het een bekend gegeven dat de integratielaag als ‘lijm’-laag wordt gezien. In dit geval zijn integraties de enige plek om zaken te overbruggen. Dat scheelt weer aan de applicatiezijde, maar kan de integratielaag negatief beïnvloeden. De integratielaag puur als lijmlaag te zien, een laag om applicaties aan elkaar te knopen, levert vaak geen solide integratieplatform op. Het is dan alsof je applicaties via duct-tape aan elkaar hangen.

Press enter or click to view image in full size

Het beste is naar het geheel kijken, zowel in de aanschaf van systemen, als in de aanpassing van een systeem aan het applicatielandschap.

In de case met de artikelenintegratie zou bijvoorbeeld het bronsysteem beter de berichten per artikel in XML/JSON kunnen aanleveren. Hierdoor kun je ‘event-driven’ werken, waardoor de load over de dag wordt verspreid en je minder resources nodig hebt. Daarnaast zal een XML/JSON makkelijker te verwerken zijn en zijn er geen conversies nodig.

Maar het bronsysteem stond toch op de nominatie uitgefaseerd te worden en de leverancier was toch langzaam? Ja, dit is een moeilijke afweging (met name vanuit de business). Vaak is het zo dat iets op de nominatie staat om uit te faseren, maar er daadwerkelijk niets gepland is. Er is geen nieuw systeem gekozen en er is nog migratie naar het nieuwe systeem gestart. Kortom: het systeem zal waarschijnlijk nog jaren draaien. Een gesprek aangaan met de leverancier om de mogelijkheden te onderzoeken ter verbetering van hun data interface kan uiteindelijk veel opleveren.

Ja, maar er was toch maar 6 weken tijd om de integratie te maken? Dat klopt, maar de webshop was toen al 6 maanden in ontwikkeling en aan het einde liep het toch uit, omdat de webshop niet af was. Waarom dan ook niet voldoende tijd aan de integratie besteden? Als een integratie vroeg in het traject wordt meegenomen en er vervolgens een aantal richtlijnen wordt gemaakt, kan dat een hoop ellende besparen. In de praktijk zul je soms van je standaarden moeten afwijken, maar dat is dan wel een bewuste keuze. De impact van de keuzes zijn vooraf helder.

Meten

Meten is weten. Voor een integratie betekent meten dat de impact van je keuzes wordt getoetst aan de praktijk. Je kunt bijvoorbeeld meten op basis van één bericht hoe lang die erover doet. Zo is de impact direct duidelijk. Tien bewerkingsstappen kunnen bijvoorbeeld samen 30 milliseconden nodig hebben, terwijl één verrijkingsstap er 3 seconden over doet. Dan is het snel duidelijk waar bottlenecks kunnen optreden.

Om de proef op de som te nemen kan er met de gemiddelde load getest worden. Wat doet dit met de systeemresources? Waar ontstaan bottlenecks? Vervolgens kan ook met groei rekening worden gehouden. Als het aantal artikelen groeit, heeft dit impact? Waar ligt het breekpunt?

Isoleren en schalen

In het voorbeeld zagen we dat na de inproductiename ook andere integraties niet meer werkten. Dus doordat er problemen waren met de artikelenintegratie werkte de orderintegratie ook niet meer. Met name als je honderden integraties hebt draaien en er opeens problemen ontstaan, is het soms heel lastig te bepalen waaraan dat ligt. Hoe hebben integraties impact op elkaar? En waar in de integratie ligt het probleem?

De meest gangbare oplossing is de integratielaag op te knippen in zelfstandige stukken. Daarnaast kan een enkele integratie worden opgeknipt in een aantal overzichtelijke stukken (zelfstandige flows). Tussen deze integraties en flows kunnen vervolgens weer brokers gezet worden als generieke transportlaag.

Het idee is dat ieder afzonderlijk stuk in een aparte container geplaatst kan worden. Als één flow veel bewerkingsstappen heeft, kunnen er meerdere instanties van die container draaien. Doordat integraties geïsoleerd draaien, heeft dat volgende voordelen:

  • integraties of delen van integraties delen onafhankelijk, zodat ze bij problemen geen invloed op elkaar hebben
  • als er toch iets fout gaat, is het duidelijk waar het probleem zich bevindt
  • integraties kunnen onafhankelijk van elkaar worden opgeschaald, zonder dat het hele platform moet worden geschaald

Technisch gezien lenen container clusters, zoals Kubernetes en OpenShift, zich beter voor schaalbaarheid dan oudere technische componenten, zoals applicatie servers of OSGi-containers. Ook deze technieken zijn schaalbaar, maar vaak zie je dat veel resources bij elkaar worden geplaatst. Om beter de complexiteit te kunnen bepalen, geven we nog een laatste voorbeeld.

Het proces

The Bakery Group krijgt van verschillende leveranciers gegevens binnen, zoals artikelgegevens, NAW-gegevens en prijzen. Vanuit een leverancier is de volgende integratie gemaakt:

Press enter or click to view image in full size

Een leverancier stuurt gegevens door. Deze worden in de onramp omgezet naar een generiek formaat. In een offramp worden de gegevens verwerkt en verzonden naar het ERP-systeem. Omdat de offramp voor elke leverancier hetzelfde is, is het idee dat proces te hergebruiken:

Press enter or click to view image in full size

Het probleem

Bij één of enkele aanroepende onramp flows zijn er geen problemen. Maar inmiddels roepen er tientallen flows de offramp aan. Nu is er wel een probleem, nl. van integratie op schaal. Bij integraties met weinig afhankelijkheden en lage datavolumes zijn de keuzes voor constructies niet van groot belang. Bij integraties op schaal wel en dat vraagt om omdenken.

Maar de vraag is eerst: waarom is dit een probleem? Waarom kan het platform dit niet regelen? Laten we dit uitleggen aan de hand van het productieproces bij The Bakery Group.

De productielijn

De fabriek van The Bakery Group bestaat uit:

  1. fabriekshal met machines
  2. inpakafdeling
  3. magazijn

Vertaald naar een integratie:

1. machines = Onramp flows

2. inpakafdeling = Offramp flows

3. magazijn = queue

taarten = bericht

Het proces

De machine produceert allerlei taarten en zet deze in het magazijn. De inpakafdeling neemt verschillende taarten, verpakt ze en zet ze op het dock voor de vrachtwagens. De fabriek krijgt een bevestiging zodra de taarten de fabriek hebben verlaten.

In eerste instantie lijkt dit een goed proces en is er niets aan de hand. Omdat er vraag naar meer taarten is, worden daarom nog enkele machines aangeschaft.

De inpakafdeling wordt langzaam een bottleneck (maar het kan ook aan de vrachtwagens die de taarten vervoeren liggen). De fabriek krijgt namelijk steeds later een bevestiging dat de taarten de fabriek hebben verlaten.

Nu kan hier het bottleneck-probleem gemaskeerd worden door:

  • af te spreken dat de bevestigingen later komen
  • het magazijn er tussenuit te halen

In het laatste geval loopt de fabriekshal vol en krijg je uiteindelijk problemen met de productie. Dus naast de fabriekshal zullen ook de grootte van de inpakafdeling, het aantal dockings en het aantal vrachtwagens meegeschaald moeten worden.

Oplossingen

Als je tientallen integratie-onramps hebt die alle één offramp flow aanroepen, krijg je schaalbaarheidsproblemen. Deze zijn anders dan voor normale integratie-issues. In de basis wil je natuurlijk zoveel mogelijk data verzamelen om te zien waar de problemen ontstaan. Vaak geven time-outs of berichten die op queues blijven staan al genoeg indicatie van een bottleneck.

De makkelijkste oplossing is wellicht elke onramp een eigen kopie van de offramp te geven. Deze constructie heeft echter haar eigen problemen:

  • meer ontwikkelwerk (bij 50 flows 50 kopieën)
  • bij wijzigingen moet alles aangepast worden (en kan er zomaar iets worden vergeten)
  • elke kopie die geladen moet worden kost extra resources. Stel: de offramp heeft 30 stappen. Dan zullen er 30 * 50 = 1500 stappen geladen moeten worden.

Schaalbaarheid in het algemeen verbeteren

Het probleem van bottlenecks en schaalbaarheid is niet nieuw. Over het algemeen is gebleken dat verticaal schalen (dat wil zich zeggen de server meer resources, zoals meer geheugen, te geven) beperkte mogelijkheden biedt. Meestal kun je verschillende oplossingen doorvoeren (geen enkele is eenvoudig).

Enkele voorbeelden:

Event-Driven architectuur

Event-Driven wil zeggen dat je verwerkt op basis van een gebeurtenis. Dus bijvoorbeeld in plaats van een heel artikelbestand met 6000 artikelen te verwerken, verwerk je alleen de gewijzigde artikelen. Hierdoor heb je een vaste berichtgrootte en kun je de load over het systeem verspreiden. Mochten alle artikelen wijzigen dan zullen deze in batches van bijvoorbeeld 100 per minuut aangeboden worden.

Een event-driven architectuur werkt zoveel mogelijk “one-way”. Hierdoor is er geen terugkoppeling nodig en kun je makkelijker queues ter ontkoppeling inzetten, zonder je zorgen te maken over time-outs.

Een event-driven architectuur werkt in een integratie zoveel mogelijk onafhankelijk, zodat er geen afhankelijkheden ontstaan met andere integraties (flows) of externe systemen. Zo kun je ze makkelijker in aparte containers draaien (mits het platform dit toelaat).

Daarnaast is een event-driven architectuur erop gericht de berichten zo snel mogelijk weer uit het systeem te krijgen. De constructies worden vaak gestandaardiseerd en zo simpel mogelijk gehouden.

Je kunt natuurlijk vasthouden aan andere architectuur, maar dan blijft het schaalbaarheidsprobleem van kracht.

Observability

Iets wat in het integratieplatform verbeterd kan worden is de zichtbaarheid. Zo kan er tijdig een aparte instantie worden geïnstalleerd en kunnen problemen worden geïsoleerd.

Intermediair

Een andere mogelijkheid om de load (en de business logica) te verminderen is gebruik maken van een datahub, een data staging area of een streaming broker (bijv. Kafka). In deze gevallen laat je de data daar centraal binnenkomen. De ruwe data stuur je eerst naar een aparte database/applicatie of streaming broker. Daar kun je vervolgens data samenstellen, loggen en vervolgens weer doorsturen.

Horizontaal schalen

In het algemeen worden deze schaalbaarheidsproblemen opgelost door horizontaal te schalen. In dit geval zou je meerdere instanties van de offramp hebben draaien (soms ook automatisch te schalen via autoscaling). Dus van elke aanroepende flow draait er één instantie, terwijl er 50 instanties van de offramp draaien. De offramp kun je vervolgens terugschalen als het minder druk is.

Press enter or click to view image in full size

Het klinkt erg aanlokkelijk met behulp van technologieën als Kubernetes schaalbaarheid te realiseren. Het feit is dat Kubernetes opzetten erg complex is, met name voor een integratielaag. Alles dient cloud native ontwikkeld te worden met issues als gedistribueerde flows, latency, stateless, protocollen, security, beheer enzovoorts. Maar hoewel dit een grote uitdaging is, kan het voor complexe integraties op de lange termijn toch lonen.