Cross-channel matching: de bug die pas opduikt bij site nummer twee

Een EAN-lookup zonder channelId werkt prima — totdat hetzelfde product op een tweede kanaal verschijnt. Hoe een audit in het MFP-platform vijf van zulke leaks vond en welke patronen ze daarna onmogelijk maakten.

Cross-channel matching: de bug die pas opduikt bij site nummer twee

Twee identieke koffers met hetzelfde bagagelabel, elk op een andere bagageband

Eén kanaal: alles werkt

Een typische e-commerce-codebase begint single-channel. Eén database, één productcatalogus, één winkel. Een EAN-lookup heet dan eenvoudig findByEan($ean): er is maar één kanaal, dus de query is per definitie eenduidig. ProductRepository doet zijn werk, AiBulkService doet zijn werk, exports doen hun werk. Geen ambiguïteit, geen probleem.

Twee kanalen: alles lijkt te werken

Wanneer een tweede kanaal aanlandt — bij MFP (Multi Feed Processing) waren dat eerst ditpetje.nl en dithorloge.nl, daarna ditparfum.nl — verandert de aard van het probleem stilletjes. Producten worden nu per kanaal opgeslagen, met dezelfde EAN potentieel actief op meerdere kanalen tegelijk. findByEan($ean) geeft nog steeds een product terug — alleen niet altijd het product van het kanaal dat de aanroeper bedoelde.

Het faalmodel is verraderlijk: zichtbaar geen crash, geen 500, geen logregel. Wel een productdetailpagina op ditpetje.nl die per ongeluk een horloge van dithorloge.nl toont, of een AI-enrichment die petten-tekst koppelt aan parfum-data. De bug presenteert zich als slechte content en niet als technische fout, en glipt daardoor langs de gebruikelijke alarmsignalen.

De audit en wat hij vond

Een gerichte audit op het MFP-platform traceerde alle aanroepen van findBy(['id' => $ids]) en findBy(['ean' => $ean]) en zocht naar exemplaren waarin het channelId-criterium ontbrak. Vijf cross-channel leaks kwamen aan het licht in:

  • ProductService — bulk-lookups die producten over kanaalgrenzen heen samenvoegden.
  • AiBulkService — AI-verrijking-batches waarin een verkeerd kanaal kon binnensluipen.
  • OutputQueueService — exports die producten van meerdere kanalen samenvoegden tot één feed.
  • GuideController — redactionele guides die producten ophaalden zonder kanaalfilter.
  • ProductEnrichCommand — een CLI-commando dat in onbedoelde kanalen werkte.

De fix: maak het verkeerde pad onmogelijk

De directe fix is overal channelId toevoegen. De duurzame fix is anders. Bij MFP geldt nu de regel dat alle ProductRepository-queries verplicht een channelId-criterium meekrijgen. Geen findBy(['ean' => $ean]) zonder kanaal. Geen findBy(['id' => $ids]) zonder kanaal. De repository wordt zodanig vormgegeven dat de kanaal-loze aanroep een PHPStan-fout, of in striktere varianten een type-error, oplevert. Het verkeerde pad wordt onmogelijk te schrijven, niet alleen ontmoedigd.

De les voor multi-iets-systemen

Cross-channel matching is een multi-tenant-bug onder een ander uniform. Iedere codebase die op een dag van één naar twee tenants, kanalen, organisaties of klanten gaat, kent dit risico — en de meeste codebases ontdekken het pas wanneer een klant een rare productpagina screenshot. Het preventieve werk is goedkoop: een lint-regel, een PHPStan-rule, een review-checklist. Het reactieve werk is duur: data-corruption ophelderen, customer-trust herstellen, retro's draaien. Ontwerpen voor N voordat er 2 zijn, is de goedkoopste vorm van defensive engineering die een multi-channel-platform zich kan veroorloven.

Benieuwd wat er mogelijk is?

Eén gesprek is vaak genoeg om te weten of het past. Binnen 24 uur een reactie, en zonder tussenpersonen.

  • Reactie binnen 24 uur, altijd persoonlijk
  • Na een half uur bellen weet u de orde van grootte
  • Vrijblijvend eerste gesprek, geen verplichtingen
  • Ook voor bestaande software van een andere partij