Een component moet een bericht kunnen sturen die andere componenten kunnen ontvangen zodat zij hierop kunnen acteren indien dit een interessant bericht is voor het betreffende ontvangende component.
Notificatie als REST API component
Er wordt een notificatiecomponent (NC) ontwikkeld met de functionaliteiten:
Taken 1 en 2 worden sowieso via een REST API ontsloten.
De derde taak wordt via webhooks ingevuld, waarbij we als webhook begrijpen:
Een endpoint/url bij een abonnee die voor de NC bereikbaar is waarnaar de berichten gestuurd kunnen worden. De component acteert op deze berichten. Van de webhook wordt verwacht dat het bericht correct ontvangen is als die met een HTTP 200 status antwoordt.
We onderkennen dat voor voor deze taken een meer gespecialiseerd protocol als AMQP wellicht beter geschikt is maar wel met zijn eigen uitdagingen komt (zie ook onderaan).
Technologie onafhankelijk
De hierboven genoemde REST API wordt een “standaard”. Welke onderliggende technologie echt gebruikt wordt maakt niet zo veel uit.
Wij gebruiken (waarschijnlijk) onderliggend de Pub/Sub-functionaliteit in RabbitMQ met behulp van Topics.
Kanalen
Berichten worden verstuurd via bepaalde kanalen (Exchange in AMQP). Consumers kunnen zich op zo’n kanaal abonneren, aangevuld met bepaalde filters (Topics in AMQP). Elk component krijgt zijn eigen kanaal.
Ter illustratie: De Zaken API publiceert alles op het kanaal zaken
. Een zaak
of status wijziging wordt hierop gepubliceerd. Ook als een document wordt
toegevoegd wordt het aanmaken van de relatie tusen de zaak en het document
gepubliceerd op dit kanaal.
Componenten produceren dus berichten op een bepaald kanaal - ook wel “bronnen” genoemd. Een ZRC, DRC, BRC zijn de voor de hand liggende bronnen binnen zaakgericht werken.
Relevantie van een notificatie bepalen
Een abonnement op een kanaal kan gepaard gaan met bepaalde filters. Deze filters zorgen er voor dat de consumer bepaalde notificaties niet doorgegeven krijgt omdat ze niet voldoen aan het opgegeven filter. Hiermee kan een overdaad aan irrelevante berichten worden voorkomen.
De vervolg-last van het bepalen of een notificatie, bijvoorbeeld van een zaakwijziging of het aanmaken van een zaak, relevant is, ligt bij de consumer. De consumer dient op basis van de notificatie te bepalen of het initieel relevant is (bijvoorbeeld het Zaaktype), de details op te halen en vervolgens daadwerkelijk te bepalen of deze notificatie tot een actie leidt.
Er komt een generieke API voor het ontvangen van notificaties
Om berichten te kunnen ontvangen, zal elke consumer een beperkte API specificatie moeten implementeren, beschikbaar op de webhook-url.
Het ontvangen van berichten op de webhook-url zal generiek geimplementeerd
worden in de ZGW componenten. Ook de vorm van het bericht zal gestandaardiseerd
zijn. Bij het aanmaken van een abonnement dient de webhook-url (callbackUrl
genaamd in de API specificatie) opgegeven te worden waardoor de precieze URL
niet verder gestandardiseerd wordt.
Naast alle API-specificaties voor het beheren van abonnementen en bronnen is de kern van het verhaal de notificatie zelf. Alle attributen en attribuutnamen onder voorbehoud.
Attribuut | Omschrijving |
---|---|
kanaal | De naam van het kanaal (Exchange in AMQP) behorend bij de bron. (bijv. zaken , documenten , besluiten , etc.) |
hoofdObject | De URL van de resource behorend bij het kanaal (bijv. Zaak ) van de actuele resource (omdat we nog geen historie hebben) |
resource | Naam van de daadwerkelijk resource die is gewijzigd (bijv. Status ) |
resourceUrl | De URL van de daadwerkelijk resource (optioneel) |
actie | De actie die de gebeurtenis op abstract niveau omschrijft. Dit is een vaste lijst bestaande uit gewijzigd , verwijderd , aangemaakt eventueel aangevuld met additionele waarden, die per bron zijn vastgelegd in de standaard. |
aanmaakdatum | Datum en tijd van gebeurtenis |
kenmerken | Een lijst van objecten die de kenmerken van het bericht vormen. De mogelijke kenmerken worden vastgelegd per bron in de standaard. |
LET OP: Vooruitlopend op tijdreizen - De URL moet een
?tijdstempel=<tijd en datum>
meekrijgen zodat niet de actuele versie van de
Zaak
geeft maar de versie die echt hoort bij het bericht. Verder is er de
wens om het “verschil” van voor en na de wijziging mee te geven in de
notificatie of op te vragen.
In het geval van de ZGW API’s wordt elk component gezien als bron en heeft dus zijn eigen kanaal, met eigen kenmerken en eventueel additionele acties.
Elk component kan kenmerken
definieren die helpen bij het filteren van
relevante informatie.
zaken
Kenmerk | Omschrijving |
---|---|
bronorganisatie | Overeenkomstig veld in ZAAK |
zaaktype | Overeenkomstig veld in ZAAK |
vertrouwelijkheidaanduiding | Overeenkomstig veld in ZAAK |
documenten
Kenmerk | Omschrijving |
---|---|
bronorganisatie | Overeenkomstig veld in ENKELVOUDIG INFORMATIEOBJECT |
informatieobjecttype | Overeenkomstig veld in ENKELVOUDIG INFORMATIEOBJECT |
vertrouwelijkheidaanduiding | Overeenkomstig veld in ENKELVOUDIG INFORMATIEOBJECT |
besluiten
Kenmerk | Omschrijving |
---|---|
verantwoordelijkeOrganisatie | Overeenkomstig veld in BESLUIT |
besluittype | Overeenkomstig veld in BESLUIT |
Op dit moment nog niet gespecificeerd.
Een consumer abonneert zich op notificaties door de volgende request naar de NC te sturen:
POST /api/v1/abonnementen HTTP/1.0
Authorization: Bearer abcdef1234
Content-Type: application/json
{
"callbackUrl": "https://zaken-api.vng.cloud/api/v1/callbacks",
"auth": "Bearer aef34gh...",
"kanalen": [{
"naam": "zaken",
"filters": {
"bronorganisatie": "082096752011",
"zaaktype": "https://example.com/api/v1/zaaktypen/5aa5c",
"vertrouwelijkheidaanduiding": "*"
}
}, {
"naam": "documenten",
"filters": {
"bronorganisatie": "082096752011",
"informatieobjecttype": "https://example.com/api/v1/informatieobjecttype/b8c11",
"vertrouwelijkaanduiding": "openbaar"
}
}]
}
Het DELETE
n, PATCH
en PUT
op individuele subscribers moet mogelijk zijn.
Details moeten ook opgevraagd kunnen worden, met uitzondering van de auth
gegevens.
Filter parameter(s) op kanalen
zijn handig.
Alle kenmerken
die een component definieert MOETEN aanwezig zijn in een
filter maar de waardes mogen naar wens worden ingevuld zodat er alleen berichten
binnenkomen die voldoen aan de filterkenmerken. De waarde *
betekent dat er
niet gefilterd wordt op het element.
Onder de motorkap worden de kenmerken vertaald naar een RabbitMQ-“topic”. Elke
waarde wordt base64-encoded en gescheiden door punten. Het voorbeeld wordt dus
MDgyMDk2NzUyMDEx
.aHR0cHM6Ly9leGFtcGxlLmNvbS9hcGkvdjEvemFha3R5cGVuLzVhYTVj
.b3BlbmJhYXI=
,
ofwel als we elk element weer decoden:
082096752011
.https://example.com/api/v1/zaaktypen/5aa5c
.openbaar
.
Dit is een intern implementatiedetail.
Een kanaal is het equivalent van een exchange in RabbitMQ.
Kanalen worden aangemaakt door producers (bronnen), en zijn gekarakteriseerd door een unieke naam.
POST /api/v1/kanalen HTTP/1.0
Authorization: Bearer abcdef1234
Content-Type: application/json
{
"naam": "zaken",
"documentatieLink": "http://example.com",
"filters": [
"bronorganisatie",
"zaaktype",
"vertrouwelijkheidaanduiding"
]
}
Een kanaal mag slechts eenmalig aangemaakt worden.
Daarnaast moet het ophalen van kanalen mogelijk zijn, zodat consumers kunnen zien waarop ze zich kunnen abonneren.
De filters
geven aan welke kenmerken zullen opgenomen worden in berichten
op dit kanaal, en tonen waarop consumers kunnen filteren. Bij het abonneren
moeten de filters getoetst worden tegen deze filters. Ook hier is volgorde
belangrijk.
De NC moet notificaties kunnen ontvangen:
POST /api/v1/notificaties HTTP/1.0
Authorization: Bearer abcdef1234
Content-Type: application/json
{
"kanaal": "zaken",
"hoofdObject": "https://zaken-api.vng.cloud/api/v1/zaken/d7a22",
"resource": "status",
"resourceUrl": "https://zaken-api.vng.cloud/api/v1/statussen/d7a22/721c9",
"actie": "create",
"aanmaakdatum": "2018-01-01T17:00:00Z",
"kenmerken": {
"bron": "082096752011",
"zaaktype": "https://example.com/api/v1/zaaktypen/5aa5c",
"vertrouwelijkeidaanduiding": "openbaar"
}
}
Onder water worden de kenmerken in een bepaald formaat gebruikt voor topics, zodat consumers deze eenvoudiger kunnen filteren.
Merk op dat de kenmerken als Array
opgebouwd worden. Dit is omdat Object keys
in JSON geen inherente volgorde hebben, en de volgorde wel belangrijk is
voor interne doeleinden.
In eerste instantie zetten we enkel in op push via webhooks. Later laten we toe om ook notificaties te pullen.
GET /api/v1/abonnementen/ae54ef/notificaties HTTP/1.0
Authorization: Bearer abcdef1234
Accept: application/json
[{
"kanaal": "zaken",
"hoofdObject": "https://zaken-api.vng.cloud/api/v1/zaken/d7a22",
"resource": "status",
"resourceUrl": "https://zaken-api.vng.cloud/api/v1/statussen/d7a22/721c9",
"actie": "create",
"aanmaakdatum": "2018-01-01T17:00:00Z",
"kenmerken": {
"bron": "082096752011",
"zaaktype": "https://example.com/api/v1/zaaktypen/5aa5c",
"vertrouwelijkeidaanduiding": "openbaar"
}
}]
Dit zal enkel de niet bezorgde notificaties teruggeven.
Een van de grote voordelen van direct op AMQP in te haken is dat het protocol oplossingen voorziet om te garanderen dat berichten in de juiste volgorde aankomen.
In het scenario dat er bijvoorbeeld twee snelle statusupdates zijn van:
Als je geen AMQP gebruikt, kan het zijn dat door de intrinsieke aard van TCP/HTTP statusupdate 3 voor 2 afgeleverd wordt op de webhook, door latency op de eerste call bijvoorbeeld.
Dit zou kunnen leiden tot een incorrecte statusupdate via push bij clients.
We verwachten dat dit geen probleem wordt, omdat de inhoud van de wijziging niet in de notificatie opgenomen is, maar enkel de kennisgeving dat iets gebeurd is, en het is aan de consumer om de nieuwe state van de betrokken resource(s) te bevragen. Dit betekent ook dat snelle, opeenvolgende notificaties in principe kunnen geconsolideerd worden (orde van <1s).
AMQP voorziet ook in de garantie dat messages bezorgd worden als een node (tijdelijk) onbereikbaar is en later weer online komt. Dit kan het geval zijn tijdens deployments bijvoorbeeld, of een onverwachte outage.
Met webhooks kan de HTTP statuscode bij het afleveren gecontroleerd worden. Indien de consumer niet antwoordt met een HTTP 200 status, dan kan het bericht gemarkeerd worden als ‘niet-afgeleverd’, en kan er opnieuw geprobeerd worden (met exponential backoff). Ook kan de NC een interface bieden om events te re-senden.
Door de aard van de berichten (enkel kennisgeving dat er een event is, niet wat de inhoud is), informeert dit ook de client om data te re-fetchen, en is het risico op kwalijke gevolgen van vertraagde berichten beperkt.
NLx ondersteund geen AMQP.
Berichten worden in principe niet bewaard in de NC. Ontvangen notifications worden meteen doorgezet.
Een uitzondering hierop is het bewaren van berichten om ze opnieuw te kunnen versturen indien aflevering niet gelukt is - deze moeten opnieuw gescheduled worden voor aflevering. Hierbij moet het mogelijk zijn om te configureren hoevaak gepoogd moet worden een bericht af te leveren en hoe lang je moet wachten om opnieuw te proberen.
Zowel bij Regie- en Zaakservices als bij DigiLevering wordt notificaties opgelost met een publish/subscribe mechanisme. Een centrale notificatiecomponent ontvangt gebeurtenissen en distribueert deze naar abonnees. Abonnees hebben de mogelijkheid zich te abonneren op bepaalde gebeurtenissen. Bij DigiLevering zijn gebeurtenissen gedefinieerd in termen van objecttypen en attributen en kunnen bovendien filters worden gedefinieerd op attributen van de objecten.
Bij Regie- en Zaakservices kunnen alleen notificaties op zaakniveau worden gestuurd, d.w.z. als een zaak wordt gecreeerd of gewijzigd. De volgende gegevens moeten worden opgenomen in het notificatie-bericht :
Ideeen en attributen die andere standaarden gebruiken worden bekeken!
Bronnen:
Voorbeeld sequence diagram (in-depth)
Voorbeeld sequence diagram (high-level).
De referentie-implementatie implementeert geen features die als enige doel hebben om de reliability te verhogen - de scope creep hiervoor is namelijk te hoog voor de beschikbare resources.
De hosting in een Kubernetes cluster met redundante pods beschermen voldoende tegen onverwachte crashes. Daarnaast worden de berichten gelogd en is het mogelijk om met een developer-interventie notificaties die niet afgeleverd konden worden, opnieuw te versturen.