Het idee in drie zinnen. Breng eerst één werkende verbinding tot stand van bron naar bestemming, met zo weinig mogelijk variatie en bijkomende complexiteit, en toon aan dat het resultaat daadwerkelijk aankomt. Voeg daarna telkens één relevante faalmodus toe. Zo blijft een grote integratie beheersbaar, omdat je de onzekerheden van elkaar scheidt in plaats van ze allemaal tegelijk te introduceren.
De naam is ontleend aan de zeevaart. Wanneer twee schepen op zee brandstof of voorraden willen overbrengen, brengen zij eerst één dunne lijn tussen beide schepen aan. Die lijn draagt zelf niets zwaars; zij brengt alleen de verbinding met het andere schip tot stand. Daarna kunnen zwaardere kabels worden overgetrokken en uiteindelijk de slangen. De eerste draad is dun, maar echt: hij bereikt de overkant, of hij bereikt die niet. Hetzelfde principe blijkt verrassend goed te werken voor interfaces, imports en gegevensstromen.
Geen nieuwe wet, maar een bruikbare methode
Het zou overdreven zijn om te beweren dat met Thin Wire een volledig nieuwe ontwikkelmethode is ontdekt. De methode bouwt voort op bekende ideeën: incrementele ontwikkeling, tracer-bullet development, walking skeletons, vertical slicing, set-georiënteerde ETL en end-to-endverificatie. De onderscheidende waarde zit niet in een van die afzonderlijke elementen, maar in de combinatie, de formulering en een handvol praktische regels.
Het onderliggende probleem is oud:
Hoe bouw je doelgericht aan een groot einddoel zonder dat de samenhang, de eigenschappen en de nog te nemen beslissingen onderweg onbeheersbaar worden?
Of, om de bekende metafoor te gebruiken: hoe eet je een olifant?
Het antwoord luidt: hap voor hap.
Maar daarmee weet je nog niet welke hap je als eerste moet nemen, langs welke as je het geheel moet opdelen of wat die eerste stap precies moet opleveren.
Wat maak je dun?
Daarin verschillen de bekende benaderingen van elkaar. Het helpt om ze eenmalig naast elkaar te zetten.
Een MVP maakt het product dun: het beperkt de functionaliteit tot wat nodig is om vast te stellen of de beoogde waarde bestaat.
Een walking skeleton maakt het werkende systeem dun: het realiseert alleen de minimale end-to-enddoorsnede die nodig is om aan te tonen dat de belangrijkste componenten samen kunnen functioneren.
Thin Wire maakt de bouwvolgorde dun: eerst wordt één kleine maar echte gegevensstroom aangelegd, waarna deze gecontroleerd wordt uitgebreid.
Het onderscheid is niet absoluut. Een MVP kan een walking skeleton bevatten, een walking skeleton kan met behulp van een thin wire worden opgebouwd en een thin wire kan onderdeel zijn van een MVP.
Het zijn geen concurrerende doctrines. Het zijn drie manieren om hetzelfde onderliggende probleem te adresseren: voorkomen dat de eigenschappen, afhankelijkheden en beslissingen van een groot einddoel al zo vroeg met elkaar verstrengeld raken dat niemand het geheel nog betrouwbaar kan overzien.
Vier regels die het werk doen
De waarde van Thin Wire zit in de manier waarop die eerste draad wordt aangelegd. Vier regels dragen het grootste deel van het gewicht.
1. Dun betekent niet voorlopig
De draad is dun omdat er weinig overheen gaat, niet omdat de landing kunstmatig mag zijn.
De landing moet echt zijn:
- de werkelijke doeltabel;
- echte constraints;
- echte foreign keys;
- een daadwerkelijk contract;
- een controleerbaar eindresultaat.
Zonder deze voorwaarde wordt ‘dun’ al snel een excuus om alles in een JSON-blob op te slaan, een generieke stagingtabel te gebruiken of te beloven dat de normalisatie later wel wordt geregeld.
De methode zegt niet:
Bouw eerst iets voorlopigs.
Zij zegt:
Bouw eerst iets kleins dat daadwerkelijk landt.
De eerste draad hoeft niet alles te doen, maar wat hij doet, moet echt zijn.
2. Begin met de kleinste werkende propertyset en breid uit per faalmodusklasse
De eerste draad bevat alleen de kleinste verzameling properties waarmee daadwerkelijk iets van bron naar bestemming kan worden overgebracht.
Dat kan in het uiterste geval niet meer zijn dan een sleutel en één eenvoudige waarde. Het doel van die eerste stap is niet om al een bruikbaar of representatief record op te bouwen, maar om vast te stellen dat de route bestaat: de bron kan iets aanbieden, de wire kan het vervoeren en de bestemming kan het correct laten landen.
Zodra die minimale propertyset werkt, wordt de draad stap voor stap uitgebreid.
De vraag is vervolgens niet simpelweg welke property als volgende aan de beurt is, maar welke faalmodusklasse verantwoord kan worden toegevoegd.
Een faalmodusklasse is een groep properties die op dezelfde technische manier worden verwerkt en daardoor ook op vergelijkbare wijze kunnen mislukken. Ze doorlopen dezelfde soort validatie, hebben vergelijkbare afhankelijkheden en vragen bij fouten doorgaans om hetzelfde type beslissing.
Eenvoudige scalars vormen bijvoorbeeld vaak één faalmodusklasse. Ze worden rechtstreeks uitgelezen, op type, lengte of nullability gecontroleerd en vervolgens opgeslagen. Meerdere van zulke velden kunnen daarom meestal in één stap worden toegevoegd.
Foreign keys vormen een andere faalmodusklasse. Daarbij moet niet alleen een waarde worden opgeslagen, maar ook worden vastgesteld of de bijbehorende referentie bestaat, kan worden gevonden of eerst moet worden aangemaakt.
Hetzelfde geldt voor:
- lookups;
- arrays;
- conditionele logica;
- samengestelde sleutels;
- asynchrone verwerking;
- retries;
- incomplete records.
De uitbreiding volgt dus niet noodzakelijk één property per stap. Zij volgt één samenhangende categorie van verwerking en onzekerheid.
‘Voeg één ding tegelijk toe’ betekent daarom niet dat iedere property afzonderlijk moet worden geïmplementeerd. Twee eenvoudige scalars die op dezelfde manier worden gelezen, gevalideerd en opgeslagen, kunnen prima tegelijk worden toegevoegd. Een scalar en een foreign key horen doorgaans niet in dezelfde stap thuis, omdat zij fundamenteel anders kunnen falen.
Thin Wire probeert geen piepkleine commits af te dwingen. De methode probeert te voorkomen dat verschillende probleemklassen gelijktijdig worden geïntroduceerd.
De opbrengst moet nuchter worden geformuleerd. Het is verleidelijk om te stellen dat het gelijktijdig wijzigen van N zaken een foutruimte van 2ᴺ oplevert, maar als algemene stelling is dat te sterk.
De bescheidener en beter verdedigbare formulering luidt:
Naarmate meer onafhankelijke variabelen tegelijk veranderen, groeit het aantal mogelijke oorzaken en interacties snel.
Wanneer het systeem niet meer werkt nadat één nieuwe faalmodusklasse is toegevoegd, blijft de lijst met verdachten kort.
Dat is de werkelijke winst. Fouten verdwijnen niet, maar het wordt veel eenvoudiger om te zien welk probleem er daadwerkelijk voorligt.
3. Klaar betekent dat het resultaat in de bestemming bestaat
Succes wordt vastgesteld aan de ontvangende kant, niet aan de verzendende kant.
Niet:
- de API retourneerde
200 OK; - het bericht is gepubliceerd;
- de job is zonder exception afgerond;
- de logging meldt succes.
Maar:
De bedoelde toestand bestaat aantoonbaar in de bestemming.
Bij een database-integratie kan het acceptatiecriterium soms niet ingewikkelder zijn dan een SELECT.
Dat klinkt misschien banaal, maar gedistribueerde systemen zitten vol schijnsuccessen. Een producer kan correct hebben gepubliceerd, een queue kan correct hebben afgeleverd en een consumer kan correct hebben gelezen, terwijl de databasetransactie alsnog is teruggedraaid, slechts gedeeltelijk is uitgevoerd of in de verkeerde tabel is terechtgekomen.
Iedere tussenliggende component kan naar waarheid melden dat zijn taak is afgerond, terwijl het bedoelde bedrijfsresultaat nog steeds ontbreekt.
Daarom blijft de uitkomst in de bestemming het vaste referentiepunt.
4. Bewuste schuld is aanvaardbaar; verborgen schuld niet
Bij de eerste draad worden doorgaans verschillende zaken uitgesteld:
- volledige validatie;
- authenticatie en autorisatie;
- retries;
- dead-letterafhandeling;
- de tegenrichting;
- performance-optimalisatie.
Dat is niet automatisch onzorgvuldig, zolang iedere uitsluiting expliciet is en vastligt op welk moment zij wordt afgehandeld.
Zonder die registratie is tijdelijke eenvoud niet te onderscheiden van vergeten werk.
Een eerste draad kan bijvoorbeeld zonder retries worden gebouwd, zolang voor iedereen duidelijk is dat die vóór productiegebruik moeten worden toegevoegd.
De schuld is dan geen toevallig bijproduct, maar onderdeel van het bouwplan.
Begin klein, maar niet betekenisloos klein
De eerste draad hoeft niet het meest voorkomende scenario te vertegenwoordigen.
Het meest voorkomende scenario bevat vaak juist de meeste velden, uitzonderingen en varianten, waardoor meerdere onzekerheden tegelijk worden geïntroduceerd.
Het eenvoudigste geval is doorgaans een beter vertrekpunt, maar alleen wanneer het nog steeds een representatieve end-to-endroute aantoont.
Een te kunstmatig voorbeeld kan technisch slagen zonder dat het iets bruikbaars leert over de integratie.
De betere regel luidt daarom niet:
Kies altijd het eenvoudigste geval.
Maar:
Kies het eenvoudigste geval dat nog steeds een echte route, een echte transformatie en een echte landing bevat.
Dun zijn vereist ook subtractie.
De vraag is niet alleen welke informatie minimaal de rivier over moet, maar ook welke informatie niet hoeft te worden overgebracht omdat de bestemming haar zelf betrouwbaar kan afleiden.
Stel dat een client zowel een basiscode als verschillende daarvan afgeleide classificaties meestuurt. Op het eerste gezicht lijkt dat veiliger: er wordt meer informatie aangeleverd en er is minder serverlogica nodig.
Maar iedere afgeleide waarde introduceert een tweede versie van de waarheid.
Zodra de basiscode en een daarvan afgeleide classificatie niet langer overeenkomen, moet iemand beslissen:
- welke waarde voorrang heeft;
- welk systeem eigenaar is van de regel;
- waar de afleiding moet plaatsvinden;
- hoe inconsistenties worden opgelost.
Een dunne draad is daarom niet alleen klein wat betreft het aantal properties. Zij moet ook zo weinig mogelijk onafhankelijke waarheden vervoeren.
Wanneer je de methode gebruikt — en wanneer niet
Thin Wire is een methode, geen natuurwet.
De methode is vooral bruikbaar wanneer:
- bron en bestemming niet in één oogopslag te begrijpen zijn;
- fouten pas aan de andere kant van een interface zichtbaar worden;
- de brondata onbekend, inconsistent of vervuild is;
- de payload verschillende soorten complexiteit combineert;
- technische en functionele beslissingen met elkaar verstrengeld dreigen te raken.
Wanneer bron en bestemming stabiel zijn, het contract al is bewezen en het team meerdere vergelijkbare interfaces heeft gebouwd, kan een grotere eerste stap volkomen rationeel zijn.
De methode schrijft niet voor dat er altijd langzaam moet worden gewerkt. Zij schrijft voor dat je langzaam genoeg begint zolang de aard van het probleem nog onzeker is.
Bij bidirectionele interfaces is het doorgaans verstandig om eerst één richting werkend te krijgen. Bij voorkeur is dat de richting met het duidelijkste eigenaarschap, de eenvoudigste landing en de grootste leerwaarde.
Zodra twee systemen dezelfde onderliggende werkelijkheid mogen wijzigen, ontstaan vragen over:
- het bronsysteem;
- conflictresolutie;
- gelijktijdige wijzigingen;
- synchronisatielussen;
- het eigenaarschap van afzonderlijke properties.
Die vragen veranderen de aard van de interface zelf. Zij moeten doorgaans pas worden geïntroduceerd wanneer de eerste richting goed wordt begrepen.
Het is minstens zo belangrijk om te weten wanneer je moet stoppen met kruipen.
Wanneer een team na twintig stappen nog steeds ieder afzonderlijk veld als aparte draad behandelt, is de methode een ritueel geworden.
Kleine stappen zijn een middel om onbekende faalmodi te ontdekken. Zij zijn niet bedoeld als permanente snelheidsbeperking.
Na enkele representatieve draden moeten patronen zichtbaar worden:
- welke scalars zonder problemen worden overgebracht;
- hoe lookups falen;
- waar lengtebeperkingen problemen veroorzaken;
- waar nullability relevant wordt;
- welke transformaties hetzelfde patroon volgen.
Dat is het moment waarop de omvang van de stappen moet worden vergroot.
De bekende ‘regel van drie’ is hierbij een bruikbare heuristiek, geen statistische garantie. Wanneer na enkele draden nog geen duidelijke patronen ontstaan, is het systeem mogelijk minder homogeen dan verwacht en blijft een kleinere stapgrootte gerechtvaardigd.
Het werkelijke anti-patroon: droogzwemmen
Het tegenovergestelde van Thin Wire is niet zorgvuldig ontwerpen.
Het is langdurig ontwerpen zonder operationele feedback.
Er wordt een volledig contract geschreven. Iedere denkbare uitzondering wordt vooraf bedacht. Pas daarna wordt de echte bron aangesloten, waarna blijkt dat de bron:
- velden anders vult dan verwacht;
- nullwaarden aanlevert waar die niet werden voorzien;
- verouderde records verstuurt;
- arrays gebruikt waar één waarde werd verwacht;
- half-afgeronde processen zichtbaar maakt;
- waarden produceert die niet binnen het doelschema passen.
Het contract wordt aangepast, maar de interface werkt nog steeds niet.
Dat voelt productief, omdat documenten, mappings en regels zich blijven opstapelen. Toch blijft het droogzwemmen zolang er nog geen enkel record daadwerkelijk de rivier is overgestoken.
Dat betekent niet dat ontwerp onbelangrijk is. Integendeel.
Een bruikbare landing, compleet met constraints en een contractspecificatie, vraagt om aanzienlijk ontwerpwerk. In veel gevallen vindt het grootste deel van de inspanning al vóór de eerste draad plaats:
- het API-contract;
- de mapping;
- de doeltabellen;
- de foreign keys;
- de basisvalidatie;
- de keuze van het bronsysteem.
De regel is eenvoudig:
Ontwerp genoeg om een echte landing te realiseren en laat die werkende landing vervolgens het verdere ontwerp informeren.
De import als oervorm
De methode wordt waarschijnlijk het duidelijkst in een klassieke importcasus.
Gegevens uit een extern product moeten worden overgebracht naar een intern datamodel. De bestemming ligt vast, maar de brondata blijkt vervuild en inconsistent.
De verleiding is om eerst de volledige mapping af te ronden en daarna alles in één importgang te verwerken.
De Thin Wire-benadering draait die volgorde om.
Eerst worden alle records uitsluitend met de minimale sleutel ingevoegd en wordt gecontroleerd of de rijen bestaan.
Daarna:
- wordt één categorie velden bijgewerkt;
- worden de ontstane fouten geanalyseerd;
- wordt een expliciet besluit genomen;
- wordt het proces met de volgende categorie herhaald.
Stel dat een naamveld wordt toegevoegd en elf waarden langer blijken te zijn dan de doelkolom toestaat.
Dat is nu één duidelijk afgebakend probleem waarvoor één expliciet besluit nodig is:
- de waarden afkappen;
- de kolom verbreden;
- de records afwijzen;
- een waarschuwing vastleggen;
- eisen dat de brondata wordt gecorrigeerd.
Pas daarna volgt de volgende kolom of faalmodusklasse.
De import blijft set-georiënteerd, maar de redenering blijft ordelijk: één probleemklasse tegelijk.
De kracht zit niet in INSERT en UPDATE op zichzelf, maar in de bouwvolgorde:
- eerst bestaan de rijen;
- daarna groeit hun inhoud;
- iedere gang introduceert één nieuw soort onzekerheid;
- iedere foutcategorie krijgt een eigen besluit en eigen logging.
Dat is Thin Wire, ongeacht of iemand het die naam geeft.
Oude wijn in een bruikbare fles
Is Thin Wire een uniek idee?
Waarschijnlijk niet in fundamentele zin.
De samenstellende ideeën bestaan al lange tijd:
- incrementele ontwikkeling;
- end-to-endverificatie;
- vertical slices;
- tracer bullets;
- walking skeletons;
- set-georiënteerde ETL;
- één variabele tegelijk wijzigen.
Wat wel onderscheidend is, is de synthese:
- ‘dun’ verwijst naar de inhoudelijke omvang, niet naar een kunstmatige landing;
- succes wordt in de bestemming vastgesteld;
- uitbreidingen worden gescheiden per faalmodusklasse;
- de eerste route is klein maar echt;
- tijdelijke schuld wordt expliciet geregistreerd;
- de draad vervoert zo weinig mogelijk afgeleide waarheden;
- de methode bevat een bewuste overgang naar grotere stappen.
Dat maakt het geen wetenschappelijke doorbraak, en dat hoeft ook niet.
Veel bruikbare patronen zijn geen ontdekkingen van iets wat nooit eerder heeft bestaan. Zij geven een naam aan herhaalbare vakkennis, maken impliciete keuzes bespreekbaar en helpen een team om op het juiste moment hetzelfde soort besluit te nemen.
De bescheiden claim is daarom sterker dan de grootse:
Thin Wire is een methode voor het bepalen van de bouwvolgorde van een integratie. Zij houdt een groot einddoel beheersbaar door eerst één kleine, echte route aan te leggen en die daarna per faalmodusklasse uit te breiden.
Of, om terug te keren naar de olifant:
Thin Wire is geen nieuw antwoord op de vraag hoe je een olifant opeet. Het is een manier om bij integraties te bepalen waar je de eerste hap neemt, hoe je weet dat je hem daadwerkelijk hebt doorgeslikt en wanneer je klaar bent om grotere happen te nemen.