Temporaliteit in projecties
In de handreiking wordt temporaliteit van projecties reeds kort beschreven. Dit document legt uit hoe temporaliteit werkt in betrouwbare registers. We werken op van de eenvoudigste vorm —alleen de actuele stand— naar unitemporaliteit (geldigheidstijdstippen) en uiteindelijk bitemporaliteit (geldigheids- én projectietijdstippen).
We gebruiken hier een voorbeeld dat zou kunnen bestaan in de basisregistratie personen: We willen de persoonsinformatie opvragen aan de hand van een persoonId (bijvoorbeeld een BSN).
Alleen de actuele stand
De eenvoudigste vorm van een projectie bevat alleen de huidige, actuele stand van zaken. Er wordt geen historische informatie bijgehouden. Deze aanpak is eenvoudig, maar biedt geen mogelijkheden om te beantwoorden hoe de data er in het verleden uitzag.
Output voorbeeld
Een typische query-ingang van een projectie is een REST-api waar je parameters kan opgeven en een output krijgt, in dit voorbeeld in de vorm van json:
GET /persoon?id=41232
{
"persoonId": 41232,
"adres": "Groene woud 3",
"woonplaats": "Gorinchem"
…
}
Het is ten behoeve van performance vaak wenselijk projecties te persisteren, hieronder gerepresenteerd in een tabelvorm.
| id | naam | adres |
|---|---|---|
| 41232 | Piet Pietersen | Groene woud 3 |
Maar in het kader van een betrouwbaar register hebben we nu een probleem als de data wijzigt. Hoe verzorgen we de herhaalbare vraag nadat er op 1 juni 2026 een verhuizing geweest is?
Temporaliteit
Door geldigheidstijdstippen (als domeintijdstip) vast te leggen en querybaar te maken kun je veranderingen over tijd volgen. Elke mutatie krijgt een geldigheidstijdstip mee dat aangeeft vanaf wanneer deze in de echte wereld geldt.
Output voorbeeld
Door het geldigheidstijdstip toe te voegen aan de API kunnen we zien dat op 1 juni, voor de verhuizing, het adres Groene woud 3 was, en na de verhuizing Straatweg 4.
Voor verhuizing
GET /persoon?id=41232&geldigheidstijdstip=2026-06-01
{
"persoonId": 41232,
"adres": "Groene woud 3",
"woonplaats": "Gorinchem"
…
}
Na verhuizing
GET /persoon?id=41232&geldigheidstijdstip=2026-06-02
{
"persoonId": 41232,
"adres": "Straatweg 4",
"woonplaats": "Gorinchem"
…
}
In de opslag zien we dat we de uniciteit van een persoonId verliezen: er is een kolom, geldigheid, bij gekomen om de tijdstippen bij te houden. Maar op deze manier kunnen we veranderingen over tijd vastleggen.
| id | geldigheid | naam | adres |
|---|---|---|---|
| 41232 | 1998-06-05 | Piet Pietersen | Groene woud 3 |
| 41232 | 2026-06-02 | Piet Pietersen | Straatweg 4 |
Deze aanpak werkt goed zolang alle mutaties correct zijn, en na elkaar plaatsvinden. Maar wat nu als het Straatweg 42 had moeten zijn in plaats van Straatweg 4?
Correctie: het probleem
Stel, op 2 juni werd het adres geregistreerd als Straatweg 4, maar later bleek dat een typefout. Het had Straatweg 42 moeten zijn. Hoe corrigeer je dit in de projectie?
Als je de fout corrigeert met een mutatie van de bestaande regel ontstaat er een probleem: op 27 juni had de afnemer Straatweg 4 teruggekregen. Die registratie was op dat moment (voor zover bekend) correct, maar wordt achteraf gezien incorrect. De bestaande regel muteren schendt het principe van de herhaalbare vraag: bij dezelfde vraag zou je hetzelfde antwoord terug moeten krijgen. Het (achteraf) aanpassen van een regel in de storage is dus onwenselijk. Maar hoe lossen we dit dan op?
Voor uitleg over correcties van gevolgen, zie Correcties in de handreiking.
Bitemporaliteit: geldigheid én projectietijdstip
De oplossing is bitemporaliteit: naast het tijdstip waarop iets in de echte wereld geldt (geldigheidstijdstip), houden we ook bij wanneer het in ons systeem bekend is geworden: het projectietijdstip (als systeemtijd).
Output voorbeeld
Voor correctie op 11 juni
GET /persoon?id=41232&
geldigheidstijdstip=2026-06-11&
projectietijdstip=2026-06-28
{
"persoonId": 41232,
"adres": "Straatweg 4",
"woonplaats": "Gorinchem"
…
}
Na correctie op 11 juni
GET /persoon?id=41232&
geldigheidstijdstip=2026-06-11&
projectietijdstip=2026-06-30
{
"persoonId": 41232,
"adres": "Straatweg 42",
"woonplaats": "Gorinchem"
…
}
In onze storage hebben we nog een extra kolom nodig:
| id | geldigheid | projectietijdstip | naam | adres |
|---|---|---|---|---|
| 41232 | 1998-06-05 | 2023-02-12 | Piet Pietersen | Groene woud 3 |
| 41232 | 2026-06-02 | 2026-06-02 | Piet Pietersen | Straatweg 4 |
| 41232 | 2026-06-02 | 2026-06-29 | Piet Pietersen | Straatweg 42 |
Deze bitemporele aanpak maakt correctie mogelijk in combinatie met herhaalbaarheid. Je kunt zowel kijken naar hoe het had moeten zijn, als naar wat er op een bepaald moment geregistreerd stond.
Actueel en opgevolgd
De projectie kan meerdere toestanden bevatten die betrekking hebben op hetzelfde geldigheidstijdvak. Dat gebeurt wanneer een later gevolg een eerder gevolg voor hetzelfde tijdvak vervangt – bijvoorbeeld door bijstellen oftewel bij een correctie.
De meest recente toestand voor elk geldigheidstijdvak is actueel. "Meest recent" wordt bepaald op basis van het projectietijdstip: de projectierij met het nieuwste projectietijdstip geldt.
Een collectie gegevens, bepaald door het domein, kan meerdere actuele toestanden hebben – één per geldigheidstijdvak. Elke actuele toestand vertegenwoordigt de geldende stand voor zijn eigen tijdvak.
Een toestand die niet meer actueel is voor zijn geldigheidstijdvak heet opgevolgd. Het is niet verwijderd uit de projectie: de geschiedenis blijft volledig bewaard. Een opgevolgde toestand is nog steeds opvraagbaar, bijvoorbeeld voor auditing of historisch onderzoek.
Een opgevolgde toestand kan ontstaan door:
- Een domeinwijziging – een nieuw gevolg vervangt een eerder gevolg voor hetzelfde geldigheidstijdvak. Dit speelt met name bij bijstellen wat we ook wel corrigeren of herstel noemen. Het
gevolgtijdstipvan de nieuwe versie verschilt dan van het oorspronkelijke toestand. - Een replay – de gevolgen waren correct voor het geldigheidstijdvak, maar er zijn redenen om deze opnieuw toe te passen. Een oorzaak daarvoor kan (ook) bijstellen (corrigeren) van voorgaande situaties zijn, terwijl latere gevolgen ongewijzigd blijven. Dit vraagt om degelijke validaties maar is mogelijk. Zie bijvoorbeeld het ontbrekende overdracht achteraf toegevoegd voorbeeld. Een andere oorzaak is technisch van aard, namelijk de projectiecode bevatte een bug. Na een bugfix worden de gevolgen opnieuw verwerkt. De replay produceert een nieuwe toestand met hetzelfde
gevolgtijdstipals de 'bugged' toestand, maar een laterprojectietijdstipen een verbeterde toestand.
Dit onderscheid is zichtbaar in het bitemporeel diagram. Op de gevolgtijdstip-as vallen de 'bugged' toestand en de gecorrigeerd toestand samen in hetzelfde tijdvak: ze verwijzen naar hetzelfde gevolg. Op de projectietijdstip-as zijn ze wél van elkaar gescheiden. Een replay is dus alleen zichtbaar op de projectietijdstip-as – en dat is semantisch correct.
Zie het voorbeeld Projectiefout hersteld via replay voor een concrete uitwerking.
Dit was slechts een eerste blik op (bi)temporaliteit. In de volgende hoofdstukken gaan we verder in op het bijwerken van projecties, en beschrijven we implementaties van projecties, zoals het (meer traditionele)bitemporele relationele model of het alternatief de bitemporele payloadstore.