Datum: 2026-08-31
Proposed
subject in de CloudEvents is de notificatie-id in plaats van de DV-referentie, en de inkomende DV-koppelvlakken lopen over FSC met een OAuth2-token op berichtniveau.Het Notificatie Management Component (NMC) doorloopt per notificatie zes stappen:
dvRef), een berichttype en, afhankelijk van het regie-model, een e-mailadres (gedecentraliseerd) of een identificerend nummer (gecentraliseerd, waarbij het NMC het e-mailadres bij de Profielservice ophaalt).Het NMC legt per notificatie een statusgeschiedenis vast: één rij per statusovergang met een volgnummer per notificatie, het tijdstip van de bron (voor een receipt de completed_at van NotifyNL) en het tijdstip van registratie op de eigen klok, met op de notificatierij een projectie van de laatste status. De statuswaarden zijn die van de NotifyNL-receipt (delivered, temporary-failure, permanent-failure, technical-failure) plus created, sending en onbekend; een eerdere status die terugkomt wordt opnieuw vastgelegd. Elke nieuw vastgelegde status gaat na de commit als CloudEvent naar de callback-URL van de DV, met ten hoogste drie pogingen. Een status-query, een volgnummer in de callback, een herverzending en een navraag bij NotifyNL ontbreken.
De kwaliteitseisen, in volgorde van gewicht:
De volgende eigenschappen van NotifyNL wegen zwaar in de keuze:
reference mee die in de receipts terugkomt en waarop gezocht kan worden.temporary-failure volgt pas nadat de e-mailprovider tot 72 uur heeft geprobeerd af te leveren; permanent-failure betekent een ongeldig adres; bij technical-failure hoort de aanroeper opnieuw aan te bieden.delivered is niet definitief: tot zeven dagen erna kan de e-mailprovider het bericht alsnog weigeren en stuurt NotifyNL een faalreceipt. Onder de Wet modernisering elektronisch bestuurlijk verkeer (MEBV) geldt de aflevering na die termijn als geslaagd; een latere bounce blijft mogelijk, maar telt niet mee.Vrijwel elke stap is asynchroon en aflevering weegt zwaarder dan snelheid. Dat roept de vraag op of een event-driven architectuur (een message broker met choreografie, of event sourcing) het passende model is, en zo niet, welke stappen dan wel als event en welke als opdracht gemodelleerd horen te worden.
Het NMC is eigenaar van de notificatielevenscyclus en voert die uit als een georkestreerde state machine in PostgreSQL met een transactioneel eventlog. Er komt geen message broker en geen workflow-engine in de kern.
notificatie en poging (één rij per verzendpoging) zijn de bron van waarheid. Eén overgangsfunctie is de enige schrijver van de status; zij vergrendelt de notificatierij (FOR UPDATE) vóór elke schrijfactie op die rij, toetst de overgang aan de toegestane overgangen, verhoogt de versie en schrijft in dezelfde transactie een event weg in het eventlog event. De applicatie schrijft het event; een databasetrigger weigert een wijziging van de versie zonder een event met hetzelfde volgnummer in dezelfde transactie, zodat ook een herverzending (de status blijft verzonden) een event schrijft. De trigger houdt de tabel van toegestane paren (van, naar); de kopie in de applicatie wordt door een test tegen de triggerdefinitie gecontroleerd. De regels die van de poging afhangen (aantal pogingen, ordening van receipts, geldig_tot) staan in de receiptverwerking, niet in de trigger.due-tijdstip. Workers claimen taken met SELECT ... FOR UPDATE SKIP LOCKED op (soort, DV-id, due): een claimronde kiest de DV's met openstaand werk en claimt per DV een deel van de batch, met tokens uit een verzendbudget per Notify-service. De claim is een eigen transactie die vóór de uitvoering committet; de overgang na de uitvoering is een tweede transactie waarin de rijvergrendeling op de notificatie aan elke schrijfactie op die rij voorafgaat. De lease dekt de hele batch en wordt per taak verlengd vóór elke externe aanroep. Timers zijn data (due), geen berichten. De pogingenteller telt alleen mislukkingen in het NMC zelf; een uitstel na een fout in de aanroep van NotifyNL, de Profielservice of het LDV telt niet mee. Een uitgeputte verzendtaak leidt tot technisch-mislukt; een uitgeputte taak van een andere soort krijgt de status mislukt, telt in een metriek en wacht op beheer, dat de taak heropent of de overgang forceert. Een controletaak zoekt periodiek elke notificatie zonder terminale status en zonder open of mislukte taak en plant de ontbrekende taak opnieuw: een verzendtaak bij aangenomen, een herclaim van dezelfde poging bij in-verzending, bij verzonden een verzendtaak als geen poging lopend is en anders een navraagtaak, en een vaststeltaak bij bezorgd; een unieke index op (notificatie, soort) voor open taken voorkomt dubbele planning; receipttaken vallen daarbuiten en zijn uniek op (poging, tijdstip uit de receipt), zodat meerdere receipts voor één notificatie tegelijk open kunnen staan. Een onderhoudstaak maakt de volgende partitie van het eventlog aan zodra de lopende voor 80% is gevuld; een default-partitie vangt op wat daarbuiten valt.event: notificatie-id, DV-id, volgnummer, van, naar, reden, tijdstip en transactie-id. Het volgnummer is gelijk aan de versie van de notificatie na de overgang. tijdstip is het registratietijdstip op de klok van het NMC; het tijdstip van de bron, voor een receipt de completed_at van NotifyNL, staat op de poging en is het tijdstip voor het afleverbewijs. Het log bevat geen contactgegevens, geen identificerende nummers en geen DV-referentie; het is pseudonieme informatie die de DV kan herleiden en wordt als persoonsgegeven behandeld. Een cursor bestaat uit een cluster-epoch, de transactie-id en het event-id en is voor de DV een ondoorzichtige tekst. De feed leest op (transactie-id, event-id) onder het visibiliteitswatermerk van de oudste lopende transactie, zodat een cursor nooit een laat gecommit event overslaat. De feed garandeert geen volgorde tussen events van dezelfde notificatie: de DV verwerkt per subject op sequence en negeert een lager volgnummer na een hoger. Het cluster-epoch wordt door beheer verhoogd bij een herstel uit back-up of een promotie waarbij de transactie-id-reeks niet doorloopt; een cursor met een ander epoch of in een verwijderde partitie krijgt 410 en de DV herstart vanaf het oudste beschikbare event. Het log is de enige bron voor terugkoppeling aan DV's, voor rapportage en voor het afleverbewijs en heeft één bewaartermijn, die van het afleverbewijs. Het is gepartitioneerd op bereiken van transactie-id, zodat de feed alleen de partities vanaf de cursor leest; een partitie wordt opgeruimd op de leeftijd van haar jongste event.reference is het id van de poging; een receipt met een reference die op geen poging past wordt niet opgeslagen, alleen geteld. De verwerktaak beoordeelt de receipt onder de rijvergrendeling: draagt de poging nog geen NotifyNL-id (de worker viel uit tussen de 201 en de commit), dan neemt de poging het id uit de receipt over en voert de verwerktaak eerst de overgang in-verzending naar verzonden uit namens de worker, of bij een herverzending het vastleggen van de poging; past het id op de poging of op een van haar duplicaat-id's, dan wordt de receipt verwerkt; anders wordt het id als duplicaat op de poging vastgelegd en de receipt verwerkt alsof het de poging betreft, omdat elk duplicaat dezelfde ontvanger en dezelfde inhoud draagt. Receipts worden per poging geordend op het tijdstip in de receipt zelf (completed_at), begrensd op de systeemtijd, niet op aankomst. Een receipt met een status die het NMC niet kent wordt op de poging vastgelegd zonder overgang en geteld; de navraag loopt door en eindigt zonder herkende uitkomst in bezorgstatus-onbekend. De afleverstatus-navraag voert de navraagtaken (soort reconciliëren) uit die de verzend-commit per poging plant: zij vraagt de status op van elke poging met een NotifyNL-id en zonder receipt, na 1, 6 en 24 uur en daarna dagelijks tot de bewaartermijn van NotifyNL, in batches en met een vast aandeel van hetzelfde verzendbudget als het verzenden; een verwerkte receipt rondt de navraagtaak af. Een 404 op die opvraag, of het verstrijken van de bewaartermijn van NotifyNL zonder uitkomst, sluit de navraag af: de poging eindigt in onbekend en de notificatie in bezorgstatus-onbekend, de enige terminale status die een later binnengekomen receipt nog mag corrigeren. Een receipt delivered brengt de notificatie naar bezorgd en plant een vaststeltaak met due op het tijdstip uit de receipt plus de vaststellingstermijn (de termijn uit de MEBV, zeven dagen, als configuratiewaarde) plus het callback-venster van NotifyNL als marge, zodat een faalreceipt met een completed_at binnen de termijn de taak niet kan kruisen. Die taak sluit de notificatie af als definitief-bezorgd. Een temporary-failure of permanent-failure binnen de vaststellingstermijn wordt verwerkt volgens de gewone regels en rondt de open vaststeltaak af zonder overgang; een nieuwe delivered plant een nieuwe vaststeltaak. Een faalreceipt na de termijn wordt op de poging vastgelegd zonder overgang. De vaststeltaak vraagt niets na bij NotifyNL, omdat de bewaartermijn van NotifyNL de vaststellingstermijn niet hoeft te dekken.sequence is het volgnummer, subject de notificatie-id) via GET /api/nmc/v1/notificaties/wijzigingen?cursor= en bevestigt de cursor. De bij onboarding geregistreerde webhook blijft optioneel; een terugkoppeltaak per DV leest hetzelfde log onder hetzelfde watermerk en levert elk event, gebundeld per aanroep, met een ondertekende bearer-JWT. De bevestiging is de feedcursor die de DV zelf schrijft; de webhook houdt per DV alleen een leverpositie bij en een 2xx op de webhook is geen bevestiging. Elke webhook-aanroep draagt de cursor van het laatst geleverde event; een DV met webhook bevestigt daarmee via dezelfde bevestigingsaanroep. Onbevestigde terminale statussen ouder dan een per DV afgesproken termijn worden per DV gemeld op het beheerkanaal uit het DV-contract; na de maximale cursorleeftijd uit dat contract vervalt de cursor (410) en houdt hij het opruimen van het log niet langer tegen. Een gepauzeerde webhook hervat met een oplopende wachttijd tot ten hoogste een dag, of eerder op verzoek van de DV.dvRef wordt aan de deur vervangen door een HMAC over DV-id en dvRef, met een pepper per doel en een pepperversie op de rij; het NMC bewaart geen door de DV gekozen referentie in platte tekst. Het e-mailadres (gedecentraliseerd) of het identificerend nummer (gecentraliseerd) en de waarden voor de personalisation staan versleuteld op de rij met een sleutel per notificatie, gewrapt door een KEK uit de sleutelvoorziening van het platform; de KEK wordt bij het opstarten opgehaald en in het geheugen gehouden, zodat de sleutelvoorziening niet op het aanroeppad ligt. Het wissen van de gewrapte sleutel is de wisactie en gebeurt als taak op een vaste termijn na de terminale status; de envelope dient de rotatie van de KEK (herwrappen van de sleutels per notificatie door een onderhoudstaak, zonder herversleuteling van de gegevens) en de versleuteling in rust, niet het wissen uit back-ups, dat door het verlopen van de back-ups gebeurt. Ontsleutelen gebeurt alleen in de verzendtaak en in de taak ongeldig melden, die het identificerend nummer nodig heeft; er is geen leespad voor beheer. NotifyNL bewaart het adres en het samengestelde bericht gedurende zijn eigen bewaartermijn; die termijn staat in de verwerkersovereenkomst en de afleverstatus-navraag is erop afgestemd. Het e-mailadres uit de Profielservice wordt binnen de verzendtaak opgehaald en nooit opgeslagen.permanent-failure een taak die het e-mailadres bij de Profielservice ongeldig meldt: een idempotente aanroep over FSC met het identificerend nummer in de body. Het event "onbereikbaar" gaat zonder persoonsgegevens het log in; de opvolging ligt bij de DV.private_key_jwt), uitgegeven door de IAM-gateway van MOZa, met het OIN van de DV als claim. Een dienstverlenerregister, gevuld bij onboarding, koppelt het OIN aan de dvId en houdt per DV de toegestane berichttypen, de quota, de webhook, de bevestigingstermijn, de maximale cursorleeftijd en het beheerkanaal. De dvId staat op de notificatie en op elk event; status, zoeken, annuleren, feed en bevestiging werken uitsluitend binnen de dvId uit het token, en een notificatie van een andere DV bestaat voor de aanroeper niet (404). De HMAC over DV-id en dvRef maakt zoeken per DV afgesloten; dvRef is per contract een ondoorzichtige referentie zonder persoonsgegevens. Quota per DV worden bij de aanname afgedwongen (429), de feed heeft een maximale paginagrootte en een aanroeplimiet per DV. De redenen die de DV ziet zijn grof: onbereikbaar, geen-contactgegevens, verlopen, geannuleerd, technisch; een onbekende partij en een ontbrekende voorkeur vallen beide onder geen-contactgegevens, en een DV met een ongewoon hoog aandeel geen-contactgegevens wordt gemeld.De keuze uit ADR 0020 voor CloudEvents, de status-query en de webhook als push blijft staan; deze ADR voegt de cursorfeed met bevestiging toe, die in ADR 0020 niet als optie was gewogen, en wijzigt twee punten. subject is de notificatie-id, omdat het log geen DV-referentie bevat. De inkomende DV-koppelvlakken lopen over FSC, met daarbovenop een OAuth2-token op berichtniveau dat de DV identificeert.
De CloudEvents in feed en webhook hebben: id het event-id, source de URI van het NMC, type nl.overheid.moz.notificatie.status.<naar>, subject de notificatie-id, time het tijdstip, sequence het volgnummer met sequencetype Integer, en als data van, naar, reden en versie. sequence is per subject oplopend; de feed als geheel heeft geen doorlopende teller, de cursor vervult die rol.
De paden dragen een major-versie: POST /api/nmc/v1/centraal/notificaties, POST /api/nmc/v1/decentraal/notificaties, GET /api/nmc/v1/notificaties/{id}, GET /api/nmc/v1/notificaties?dvRef= (een lijst), DELETE /api/nmc/v1/notificaties/{id} (annuleren), GET /api/nmc/v1/notificaties/wijzigingen?cursor=, PUT /api/nmc/v1/notificaties/wijzigingen/bevestiging, en de receipt-callback POST /api/nmc/v1/notifynl-callback. Fouten zijn RFC 9457-problemen met een eigen type voor het quotum (429) en voor een vervallen cursor (410).
"Event" betekent in deze ADR: een rij in het eventlog, gelezen door consumers met een cursor onder een visibiliteitswatermerk. Nergens is een broker of choreografie bedoeld. Per stap staat hieronder wat een event-model op die plek zou opleveren en wat de doorslag gaf. Twee constateringen gelden voor elke stap:
SKIP LOCKED levert die verdeling; een broker-queue levert dezelfde semantiek via een tweede systeem. Fan-out speelt alleen waar verschillende consumers elk álle events willen zien, en dat is stap 5.reference) blijft in elk ontwerp nodig. Het verschil zit in de stappen waarvan het werk zelf een databaseschrijfactie is: daar vallen werk en bevestiging in één transactie samen, terwijl een broker-consumer per stap twee systemen beslaat en dus een outbox of eigen idempotentie nodig heeft.Synchroon verzoek, één transactie (notificatie, eerste taak, eerste event), 202 met notificatie-id; idempotent op DV plus dvRef-HMAC plus payload-hash, binnen de bewaartermijn van de notificatie; een batch-id groepeert alleen de aanlevering.
Wat een event zou opleveren: acceptatie ontkoppeld van de opslag, met een queue die piekmomenten absorbeert.
Doorslag: de notificatierij is zelf al de buffer; na de 202 loopt de verzendpijplijn in eigen tempo leeg. Een 202 vóór duurzame opslag breekt de eis dat een geaccepteerde notificatie nooit verloren gaat, de DV heeft synchrone validatie en een direct bruikbaar handvat nodig, en stap 6 zou eventual consistent worden (404 direct na de 202).
Geleasede taak, batch-claim tegen het verzendbudget per Notify-service. Elke claim van een verzendtaak, eerste verzending of herverzending, loopt via de overgangsfunctie onder de rijvergrendeling: aangenomen naar in-verzending voor de eerste poging, verzonden naar verzonden met een nieuwe poging voor een herverzending; is de notificatie inmiddels terminaal of bezorgd, dan wordt de taak afgerond zonder verzending. Zo delen annuleren, claimen en een late receipt dezelfde vergrendeling. De commit na de 201 loopt via dezelfde functie (in-verzending naar verzonden, of het vastleggen van de poging bij een herverzending) en plant de navraagtaak voor de poging, met due na een uur. Een herclaim na een verlopen lease is geen overgang: hij hergebruikt dezelfde poging, verhoogt het claim-epoch en zoekt eerst op reference; een tweede verzending blijft mogelijk wanneer de eerste aanroep nog onderweg was, en de commit met het lagere claim-epoch wordt geweigerd. Een dubbele e-mail na een verlopen lease is een aanvaard risico; de lease begrenst het niet, de zoekopdracht op reference verkleint het.
Wat een event zou opleveren: push naar workers zonder polling, backpressure via prefetch, schalen op queue-diepte.
Doorslag: de verzendsnelheid wordt begrensd door het verzendbudget, een gedeelde teller in de database; push verplaatst alleen waar de achterstand staat. De dedup en de statuscommit blijven ook met een broker nodig, maar beslaan dan twee systemen. En tussen voorkeur- en verzendstap zou het e-mailadres berichtinhoud op de broker worden.
Eerst opslaan (zonder het e-mailadres uit de receipt), dan 200 antwoorden, daarna verwerken onder rijvergrendeling; de afleverstatus-navraag garandeert de eindstatus.
Dit is het eventvormige deel: NotifyNL meldt een event, en het ontwerp behandelt het zo: opslaan vóór verwerken, herverwerkbaar.
Waarom geen broker ertussen: de receipts komen bij NotifyNL vandaan al at-least-once en ongeordend, dus dedup en ordening horen bij de overgangsfunctie onder rijvergrendeling. De opslag-eerst-tabel is al de queue; een broker zou dezelfde regels een tweede keer nodig hebben.
due als timer; beleid per berichttype; geldig_tot geldt voor het starten van een poging: een verzending of herverzending wordt na geldig_tot niet meer gestart en de notificatie eindigt dan in verlopen; een poging die al bij NotifyNL ligt loopt door tot haar uitkomst, ook na geldig_tot.
Wat een event zou opleveren: delayed messages als retrymechanisme en een dead-letter-queue na uitputting.
Doorslag: de vertragingen zijn uren tot 72 uur en de aantallen op piekmomenten groot; daar zijn delayed exchanges ongeschikt voor. Annuleren tot aan de claim is bij een due-rij een update; een gepubliceerd uitgesteld bericht is niet meer in te trekken.
Het event "onbereikbaar" gaat het log in; de DV leest het via stap 5. Bij gecentraliseerde regie volgt direct op permanent-failure een taak die het e-mailadres bij de Profielservice ongeldig meldt, idempotent over FSC.
Wat een event zou opleveren: "onbereikbaar" als event waarop een consumer reageert. Dat event bestaat, en de DV is er de consument van.
Doorslag: het ongeldig melden wijzigt gegevens bij een andere dienst en vereist bevestiging en idempotentie: opdracht-semantiek. De Profielservice abonneert niet op een interne broker en het identificerend nummer hoort niet in een event.
Elke overgang is een event in het log; status-query, cursorfeed en optionele dispatcher lezen het; de DV bevestigt met een cursor per DV.
Waarom hier wél: dit is het enige punt met echte fan-out. Feed, dispatcher, rapportage en afleverbewijs consumeren dezelfde events, elk in eigen tempo, met een cursor die door het watermerk nooit een event overslaat.
Waarom als log en geen broker-topic: de consumers zijn DV's van uiteenlopende volwassenheid buiten onze infrastructuur; pull met bevestiging dekt ze allemaal (zie alternatief 5), en het log is tegelijk het afleverbewijs met zijn eigen bewaartermijn.
Lezen van de eigen status, met versie, op notificatie-id of via GET /api/nmc/v1/notificaties?dvRef= op de dvRef-HMAC binnen de eigen DV.
Wat een event-model zou opleveren: een apart leesmodel (projectie) dat onafhankelijk schaalt.
Doorslag: de status is al het leesmodel en is consistent vanaf de 202. Een projectie zou eventual consistent zijn en een tweede opslag vragen zonder leesvolume dat erom vraagt.
notificatie.status: aangenomen, in-verzending, verzonden, bezorgd, en terminaal definitief-bezorgd, niet-bezorgbaar, technisch-mislukt, bezorgstatus-onbekend, verlopen, geannuleerd. bezorgd is niet terminaal: binnen de vaststellingstermijn kan een temporary-failure of permanent-failure de notificatie nog naar verzonden, niet-bezorgbaar of verlopen brengen. Terminale statussen zijn absorberend, met als enige uitzondering bezorgstatus-onbekend, dat door een laat event naar bezorgd of niet-bezorgbaar mag; een late temporary-failure of technical-failure wordt daar alleen op de poging vastgelegd, zonder herverzending.
poging.status: gepland, verzonden, bezorgd, tijdelijk-mislukt, permanent-mislukt, technisch-mislukt, onbekend. Een poging is lopend zolang haar status verzonden of onbekend is.
Overgangsregels, in één functie voor receipts en afleverstatus-navraag:
aangenomen naar in-verzending; claim van een herverzending: verzonden naar verzonden met een nieuwe poging; annuleren kan alleen vanuit aangenomen.delivered op welke poging dan ook, ook op een duplicaat-id: bezorgd; een nog openstaande verzendtaak wordt bij de claim afgerond zonder verzending. De vaststeltaak brengt bezorgd na de vaststellingstermijn naar definitief-bezorgd.temporary-failure of permanent-failure op de poging waarop bezorgd rust volgt binnen de vaststellingstermijn de regels hieronder alsof de poging nog liep: verzonden met een herverzendtaak, niet-bezorgbaar of verlopen; de open vaststeltaak wordt daarbij afgerond zonder overgang. Na die termijn, en voor elke andere niet-lopende poging, wordt de receipt op de poging vastgelegd zonder overgang van de notificatie.permanent-failure: niet-bezorgbaar met reden onbereikbaar.temporary-failure of technical-failure op de eerste poging plant een herverzendtaak, waarvan de claim de nieuwe poging aanmaakt; op de tweede poging geeft temporary-failure niet-bezorgbaar met reden onbereikbaar en technical-failure technisch-mislukt.niet-bezorgbaar met reden onbereikbaar. Een 401, 403, 429 of 5xx, een verbindingsfout of een time-out betreft de aanroep, niet de poging: de taak wordt uitgesteld en herhaald zonder dat de pogingenteller oploopt. Een storing bij NotifyNL kost de notificatie geen poging; het wachten wordt alleen door geldig_tot begrensd: verstrijkt dat tijdens het uitstel, dan eindigt de notificatie in verlopen. Een 404 op een statusopvraag, of het einde van de bewaartermijn van NotifyNL zonder uitkomst: bezorgstatus-onbekend.niet-bezorgbaar met reden geen-contactgegevens. Andere fouten van de Profielservice: uitstel en herhaling, eveneens zonder dat de pogingenteller oploopt.temporary-failure of technical-failure na geldig_tot, of een uitstel dat geldig_tot passeert: verlopen in plaats van een herverzending.permanent-failure die op een poging past, ook via een duplicaat-id.Elke stap publiceert een bericht (RabbitMQ met acknowledgements, of Kafka); consumers reageren; de status ontstaat als projectie.
Voordelen: ontkoppeling van stappen, push in plaats van polling, backpressure via prefetch, fan-out naar meerdere consumers, en een model dat aansluit bij het asynchrone karakter van het domein.
Waarom niet:
Eén duurzame workflow per notificatie met activiteiten, retries, timers en signalen als primitieven.
Voordelen: timers, retries, signalen en versionering zijn opgelost; workers zijn stateless en schalen op de taakachterstand; goede zichtbaarheid per uitvoering.
Waarom niet:
due, lease en pogingenteller ook biedt.Het eventlog als enige bron van waarheid; de status als projectie.
Voordelen: één schrijfactie, het afleverbewijs zit in het log, geen discipline van "beide bijwerken".
Waarom niet: stap 6 vereist een projectie in dezelfde transactie, en dan vallen beide ontwerpen samen. Event sourcing lost de commit-ordening van de feed en de gelijktijdigheid van taken op dezelfde notificatie niet op, en herspelen en versioneren van items die weken leven is zwaarder dan een migratie op rijen. De gekozen variant houdt de status als bron en dwingt met een trigger af dat elke overgang een event schrijft.
Voordelen: push naar workers, backpressure via prefetch, dead-letter exchange, geen polling-last op de database.
Waarom niet: de timers kunnen er niet in (zie alternatief 1), dus de timers staan toch in PostgreSQL en de broker verdubbelt de SKIP LOCKED-claim. De publicatie vanuit de transactie vergt een outbox. Extra cluster zonder functionele winst.
Voordelen: de DV krijgt de status direct; sluit aan bij het webhook-patroon van de NL API Strategie.
Waarom niet als basis: het NMC weet dan niet of de DV de terminale status heeft ontvangen, en veel DV's kunnen geen beveiligd inkomend endpoint bieden. Een niet-bereikbare webhook zou bovendien per event retries opleveren en een terugkerende DV overspoelen. De status-query en de cursorfeed dekken elke DV; de webhook blijft als optie, in lijn met ADR 0020.
Alleen referentie, status en callback-URL bewaren en de rij na de terugkoppeling verwijderen.
Waarom niet: zonder eventlog is het afleverbewijs niet reproduceerbaar, bestaat er geen feed en geen bevestiging, en is elke verloren callback een stille stop. De dataminimalisatie wordt bereikt door het log persoonsgegevensvrij te houden en de versleutelde velden met de sleutel te wissen.
Per notificatie een geordende lijst van statussen met de receiptwaarden van NotifyNL, een projectie van de laatste status op de rij, en bij elke nieuw vastgelegde status direct een push naar de callback-URL van de DV.
Voordelen: het afleverbewijs zit in de geschiedenis, elke overgang is een insert, en de push is eenvoudig.
Waarom niet als eindontwerp: de geschiedenis is per notificatie geordend en heeft geen positie in commit-volgorde, dus er is geen cursor waarmee een DV de events van al zijn notificaties zonder gaten leest, en geen bevestiging; een push die na de laatste poging niet is aangekomen is verloren. De ene statuslijst vermengt de status van de notificatie met de uitkomst van een verzendpoging, waardoor een herverzending en een late receipt op een eerdere poging niet uit te drukken zijn en niet is belegd welke uitkomst telt. Optimistic locking laat twee gelijktijdige receipts botsen in plaats van ze te serialiseren. De gekozen variant houdt de insert-only geschiedenis op volgnummer, maakt van het volgnummer de versie van de notificatie, splitst notificatiestatus en pogingstatus en leest de terugkoppeling uit het log.
delivered die het callback-venster van NotifyNL niet haalt blijft onopgemerkt: de afleverstatus-navraag kijkt alleen naar pogingen zonder receipt en de vaststeltaak vraagt niets na. Het receipt-endpoint slaat eerst op en antwoordt daarna, dus dat verlies vergt een storing van dat endpoint langer dan het callback-venster. Dit is een aanvaard risico.min(achterstand, beschikbare tokens) per verzendbudget. De lease dekt de geclaimde batch en wordt per taak verlengd vóór elke externe aanroep; de terminatieperiode van een pod is langer dan één HTTP-timeout plus commit, en elke commit draagt het claim-epoch.due per DV en binnen het verzendbudget, zodat een piek van één DV de andere DV's niet blokkeert. Quota per DV worden bij de aanname afgedwongen.INSERT ... ON CONFLICT DO UPDATE ... RETURNING), zonder aparte bijvuller en zonder een andere lock; niet-gebruikte tokens vervallen met het tijdvak, en de afleverstatus-navraag heeft een vast aandeel van elk tijdvak. Het NMC gebruikt eigen NotifyNL-services; andere MOZa-diensten delen die niet, zodat het budget de enige verbruiker van de limiet is. Elke databaserol heeft een statement-timeout, een transaction-timeout en een idle-in-transaction-timeout, omdat elke lopende schrijftransactie het watermerk van de feed vasthoudt; opruimen en wissen lopen in kleine batches met korte transacties. Rapportage en feed-scans draaien op de replica, waarbij feed en watermerk uit dezelfde snapshot komen; de feed heeft een maximale paginagrootte en een aanroeplimiet per DV. De modulegrens van de verzendpijplijn (raakt alleen taak, poging en verzendbudget en roept de overgangsfunctie aan) ligt vanaf het begin vast.ANY 1 van twee standbys onder een operator met automatische failover die alleen de standby met de hoogste LSN promoveert, zodat een bevestigde commit niet verloren gaat; één synchrone standby zou bij uitval alle commits blokkeren.SKIP LOCKED-claim) binnen één transactie blijft en session-afhankelijke features (session-level advisory locks, LISTEN/NOTIFY) niet worden gebruikt.iss, de webhook-URL als aud en een korte geldigheid; de DV controleert de handtekening met de publieke sleutel uit de JWKS van het NMC. Na een per DV afgesproken aantal mislukte leveringen pauzeert de webhook en blijft de feed het pad; de terugkoppeltaak levert alleen vanaf de eigen leverpositie, nooit per event opnieuw.reference bepalen samen of een receipt wordt opgeslagen; afgewezen receipts worden geteld en niet bewaard. De authenticatie richting NotifyNL is een JWT, geen mTLS.mislukt, het aantal notificaties in bezorgstatus-onbekend, het aantal onbevestigde terminale statussen per DV, het aantal afgewezen receipts en het aandeel geen-contactgegevens per DV.notificatie en poging worden door de onderhoudstaak verwijderd na de bewaartermijn van het afleverbewijs. Het eventlog is pseudonieme informatie die door de DV herleidbaar is; per tabel is doel en bewaartermijn vastgelegd.notificatiedocs/08-data.md; de hoofdstukken 3, 4 en 5 volgen dit ontwerp voor dataminimalisatie, degradatie en terugkoppeling, en de container- en componentbeschrijving in notificatiedocs/06-software-architectuur.md volgt dit ontwerp (aanname, workers, eventlog en dispatcher in plaats van één orchestrator).notificatie.status, de receiptwaarden van NotifyNL worden de status van de poging met het tijdstip van de bron erbij, en de directe push per status wordt de terugkoppeltaak die het log leest.sequence voor pull-consumers, geen gevoelige data in contextattributen