Fouttaxonomie voor zakelijke apps: consistente UI en monitoring
Een fouttaxonomie voor zakelijke apps helpt validatie-, authenticatie-, rate-limiet- en afhankelijkheidsfouten te classificeren, zodat waarschuwingen en UI-reacties consistent blijven.

Wat een fouttaxonomie oplost in echte bedrijfsapps
Een fouttaxonomie is een gedeelde manier om fouten te benoemen en te groeperen zodat iedereen ze op dezelfde manier afhandelt. In plaats van dat elk scherm en elke API zijn eigen berichten verzint, definieer je een kleine set categorieën (zoals validation of auth) en regels voor hoe ze aan gebruikers en in monitoring verschijnen.
Zonder die gedeelde structuur verschijnt hetzelfde probleem in verschillende vormen. Een verplicht veld dat ontbreekt kan op mobiel als “Bad Request” verschijnen, op het web als “Er is iets misgegaan” en in logs als een stack trace. Gebruikers weten niet wat ze daarna moeten doen, en on-call teams verspillen tijd met raden of het om gebruikersfout, een aanval of een storing gaat.
Het doel is consistentie: hetzelfde type fout leidt tot hetzelfde UI-gedrag en hetzelfde alarmgedrag. Validatieproblemen moeten naar het exacte veld wijzen. Toegangsproblemen moeten de actie stoppen en uitleggen welke toegang ontbreekt. Afhankelijkheidsfouten moeten een veilige retry aanbieden, terwijl monitoring het juiste team alarmeert.
Een realistisch voorbeeld: een accountmanager probeert een klantrecord aan te maken, maar de betalingsservice ligt eruit. Als je app een generieke 500 teruggeeft, proberen ze het opnieuw en kunnen later duplicaten ontstaan. Met een duidelijke dependency-failure categorie kan de UI zeggen dat de service tijdelijk onbereikbaar is, dubbele inzendingen voorkomen en monitoring het juiste team waarschuwen.
Dit soort afstemming is vooral belangrijk wanneer één backend meerdere clients voedt. Als de API, webapp, mobiele app en interne tools allemaal op dezelfde categorieën en codes vertrouwen, voelen storingen minder willekeurig.
Een eenvoudig model: categorie, code, message, details
Taxonomieën blijven makkelijker te onderhouden wanneer je vier zaken scheidt die vaak door elkaar worden gebruikt: de categorie (wat voor soort probleem het is), de code (een stabiele identifier), het bericht (menselijke tekst) en de details (gestructureerde context). HTTP-status blijft belangrijk, maar het zou niet het hele verhaal moeten zijn.
Categorie beantwoordt: “Hoe moeten UI en monitoring zich gedragen?” Een 403 kan in de ene context “auth” betekenen, terwijl een andere 403 “policy” kan zijn als je later regels toevoegt. Categorie gaat over gedrag, niet over transport.
Code beantwoordt: “Wat is er precies gebeurd?” Codes moeten stabiel en saai zijn. Als je een knop hernoemt of een service refactort, mag de code niet veranderen. Dashboards, alerts en support-scripts vertrouwen hierop.
Message beantwoordt: “Wat vertellen we een persoon?” Bepaal voor wie het bericht bedoeld is. Een gebruikersgericht bericht moet kort en vriendelijk zijn. Een supportbericht kan vervolgstappen bevatten. Logs mogen technischer zijn.
Details beantwoordt: “Wat hebben we nodig om het te repareren?” Houd details gestructureerd zodat de UI kan reageren. Voor een formulierfout kunnen dat veldnamen zijn. Voor een afhankelijkheidsprobleem kan dat de naam van de upstream-service en een retry-after waarde zijn.
Hier is een compact formaat dat veel teams gebruiken:
{
"category": "validation",
"code": "CUSTOMER_EMAIL_INVALID",
"message": "Enter a valid email address.",
"details": { "field": "email", "rule": "email" }
}
Als features veranderen, houd categorieën klein en stabiel, en voeg nieuwe codes toe in plaats van oude te hergebruiken. Dat houdt UI-gedrag, monitoringtrends en support-playbooks betrouwbaar terwijl het product evolueert.
Kerncategorieën: validation, auth, rate limits, dependencies
De meeste zakelijke apps kunnen starten met vier categorieën die overal terugkomen. Als je ze overal hetzelfde benoemt en behandelt—backend, web en mobiel—kan je UI consistent reageren en wordt monitoring leesbaar.
Validation (verwacht)
Validatiefouten ontstaan wanneer gebruikersinvoer of een businessregel faalt. Dit is normaal en moet makkelijk te herstellen zijn: ontbrekende verplichte velden, ongeldige formaten of regels zoals “korting mag niet meer dan 20% zijn” of “ordertotaal moet > $0 zijn”. De UI moet het exacte veld of de regel benadrukken, niet een generieke melding tonen.
Authenticatie vs autorisatie (verwacht)
Auth-fouten splitsen zich meestal in twee gevallen: niet geauthenticeerd (niet ingelogd, sessie verlopen, token ontbreekt) en niet geautoriseerd (ingelogd, maar zonder permissie). Behandel ze verschillend. “Log opnieuw in” past bij het eerste geval. Bij het tweede vermijd je het onthullen van gevoelige details, maar wees wel duidelijk: “Je hebt geen toegang om facturen goed te keuren.”
Rate limits (verwacht, maar tijdsgebonden)
Rate limiting betekent “te veel verzoeken, probeer het later opnieuw.” Het verschijnt vaak bij imports, drukke dashboards of herhaalde retries. Voeg een retry-after hint toe (zelfs als dat maar “wacht 30 seconden” is), en zorg dat de UI terugschakelt in plaats van de server te blijven belasten.
Afhankelijkheidsfouten (vaak onverwacht)
Afhankelijkheidsfouten komen van upstream services, timeouts of storingen: payment providers, e-mail/SMS, databases of interne services. Gebruikers kunnen dit niet oplossen, dus de UI moet een veilige fallback aanbieden (concept opslaan, later proberen, contact opnemen met support).
Het belangrijkste verschil is gedrag: verwachte fouten horen bij de normale flow en verdienen precieze feedback; onverwachte fouten duiden op instabiliteit en moeten alerts, correlation IDs en zorgvuldige logging triggeren.
Stap voor stap: bouw je taxonomie in één workshop
Een taxonomie moet klein genoeg zijn om te onthouden, maar strikt genoeg zodat twee teams hetzelfde probleem hetzelfde label geven.
1) Timebox en kies een kleine set
Begin met een workshop van 60 tot 90 minuten. Maak een lijst van de fouten die je het meest ziet (slechte input, inlogproblemen, te veel verzoeken, third-party storingen, onverwachte bugs), en groepeer ze vervolgens in 6 tot 12 categorieën die iedereen zonder documentatie hardop kan noemen.
2) Stem in op een stabiel code-schema
Kies een naamgevingspatroon dat leesbaar blijft in logs en tickets. Houd het kort, vermijd versienummers en behandel codes als permanent zodra ze gepubliceerd zijn. Een gangbaar patroon is een categorie-prefix plus een duidelijke slug, zoals AUTH_INVALID_TOKEN of DEP_PAYMENT_TIMEOUT.
Voordat je de kamer verlaat, beslis wat elke fout moet bevatten: category, code, safe message, gestructureerde details en een trace- of request-ID.
3) Schrijf één regel voor category vs code
Teams blijven steken wanneer categorieën een dumpplaats worden. Een eenvoudige regel helpt: category beantwoordt “Hoe moeten UI en monitoring reageren?”, code beantwoordt “Wat is er precies gebeurd?”. Als twee fouten verschillend UI-gedrag moeten krijgen, mogen ze niet dezelfde categorie delen.
4) Stel standaard UI-gedrag per categorie in
Bepaal wat gebruikers standaard zien. Validatie markeert velden. Auth stuurt naar inloggen of toont een toegangsmelding. Rate limits tonen “probeer het opnieuw over X seconden”. Afhankelijkheidsfouten tonen een rustige retry-pagina. Zodra deze defaults bestaan, kunnen nieuwe features die volgen in plaats van telkens iets nieuws uit te vinden.
5) Test met echte scenario’s
Draai vijf veelvoorkomende flows (signup, checkout, search, admin edit, file upload) en label elke fout. Als de groep discussieert, heb je meestal één duidelijkere regel nodig, niet twintig nieuwe codes.
Validatiefouten: maak ze actiegericht voor gebruikers
Validatie is het type fout dat je meestal direct aan de gebruiker wilt tonen. Het moet voorspelbaar zijn: het vertelt de gebruiker wat te herstellen, en het veroorzaakt nooit een retry-loop.
Field-level en form-level validatie zijn verschillende problemen. Field-level fouten horen bij één invoer (email, telefoon, bedrag). Form-level fouten gaan over de combinatie van inputs (begindatum moet voor einddatum zijn) of ontbrekende vereisten (geen verzendmethode geselecteerd). Je API-respons moet dat onderscheid duidelijk maken zodat de UI correct kan reageren.
Een veelvoorkomende businessregel-fout is “kredietlimiet overschreden.” De gebruiker heeft mogelijk een geldig nummer ingevoerd, maar de actie is niet toegestaan op basis van accountstatus. Behandel dit als een form-level validatiefout met een duidelijke reden en een veilige hint, zoals “Je beschikbare limiet is $500. Verlaag het bedrag of vraag een verhoging aan.” Vermijd het tonen van interne namen zoals databasevelden, scoringsmodellen of regelengine-stappen.
Een actiegerichte respons bevat meestal een stabiele code (niet alleen een Engelse zin), een gebruiksvriendelijk bericht, optionele veldpointers voor field-level problemen en kleine veilige hints (voorbeeldformaten, toegestane bereiken). Als je een regenaam voor engineers nodig hebt, zet die dan in logs, niet in de UI.
Log validatiefouten anders dan systeemfouten. Je wilt genoeg context om patronen te debuggen zonder gevoelige data op te slaan. Noteer user ID, request ID, de regenaam of code en welke velden faalden. Voor waarden log alleen wat nodig is (vaak “aanwezig/ontbrekend” of lengte) en mask alles gevoeligs.
In de UI: focus op oplossen, niet op opnieuw proberen. Markeer velden, behoud wat de gebruiker heeft ingevuld, scroll naar de eerste fout en schakel automatische retries uit. Validatiefouten zijn geen tijdelijk probleem, dus “probeer het nogmaals” is tijdverspilling.
Auth- en permissiefouten: behoud veiligheid en duidelijkheid
Authenticatie- en autorisatiefouten lijken voor gebruikers op elkaar, maar betekenen verschillende dingen voor beveiliging, UI-flow en monitoring. Ze scheiden zorgt voor consistent gedrag op web, mobiel en API-clients.
Unauthenticated betekent dat de app niet kan bewijzen wie de gebruiker is. Typische oorzaken zijn ontbrekende credentials, een ongeldig token of een verlopen sessie. Forbidden betekent dat de gebruiker wel bekend is, maar geen toestemming heeft voor de actie.
Session expired is de meest voorkomende randvoorwaarde. Als je refresh tokens ondersteunt, probeer dan eenmaal stil te vernieuwen en herhaal het originele verzoek. Als refresh faalt, geef een unauthenticated fout terug en stuur de gebruiker naar inloggen. Vermijd loops: na één refreshpoging stop je en laat je een duidelijke volgende stap zien.
UI-gedrag moet voorspelbaar blijven:
- Unauthenticated: vraag om in te loggen en behoud wat de gebruiker probeerde te doen
- Forbidden: blijf op de pagina en toon een toegangsmelding, plus een veilige actie zoals “vraag toegang aan”
- Account uitgeschakeld of ingetrokken: log uit en toon een kort bericht dat support kan helpen
Voor auditing log je genoeg om te beantwoorden “wie probeerde wat en waarom werd het geblokkeerd” zonder secrets te lekken. Een nuttig record bevat user ID (indien bekend), tenant of workspace, actienaam, resource-identifier, timestamp, request ID en het resultaat van de beleidscontrole (allowed/denied). Houd ruwe tokens en wachtwoorden uit logs.
In gebruikersgerichte berichten, onthul geen rolnamen, permissieregels of interne beleidsstructuur. “Je hebt geen toegang om facturen goed te keuren” is veiliger dan “Alleen FinanceAdmin kan facturen goedkeuren.”
Rate limit fouten: voorspelbaar gedrag bij load
Rate limits zijn geen bugs. Het zijn vangrails. Behandel ze als een volwaardige categorie zodat UI, logs en alerts consistent reageren wanneer het verkeer stijgt.
Rate limits verschijnen meestal in een paar vormen: per gebruiker (iemand die te snel klikt), per IP (veel gebruikers achter één kantoornetwerk) of per API-key (een integratiejob die uit de hand loopt). De oorzaak doet ertoe omdat de oplossing verschilt.
Wat een goede rate-limit respons bevat
Clients hebben twee dingen nodig: dat ze gelimiteerd zijn en wanneer ze het opnieuw mogen proberen. Geef HTTP 429 terug plus een duidelijke wachttijd (bijvoorbeeld Retry-After: 30). Voeg ook een stabiele foutcode toe (zoals RATE_LIMITED) zodat dashboards events kunnen groeperen.
Houd het bericht rustig en specifiek. “Too many requests” is technisch juist maar niet behulpzaam. “Probeer over 30 seconden opnieuw” schept verwachtingen en vermindert herhaalde klikken.
In de UI voorkom je snelle retries. Een simpel patroon is de actie uitschakelen voor de wachttijd, een korte countdown tonen en één veilige retry aanbieden wanneer de timer eindigt. Vermijd formuleringen die gebruikers doen denken dat data verloren is.
Monitoring is waar teams vaak overreageren. Pager niet voor elke 429. Volg rates en waarschuw bij ongewone pieken: een plotselinge stijging voor één endpoint, tenant of API-key is actiegericht.
Backend-gedrag moet ook voorspelbaar zijn. Gebruik exponentiële backoff voor automatische retries en maak retries idempotent. Een “Create invoice” actie mag niet twee facturen aanmaken als het eerste verzoek daadwerkelijk is geslaagd.
Afhankelijkheidsfouten: handel storingen zonder chaos af
Afhankelijkheidsfouten zijn die waarbij gebruikers niets kunnen doen om het te verhelpen. De gebruiker deed alles goed, maar een payment gateway time-outte, een database-verbinding viel uit of een upstream service gaf een 5xx. Behandel deze als een aparte categorie zodat zowel UI als monitoring voorspelbaar reageren.
Begin met het benoemen van de veelvoorkomende faalsoorten: timeout, connection error (DNS, TLS, geweigerd) en upstream 5xx (bad gateway, service unavailable). Zelfs als je de hoofdoorzaak niet kunt achterhalen, kun je vastleggen wat er gebeurde en consistent reageren.
Retry vs fail fast
Retries helpen bij korte haperingen, maar kunnen een storing ook verergeren. Gebruik eenvoudige regels zodat elk team dezelfde keuze maakt.
- Retry wanneer de fout waarschijnlijk tijdelijk is: timeouts, connection resets, 502/503
- Fail fast voor door de gebruiker veroorzaakte of permanente gevallen: 4xx van de dependency, ongeldige credentials, ontbrekende resource
- Begrens retries (bijvoorbeeld 2 tot 3 pogingen) en voeg een kleine backoff toe
- Retry nooit niet-idempotente acties tenzij je een idempotency key hebt
UI-gedrag en veilige fallbacks
Wanneer een afhankelijkheid faalt, vertel wat de gebruiker daarna kan doen zonder hen de schuld te geven: “Tijdelijk probleem. Probeer het later opnieuw.” Als er een veilige fallback is, bied die aan. Voorbeeld: als Stripe down is, laat de gebruiker de bestelling opslaan als “Pending payment” en stuur een e-mailbevestiging in plaats van het winkelwagentje te verliezen.
Bescherm gebruikers ook tegen dubbele inzendingen. Als de gebruiker tijdens een trage respons twee keer op “Betaal” tikt, moet je systeem dat detecteren. Gebruik idempotency keys voor create-and-charge flows, of statuscontroles zoals “order al betaald” voordat je de actie opnieuw uitvoert.
Voor monitoring log je velden die snel één vraag beantwoorden: “Welke dependency faalt en hoe erg is het?” Leg dependency name, endpoint of operatie, duur en het uiteindelijke resultaat vast (timeout, connect, upstream 5xx). Dit maakt alerts en dashboards zinvol in plaats van luidruchtig.
Maak monitoring en UI consistent over kanalen
Taxonomieën werken alleen wanneer elk kanaal dezelfde taal spreekt: de API, de web UI, de mobiele app en je logs. Anders verschijnt hetzelfde probleem als vijf verschillende berichten en weet niemand of het om gebruikersfout of een echte storing gaat.
Behandel HTTP-statuscodes als een secundaire laag. Ze helpen bij proxies en basaal clientgedrag, maar je category en code moeten de betekenis dragen. Een dependency-timeout kan nog steeds een 503 zijn, maar de categorie vertelt de UI om “Opnieuw proberen” aan te bieden en vertelt monitoring om de on-call te pagineren.
Laat elke API één standaardfoutvorm teruggeven, zelfs wanneer de bron verschilt (database, auth-module, third-party API). Een eenvoudige vorm zoals deze houdt UI-afhandeling en dashboards consistent:
{
"category": "dependency",
"code": "PAYMENTS_TIMEOUT",
"message": "Payment service is not responding.",
"details": {"provider": "stripe"},
"correlation_id": "9f2c2c3a-6a2b-4a0a-9e9d-0b0c0c8b2b10"
}
Correlation IDs zijn de brug tussen “een gebruiker zag een fout” en “we kunnen het traceren.” Toon de correlation_id in de UI (een kopieerknop helpt) en log hem altijd op de backend zodat je één request over services kunt volgen.
Stem af wat veilig is om in de UI te tonen versus alleen in logs. Een praktische scheiding is: UI krijgt category, een duidelijke boodschap en een volgende stap; logs krijgen technische foutdetails en requestcontext; beiden delen correlation_id en de stabiele foutcode.
Snelle checklist voor een consistent foutensysteem
Consistentie is in het beste geval saai: elk kanaal gedraagt zich hetzelfde en monitoring vertelt de waarheid.
Controleer eerst de backend, inclusief achtergrondjobs en webhooks. Als een veld optioneel is, slaan mensen het over en valt consistentie uit elkaar.
- Elke fout bevat een category, een stabiele code, een gebruikersveilig bericht en een trace ID.
- Validatieproblemen zijn verwacht en moeten geen paging-alerts triggeren.
- Auth- en permissiekwesties worden voor beveiligingspatronen gevolgd, maar niet behandeld als storingen.
- Rate limit responses bevatten een retry-hint (bijvoorbeeld seconden om te wachten) en spammen geen alerts.
- Afhankelijkheidsfouten bevatten de naam van de dependency plus timeout- of statusdetails.
Controleer dan UI-regels. Elke categorie moet naar één voorspelbaar schermgedrag mappen zodat gebruikers niet hoeven te raden wat nu de volgende stap is: validatie markeert velden, auth vraagt om inloggen of toont toegang, rate limits tonen rustig wachten, afhankelijkheidsfouten bieden retry en fallback wanneer mogelijk.
Een eenvoudige test is in staging één fout van elke categorie te triggeren en te verifiëren dat je hetzelfde resultaat krijgt in de webapp, mobiele app en adminpaneel.
Veelvoorkomende fouten en praktische vervolgstappen
De snelste manier om een foutensysteem kapot te maken is het als bijzaak behandelen. Verschillende teams gebruiken dan verschillende woorden, verschillende codes en ander UI-gedrag voor hetzelfde probleem. Taxonomiewerk betaalt zich terug als het consistent blijft.
Veelvoorkomende foutpatronen:
- Interne exceptietekst naar gebruikers lekken. Dat verwart mensen en kan gevoelige details onthullen.
- Alle 4xx als “validation” labelen. Ontbrekende permissie is niet hetzelfde als een ontbrekend veld.
- Voor elke feature nieuwe codes verzinnen zonder review. Je eindigt met 200 codes die op hetzelfde 5 dingen neerkomen.
- De verkeerde fouten opnieuw proberen. Een permissiefout of onjuist e-mailadres opnieuw proberen creëert alleen maar ruis.
Een simpel voorbeeld: een accountmanager verstuurt een “Create customer” formulier en krijgt een 403. Als de UI alle 4xx als validatie behandelt, markeert het willekeurige velden en vraagt het de gebruiker “corrigeer invoer” in plaats van te vertellen dat ze toegang nodig hebben. Monitoring toont dan een piek in “validation issues” terwijl het echte probleem rollen zijn.
Praktische vervolgstappen die in één korte workshop passen: schrijf een één-pagina taxonomiedocument (categorieën, wanneer te gebruiken, 5 tot 10 canonieke codes), definieer berichtregels (wat gebruikers zien vs wat in logs gaat), voeg een lichte reviewgate toe voor nieuwe codes, stel retry-regels per categorie in en implementeer end-to-end (backend response, UI-mapping en monitoringdashboards).
Als je bouwt met AppMaster (appmaster.io), helpt het om deze regels op één plek te centraliseren zodat dezelfde category- en code-gedragingen door backend, webapp en native mobiele apps consistent worden toegepast.
FAQ
Begin wanneer dezelfde backend meer dan één client bedient (web, mobiel, interne tools), of wanneer support en on-call steeds vragen: “Is dit gebruikersfout of een systeemprobleem?” Een taxonomie levert snel resultaat zodra je herhaalde flows hebt zoals aanmelding, checkout, imports of admin-bewerkingen waar consistente afhandeling belangrijk is.
Een goed uitgangspunt is 6–12 categorieën die mensen kunnen onthouden zonder steeds de documentatie te raadplegen. Houd categorieën stabiel en breed (zoals validation, auth, rate_limit, dependency, conflict, internal) en druk de specifieke situatie uit met een code, niet met een nieuwe categorie.
De categorie stuurt gedrag; de code identificeert de precieze situatie. De categorie vertelt de UI en monitoring wat te doen (velden markeren, tot aanmelden aanzetten, backoff toepassen, retry aanbieden), terwijl de code stabiel blijft voor dashboards, alerts en support-scripts, zelfs als de UI-tekst verandert.
Behandel berichten als content, niet als identificatoren. Geef een korte, gebruikersveilige boodschap terug voor de UI en vertrouw op de stabiele code voor groepering en automatisering. Als je meer technische tekst nodig hebt, zet die dan in logs en koppel het aan dezelfde correlation ID.
Includeer een categorie, een stabiele code, een gebruikersveilige boodschap, gestructureerde details en een correlation- of request-ID. Details moeten zo zijn vormgegeven dat de client er iets mee kan doen, zoals welk veld faalde of hoelang te wachten, zonder ruwe exceptietekst te dumpen.
Geef waar mogelijk field-level pointers terug zodat de UI het exacte invoerveld kan markeren en wat de gebruiker heeft ingevuld kan behouden. Gebruik een aparte form-level fout wanneer het probleem gaat over een combinatie van velden of een businessregel (bijv. datumreeksen of kredietlimieten), zodat de UI niet het verkeerde veld raadt.
Unauthenticated betekent dat de gebruiker niet is ingelogd of dat de sessie/token ongeldig is; de UI moet dan naar inloggen sturen en de taak behouden. Forbidden betekent dat de gebruiker wel is ingelogd maar geen toestemming heeft; de UI blijft op de pagina en toont een toegangsmelding zonder gevoelige rollen of beleidsregels te onthullen.
Geef een expliciete wachttijd terug (bijvoorbeeld een retry-after waarde) en houd de code stabiel zodat clients backoff consistent kunnen implementeren. In de UI: voorkom herhaalde klikken en geef een duidelijk volgend-stap bericht, want automatische snelle retries maken rate-limiting meestal erger.
Retry alleen wanneer de fout waarschijnlijk tijdelijk is (timeouts, connection resets, upstream 502/503) en begrens retries met een kleine backoff. Voor niet-idempotente acties vereist je een idempotency key of een statuscontrole—anders kan een retry duplicaten creëren wanneer de eerste poging eigenlijk geslaagd was.
Laat de correlation ID aan de gebruiker zien (zodat support erom kan vragen) en log hem altijd server-side met de code en kerngegevens. Dit maakt het mogelijk één fout te traceren over services en clients; in AppMaster-projecten helpt het centraliseren van deze vorm om backend, web en native mobiele gedrag op één lijn te houden.


