Ga naar hoofdinhoud

Projecties

Het gevolgenjournaal legt nauwkeurig vast wat er is gebeurd, maar is in veel gevallen geen praktische bron voor bevragingen. Wie wil weten waar iemand nu woont, wil voor een antwoord niet alle adreswijzigingen sinds 1994 moeten afspelen. Voor het beantwoorden van zulke vragen maken we projecties.

Rol van projecties​

Een projectie is een uit het gevolgenjournaal afgeleide, op de informatiebehoefte van afnemers toegesneden verzameling van projectie-elementen. Een projectie-element is een individuele bewering binnen een projectie. Waar het gevolgenjournaal documenteert wat er is gebeurd, beantwoorden projecties vragen als "leeft persoon p1 nog?", "wat is zijn burgerlijke staat?" en "heeft hij gezag over p3?". Zulke vragen veronderstellen een toestand: een beeld van de wereld dat gedurende een bepaalde periode geldig is.

Niet alle projecties beschrijven een toestand. Een notificatiebericht informeert een afnemer over een verandering die door registratie van een gevolg is veroorzaakt. Net als het onderliggende gevolg is zo'n bericht temporeel atomair: het beschrijft wat er is gebeurd, niet welke toestand na dat gebeuren geldig werd.

Gevolgen en projectie-elementen zijn dus allebei beweringen. Toch hebben ze binnen het register niet dezelfde status. Het gevolg is een 'primair feit', ontstaan als uitkomst van performatief handelen en waarop een bevoegde actor kan worden aangesproken. Een projectie-element is daarvan slechts een afgeleide. Dit betekent dat projectie-elementen, anders dan gevolgen, wél opnieuw van hun invoer (gevolgen) af te leiden zijn. Door gevolgen opnieuw te verwerken (replay) kunnen projecties dus meer dan eens worden opgebouwd.

Afstemming van projecties op de informatiebehoefte van afnemers vereist dat één journaal meerdere projecties kan voeden, elk met een eigen bereik en een eigen vorm. De bijbehorende capability is Onderhouden van projecties op maat: het vermogen meerdere projecties te onderhouden die aansluiten bij de informatiebehoefte van specifieke doelgroepen.

Hoe projecties technisch worden bijgehouden, is een implementatiekeuze: ze kunnen vooraf worden berekend en opgeslagen, of op het moment van bevraging op basis van de opgegeven parameters worden gegenereerd. Het tweede deel van dit hoofdstuk, vanaf de ontwerpafwegingen, gaat over die keuze.

Projectieperspectief: continuant of occurrent?​

Als we willen begrijpen hoe projecties eruit zien, helpt het te begrijpen hoe we data willen representeren en wat de consequenties daarvan zijn. Dat doen we door om te beginnen een onderscheid aan te brengen tussen projecties die gaan over continuants (ook wel endurants of in het Nederlands voortdurenden) en projecties van occurrents (ook wel perdurants of in het Nederlands voorkomenden).

Een toestandsprojectie projecteert een entiteit - de continuant - die in de tijd beschouwd kan worden. Zo'n projectie levert voor ieder moment van het bestaan (of de geldigheid) van de continuant een complete en betekenisvolle toestand op.

Niet alle continuants zijn gelijk. Sommige daarvan kunnen onder één identiteit maximaal één geldigheidscyclus doormaken (denk aan een mens). Hun bestaansperiode is continu. Daarnaast komen continuants (zoals een huwelijk) voor die meerdere, discontinue (of onderbroken) geldigheidsperiodes kunnen kennen. Het ontstaan van een tweede (of derde, vierde enzovoort) geldigheidsperiode bij zo'n entiteit noemen we vaak herleven.

Een gebeurenprojectie projecteert een entiteit - de occurrent - die zich in de tijd voltrekt. Zo'n projectie kan alleen compleet en betekenisvol zijn als we alle momenten die de voltrekking van de occurrent duurde samen beschouwen.

Occurrents kunnen we onderverdelen naar de tijd die voltrekking duurde. Punctuele occurrents zijn instantaan (of temporeel atomair) en voltrekken zich op één moment. Duratieve occurrents voltrekken zich gedurende een periode, die in veel gevallen kan worden opgedeeld in fases (of stages).

Wetenschappelijke achtergrond en debat

In deze handreiking noemen we continuant en occurrent omdat ze helpen geprojecteerde gegevens te begrijpen. Achter deze begrippen gaan een lange wetenschappelijke geschiedenis en veel debat schuil.

De woorden continuant en occurrent werden voor het eerst beschreven in het driedelige 'Logic' (1921–1924) van de Britse logicus W.E. Johnson. Via debatten over persistentie en formele ontologie, waaronder Barry Smiths Basic Formal Ontology, vonden ze vervolgens hun weg naar de informatica.

Over deze begrippen bestaat binnen de wetenschap veel discussie. Zo stelde James F. Allen in 'Maintaining Knowledge about Temporal Intervals' dat occurrents weliswaar instantaan kunnen lijken, maar dat voltrekking bij nadere beschouwing ("turning up the magnification") toch altijd een periode met tijdsduur vraagt.

In reactie daarop ('A critical examination of Allen's theory of action and time') betoogde Antony Galton dat het problematisch is om alle occurrents als interval te behandelen en dat we daarnaast ook instants moeten kunnen herkennen. Een middenpositie wordt ingenomen door Bittner et al., die in 'Endurants and Perdurants in Directly Depicting Ontologies' de periode waarbinnen een occurrent zich voltrekt verdelen in al dan niet instantane stages (fases).

Ook het zelfstandig bestaansrecht van continuants staat ter discussie. Zo betogen perdurantisten dat alle objecten in vier dimensies bestaan. Wat we hierboven een continuant noemden, is vanuit dit perspectief geen zelfstandige entiteit, maar slechts een instantane doorsnede van zo'n vierdimensionale occurrent.

Projectiedoel bepaalt perspectief​

Of een entiteit geprojecteerd wordt als continuant of als occurrent staat los van de manier waarop we gegevens in het gevolgenjournaal of andere bron voor onze projectie hebben vastgelegd. Het projectieperspectief wordt dus niet bepaald door het vastleggingsperspectief van het bronmateriaal, maar door de manier waarop we naar dat bronmateriaal willen kijken - en in het verlengde daarvan van door de informatiebehoefte die we met een projectie willen invullen.

Dit betekent dat op basis van één set gevolgen voor de ene doelgroep toestandsprojecties kunnen worden geproduceerd, terwijl voor andere afnemers projecties vanuit occurrentperspectief worden gemaakt.

Onderstaand voorbeeld illustreert hoe op basis van één set gevolgen die rondom een vergunning zijn bijgehouden, projecties in continuant- én occurrentvorm kunnen worden geproduceerd. De toestandsprojectie beschouwt de vergunning als entiteit waarvoor een toestand kan worden getoond. Projecteren we de vergunning als occurrent, dan krijgen we een aan de hand van 'milestones' geïllustreerde levenscyclus te zien.

// In het gevolgenjournaal bekende gevolgen die de levenscyclus van de vergunning hebben beïnvloed

[
{
"gevolgId": "ev-001",
"gevolgType": "vergunningsaanvraag ontvangen",
"betreft": { "type": "aanvraag", "id": "av-2004-1459" },
"gebeurdOp": "2004-03-12"
},
{
"gevolgId": "ev-002",
"gevolgType": "vergunning verleend",
"betreft": { "type": "vergunning", "id": "vg-2004-0815" },
"beïnvloedt": { "type": "aanvraag", "id": "av-2004-1459" },
"gebeurdOp": "2004-06-18"
},
{
"gevolgId": "ev-003",
"gevolgType": "vergunning ter inzage gelegd",
"betreft": { "type": "vergunning", "id": "vg-2004-0815" },
"gebeurdOp": "2004-06-21"
},
{
"gevolgId": "ev-004",
"gevolgType": "bezwaar ontvangen",
"betreft": { "type": "bezwaar", "id": "bz-2004-0213" },
"beïnvloedt": { "type": "vergunning", "id": "vg-2004-0815" },
"gebeurdOp": "2004-06-23"
},
{
"gevolgId": "ev-005",
"gevolgType": "bezwaar afgewezen",
"betreft": { "type": "bezwaar", "id": "bz-2004-0213" },
"beïnvloedt": { "type": "vergunning", "id": "vg-2004-0815" },
"gebeurdOp": "2004-07-17"
},
{
"gevolgId": "ev-006",
"gevolgType": "vergunning onherroepelijk geworden",
"betreft": { "type": "vergunning", "id": "vg-2004-0815" },
"gebeurdOp": "2004-08-02"
}
]
// Vergunningaanvraag als continuant: het object met actuele toestand

// Request
{
"resource": "vergunningen",
"id": "vg-2004-0815",
"presentatieperspectief": "continuant",
"geldigOp": "2004-07-24"
}

// Response
{
"id": "vg-2004-0815",
"status": "verleend",
"isOnherroepelijk": "false"
}
// Vergunningaanvraag als occurrent: gefaseerde levenscyclus

// Request
{
"resource": "vergunningen",
"id": "vg-2004-0815",
"presentatieperspectief": "occurrent",
"gebeurdVanaf": "1970-01-01",
"gebeurdTot": "2004-07-25"
}

// Response
{
"id": "vg-2004-0815",
"fases": [
{ "type": "vergunning verleend", "gebeurdOp": "2004-06-18" },
{ "type": "vergunning ter inzage gelegd", "gebeurdOp": "2004-06-21" },
{ "type": "vergunning door bezwaar betwist", "gebeurdOp": "2004-06-23" },
{ "type": "betwisting vergunning door bezwaar beëindigd", "gebeurdOp": "2004-07-17" }
]
}

Intermezzo: van gevolg naar projectie

Een projectie is geen directe weergave van de gevolgen in het journaal – er zit altijd bewerking tussen. Bij de toestandsprojectie hierboven is dat bijvoorbeeld zichtbaar in de boolean isOnherroepelijk. Die is op false gezet omdat er op het gevraagde moment nog geen gevolg "vergunning onherroepelijk geworden" was geregistreerd. Om tot een te projecteren toestand te komen, worden hier dus gevolgen geïnterpreteerd.

Ook bij een gebeurenprojectie is bewerking nodig, maar die is subtieler. Omdat het gevolgenjournaal zelf al bestaat uit occurrents, zou je kunnen denken dat die in een projectie één op één overgenomen worden. De projectie in het voorbeeld hierboven wijkt niettemin subtiel af van de gevolgen waarop die gebaseerd is. Dat komt door een afwijkend perspectief. Het journaal beschrijft vanuit bijhoudingsperspectief zo nauwkeurig mogelijk wat er binnen een bounded context gebeurt. Vanuit projectieperspectief is het gewenst het gebeurde opnieuw te ordenen rond bepaalde kernconcepten.

In het voorbeeld zie je dit op twee manieren terug. Het gevolg "bezwaar ontvangen" is opgesteld rond het vanuit bijhoudingsperspectief meest relevante concept - het bezwaar zelf. In de vergunningprojectie moet dit gevolg 'hertaald' worden zodat het leest als een fase in de levenscyclus van de vergunning, ergo "vergunning door bezwaar betwist". Het gevolg "vergunningsaanvraag ontvangen" verschijnt in de projectie helemaal niet omdat de vergunning op dat moment nog niet bestond en de levenscyclus daarvan dus nog niet was begonnen.

Toestandsprojecties en gebeurenprojecties​

Het conceptuele registermodel vertaalt het onderscheid tussen continuant en occurrent naar twee hoofdvormen van projecties. In een toestandsprojectie beschrijven de projectie-elementen toestanden; de inhoud is uitgedrukt als continuant. In een gebeurenprojectie beschrijven de projectie-elementen gebeurens; de inhoud is uitgedrukt als occurrent. Dit onderscheid bepaalt welke vragen een afnemer aan een projectie kan stellen. Op basis van een toestand kan worden gevraagd wat er op enig moment gold; van een gebeuren kan worden gevraagd wanneer het plaatsvond en wat eraan voorafging.

Wat een projectie verder bevat, legt de projectiespecificatie vast als het bereik van die projectie. Daarin zitten onder meer twee temporele keuzes: welke domeintijd wordt meegenomen (alleen wat actueel geldt, of ook wat ooit gold) en welke onderschrijving (alleen wat het register nu onderschrijft, of ook wat het ooit onderschreef). Uit combinaties van die keuzes ontstaan vijf veelvoorkomende patronen, de projectie-archetypen.

Archetypen van toestandsprojecties​

ArchetypeDefinitieToelichting
Actueel geldige toestandToestandsprojectie bestaande uit de projectie-elementen die het register nu onderschrijft en die de in de domeintijd actueel geldige toestand beschrijven.Beantwoordt de vraag wat nu geldt, zoals het huidige woonadres van een persoon. De meeste bevragingen kunnen door deze projectie worden beantwoord.
In domein geldige historische toestandenToestandsprojectie bestaande uit de projectie-elementen die het register nu onderschrijft en die in de domeintijd ooit geldige toestanden beschrijven.Geeft de geschiedenis zoals het register die nu ziet: wat ooit gold, zonder wat later is onttrokken. Nodig voor vragen als "waar woonde deze persoon op 1 januari 2020?".
Alle historische toestandenToestandsprojectie bestaande uit de projectie-elementen die het register ooit heeft onderschreven en die in de domeintijd ooit geldige toestanden beschrijven.Bevat ook wat later is onttrokken. Nodig voor verantwoording: een afnemer die handelde op basis van een gegeven dat later onjuist bleek, moet kunnen aantonen dat het register dat gegeven ooit juist achtte.

Archetypen van gebeurenprojecties​

ArchetypeDefinitieToelichting
Onderschreven verloopGebeurenprojectie bestaande uit de projectie-elementen die het register nu onderschrijft.Geeft weer wat er volgens het register nu is gebeurd. Geschikt als basis voor notificaties over wat in het domein is voorgevallen.
Volledig verloopGebeurenprojectie bestaande uit de projectie-elementen die het register ooit heeft onderschreven.Bevat ook de gebeurens die het register later niet meer onderschrijft. Daarmee is de bijhoudingsgeschiedenis van het register volledig te volgen, inclusief wat wanneer is onttrokken.

Hoe deze archetypen te implementeren zijn, staat verderop bij de implementeringsvormen per projectie-archetype.

Tijd in projecties​

Bij toestandsprojecties en gebeurenprojecties horen verschillende vormen van domeintijd: geldigheidstijd voor toestanden en voltrekkingstijd voor gebeurens. Een toestandsprojectie bevraag je met parameters als geldigOp, geldigVanaf of geldigTot; een gebeurenprojectie met een voltrekkingsperiode, zoals gebeurdVanaf en gebeurdTot. Daarnaast draagt elk projectie-element een eigen systeemtijd, het projectietijdstip. Het hoofdstuk Tijd werkt dit uit, inclusief de oorsprong van het begrip geldigheidstijd en het onderscheid met voltrekkingstijd.

Projecties opbouwen​

De functie Projecteren werkt een gevolg door in een projectie. Projecteren leest daarvoor drie dingen: het gevolg, de projectiespecificatie die per projectie beschrijft hoe gevolgen daarin doorwerken, en de projectie zelf, omdat het bepalen van de verandering die een gevolg binnen een toestandsprojectie teweegbrengt veelal kennis van de bestaande of 'huidige' stand vereist. De projectiespecificatie werkt de inwerkingsregel uit het bounded contextmodel uit.

De opbouw van projecties loopt daarmee parallel aan die van het gevolgenjournaal:

GevolgenProjecties
Aanleidingcommandogevolg
Specificatievormingsspecificatieprojectiespecificatie
Huidige standopnametoestandprojectie
Resultaatgevolg in het gevolgenjournaalprojectie-element in een reeks

Projecteren bestaat uit twee deelfuncties. Openen opent een reeks voor een entiteit die nog niet in de projectie voorkwam. Bijwerken produceert een projectie-element. Door het bijwerken van projecties altijd via deze twee standaardoperaties te laten verlopen, ongeacht het soort gevolg dat wordt verwerkt, verkleint het register de kans op verwerkingsfouten. Elke verwerking volgt hetzelfde patroon; alleen de projectiespecificatie bepaalt hoe een concreet gevolg doorwerkt. Deze standaardisatie is een aanbevolen ontwerppatroon voor robuuste projectieopbouw.

Een reeks is het geheel van projectie-elementen binnen één projectie die samen een entiteit beschrijven. De reeks groepeert wat over dezelfde entiteit is vastgelegd en is daarmee de plek waar de identiteit uit het gevolgenjournaal doorwerkt. Ook elementen die over een bewering gaan, horen bij de reeks. "Bewering b1 over de bewoning van adres a1 door persoon p1 is onttrokken" beschrijft p1 niet, maar zonder dat element klopt de bewoningsgeschiedenis van p1 niet.

Een projectie-element draagt alleen de tijd van zijn eigen productie en wat zijn grond over de domeintijd zegt. Wanneer een toestand ophoudt, blijkt uit een volgend element in de reeks. Het einde in domeintijd volgt uit de volgende toestand van de entiteit, het einde van de onderschrijving uit een element dat de onttrekking weergeeft. Een tabel met kolommen voor begin en einde registratie is een manier van opslaan die meerdere elementen uit één reeks samenbrengt.

Een gevolg legt één besluit of gebeuren vast. Dat besluit kan bestaan uit deelbesluiten, maar het blijft één besluit, dat (voor zover relevant voor de projectie) in zijn geheel wel of niet in een projectie wordt verwerkt. Zie transactionaliteit bij de ontwerpafwegingen.

Voor meer details, zie de verdiepingen temporaliteit in projecties en bijwerken van projecties.

Correctie en onttrekking in projecties​

In de meeste gevallen is een verstrekt projectie-element onjuist omdat één of meerdere gevolgen waarop het steunt onjuist waren. Herstel begint dan in het gevolgenjournaal, met een correctiegevolg. Projecteren werkt dat gevolg door zoals elk ander gevolg: het voegt projectie-elementen toe. Een element dat de onttrekking weergeeft, beëindigt de onderschrijving van het onjuiste element; nieuwe elementen beschrijven met terugwerkende kracht hoe het had moeten zijn.

Bestaande projectie-elementen blijven daarbij behouden; de nieuwe elementen worden ernaast geplaatst, met een later systeemtijdstip. Het register overschrijft nooit, als temporele consequentie van het append-only-patroon.

Ook kan uit onderzoek blijken dat de gevolgen juist waren en de fout zat in het projecteren. Dan wordt de projectiespecificatie of de software die haar uitvoert hersteld, waarna de gevolgen volledig of selectief opnieuw worden verwerkt (replay). Ook in dit scenario wordt bepaald vanaf welk moment een bestaande foutieve toestand niet langer wordt onderschreven; de foutieve projectie-elementen worden opnieuw opgebouwd, en de nieuwe elementen komen naast de oude te staan. Een nieuw gevolg is hiervoor niet nodig. Dat de nieuwe elementen met een herstelde werkwijze zijn afgeleid, blijkt uit hun herkomst. Zie het voorbeeld projectiefout hersteld via replay.

Of een afnemer de onttrekking ziet, hangt af van het archetype van de projectie. Projecties die alleen bevatten wat het register nu onderschrijft – de actueel geldige toestand, de in domein geldige historische toestanden en het onderschreven verloop – laten het onttrokken element weg. Voor wie zo'n projectie bevraagt, is het resultaat voor onttrokken gegevens hetzelfde als voor gegevens die nooit hebben bestaan. Projecties die ook bevatten wat het register ooit onderschreef – alle historische toestanden en het volledig verloop – behouden het onttrokken element, naast het element dat de onttrekking weergeeft. Daarmee blijft de herhaalbare vraag beantwoordbaar en kan een afnemer aantonen dat het register een gegeven ooit juist achtte. Toegang tot die projecties kan worden beperkt, bijvoorbeeld tot juridisch of historisch onderzoek.

Projectiesoorten​

Met het onderscheid tussen continuant en occurrent en de bijbehorende temporele dimensies hebben we de bouwstenen om projecties scherper te kunnen typeren. Voor welke 'soort' projectie is welke domeintijd relevant? Geldigheidstijd, voltrekkingstijd, of geen van beide? En binnen die dimensie: zijn meerdere geldigheidsperiodes mogelijk of niet, duurt voltrekking tijd, of gebeurt dat in één moment?

Onderstaande tabel plaatst deze categorieën op een domeintijdas. Op de andere as kunnen we dan de tijdsdimensie plaatsen die wel door het register wordt bepaald: de systeemtijd. De tabel die hierdoor ontstaat kunnen we vullen met acht projectiesoorten. Voor ieder daarvan volgt hieronder een korte typering en één of meer voorbeelden.

Projectiesoorten naar perspectief en temporele vereisten

Nota bene: tabel en projectiesoorten gaan over projecties die in het normale verkeer aan afnemers worden getoond. Betrouwbaar beheer en navolgbaar foutherstel maken het in vrijwel alle gevallen gewenst systeemtijd bij te houden - dus ook voor specificatiegegevens, automatische observaties en vaste waarden.

Herlevend ding​

Een toestandsprojectie van een entiteit die meerdere, al dan niet aansluitende geldigheidsperiodes onder dezelfde identiteit kan kennen. Tussen periodes kan de geldigheid onderbroken zijn en later weer worden hervat. Voorbeeld: een huwelijk dat na ontbinding opnieuw wordt voltrokken tussen dezelfde personen.

Niet-herlevend ding​

Een toestandsprojectie van een entiteit die onder één identiteit één aaneengesloten geldigheidsperiode kent. Eindigt de geldigheid, dan eindigt de entiteit definitief – een tweede geldigheidsperiode onder dezelfde identiteit is niet mogelijk. Voorbeelden: een mens, een dienstverband, een vergunning.

Traject​

Een gebeurenprojectie van een entiteit die zich gefaseerd over een aaneengesloten periode voltrekt. Het traject heeft een aanvang, een einde en interne fases die samen de voltrekking vormen. Voorbeelden: een vergunningprocedure, een behandeltraject, een bezwaarprocedure.

Gebeurtenis​

Een gebeurenprojectie van een entiteit die zich op één moment in de tijd voltrekt. De gebeurtenis kent geen interne fases en is temporeel atomair. Voorbeelden: een melding, een meting, een klantcontact.

Systeemevent​

Een gebeurenprojectie van een gebeurtenis die uitsluitend binnen het systeem plaatsvindt en geen tegenhanger heeft in de werkelijkheid daarbuiten. Domeintijd ontbreekt; alleen systeemtijd is relevant. Voorbeelden: een logregel, een audit trail entry, een systeemmelding.

Specificatiegegeven​

Een toestandsprojectie van een entiteit met beperkte veranderlijkheid die de kaders beschrijft waarbinnen andere, meer volatiele gegevens zich bewegen. Daarom hoeft hierover bijgehouden registratiehistorie in projecties doorgaans niet te worden getoond. Voorbeelden: een zaaktype, een producten- en dienstencatalogus, een classificatie.

Automatische observatie​

Een gebeurenprojectie waarbij vastlegging zonder menselijke tussenkomst plaatsvindt op het moment van plaatsvinden, waardoor registratiemoment en voltrekkingsmoment per definitie samenvallen. Voorbeelden: een sensormeting, een GPS-track, een automatische transactie.

Vaste waarde​

Een toestandsprojectie zonder tijdsdimensie. De waarde staat onveranderlijk vast en kent geen geldigheids- of registratiehistorie. Voorbeelden: een landcode, een geslachtsaanduiding, een natuurconstante.

Ontwerpafwegingen​

Temporaliteit​

Een belangrijke factor voor een projectie is de keuze voor wat betreft temporaliteit. Voor het kunnen beantwoorden van de herhaalbare vraag is bitemporaliteit een eis: als ingegrepen wordt op de tijdlijn, bijvoorbeeld als gevolg van een correctie, dient de projectie nog steeds de oorspronkelijke toestand te kunnen reproduceren.

Bitemporaliteit brengt op zichzelf echter complexiteit met zich mee die niet in alle gevallen nodig is. Wanneer het kunnen stellen van de herhaalbare vraag minder of niet van belang is, zou een specifieke temporele projectie op de geldigheidstijdlijn of alleen een actuele toestandsprojectie ook kunnen voldoen.

Een implementatiegerichte uitleg van verschillende vormen van temporaliteit in projecties is hier te vinden.

Eventual consistency als ontwerpoverweging​

Tussen het vastleggen van een gevolg in het gevolgenjournaal en de zichtbaarheid van het resultaat in een projectie zit verwerkingstijd. Bij synchrone verwerking is die verwaarloosbaar; bij asynchrone of gedistribueerde architectuur kan het venster significant zijn. Gedurende dat venster kijkt een afnemer naar een projectie die het meest recente gevolg nog niet weerspiegelt.

Deze eventual consistency gaat uitsluitend over tijdigheid, niet over logische consistentie. Gevolgen zijn er juist op gericht om een eenduidige handeling als één samenhangend geheel vast te leggen, en ondersteunen daarmee de logische consistentie van het gevolgenjournaal. Een projectie die het meest recente gevolg nog niet weerspiegelt, is dus niet tegenstrijdig met wat al is vastgelegd – de informatie is alleen nog niet doorgekomen.

Dit is geen fout maar een eigenschap van de architectuur: gevolg en projectie leven in gescheiden stores, elk met een eigen systeemtijdstip. De inherente beperkingen aan de herhaalbare vraag benoemen dit als grens van herhaalbaarheid; hier benoemen we het als ontwerpkeuze. Bij het ontwerp van een projectie moet worden afgewogen hoeveel verwerkingstijd acceptabel is en welke consequenties dat heeft voor de garanties die aan afnemers worden geboden.

Query-complexiteit versus query-flexibiliteit​

De keuze van gegevensrepresentatie van een projectie en de gewenste informatie die een afnemer wenst, bepalen de complexiteit van de (database)query die gedaan moet worden om in een informatiebehoefte te voorzien. In het voorgestelde patroon, waarin we commando's, gevolgen en projecties van elkaar scheiden, hebben we niet meer de 'ballast' van de commandokant, en kunnen we onze projecties specifieker optimaliseren voor de afnemer(s).

Hier zijn echter nog steeds afwegingen in te maken. Een projectie kan gebouwd worden op een manier dat de payload response van een query as is (in bijvoorbeeld een json- of xml-structuur) opgeslagen wordt in de database. We noemen dit de bitemporele opslag van snapshots, kortweg snapshotting. Elke snapshot combineert daarin de payload met metadata over herkomst en tijd. Deze wijze van persisteren heeft een aantal voordelen:

  1. Dit is de best mogelijke manier om de herhaalbare vraag te kunnen beantwoorden: de payload wordt as-is opgeslagen, en (later) veranderende code beïnvloedt deze payload niet.
  2. Omdat de structuur van het bericht vastligt, ook nadat er codewijzigingen gedaan worden, kan er in terugmeldingen en onderzoeken via bijvoorbeeld xpath of jsonpath hard verwezen worden naar entiteiten of velden waarover getwijfeld wordt.
  3. De (database) opvraging is eenvoudig: op basis van tijdstippen en een index dient een payload opgezocht te worden. Dit voorkomt grotendeels het verstrekken van foutieve informatie als gevolg van foutief geïmplementeerde queries.

Zeker in combinatie met bitemporaliteit zien wij snapshotting als een goede keuze voor wat betreft informatieverstrekking. Bitemporaliteit kan, in combinatie met bijvoorbeeld ingewikkelde correcties, zeer ingewikkelde situaties opleveren die zelfs voor experts moeilijk te doorgronden zijn. Het feit dat de query eenvoudig is en de payload statisch is bij snapshotting, maakt dat deze projectievorm uitermate robuust is voor het beantwoorden van de herhaalbare vraag.

Snapshotting heeft echter ook nadelen: omdat de datastructuur zo rigide vastligt kun je moeilijker, of niet, meerdere soorten vragen beantwoorden met dezelfde projectie. Ook leent deze vorm van opslag zich minder voor joins (het combineren van verschillende payloads/entiteiten). Dit alles maakt deze structuur minder flexibel, en dit dwingt je tot het maken van veel zeer afnemerspecifieke projecties.

Het bitemporele tabelmodel kan naargelang behoefte dan een goede keuze zijn. Een relationeel model, en zeker een bitemporeel relationeel model, is veel flexibeler in het kunnen beantwoorden van allerhande soorten vragen. De aard van de datarepresentatie maakt echter wel de queries en de mapping naar de doelstructuur van de payload complexer, wat het garanderen van herhaalbaarheid van vragen doet afnemen.

Betrouwbare modellen versus onbetrouwbare realiteit​

Een goed datamodel beschrijft structuren, entiteiten en properties van een domein. Een huisnummer is een getal, terwijl een adres een straatnaam bevat als string met een maximaal aantal karakters. Deze eigenschappen worden idealiter goed gevalideerd aan de commandokant, en door dat te doen kun je duidelijke contracten afspreken met je afnemers over wat voor data je verstrekt en in welke vorm. Dergelijke modellen bepalen databasestructuren en beperkingen voor wat betreft gegevensopslag.

De realiteit is echter vaak weerbarstig: ooit verkeerd geïmplementeerde validaties, oude data uit migraties of veranderingen in het datamodel naar verloop van tijd kunnen er in veel gevallen voor zorgen dat de data die je hebt niet altijd voldoet aan het voorgeschreven gegevensmodel. Huisnummers met letters, te lange straatnamen... Als je gebonden bent aan het principe van het kunnen beantwoorden van de herhaalbare vraag zal deze data altijd foutief blijven.

Een relationeel model biedt duidelijkheid over het datamodel, maar is minder flexibel als het gaat om data die niet aan dit model voldoet. Er moet altijd een keuze gemaakt worden: voor zeldzame uitzonderingen dient het model aangepast te worden, of deze uitzonderingen kunnen niet verwerkt worden. Hetzelfde probleem geldt vaak voor berichtvalidaties via bijvoorbeeld XSD's.

Een verhuizing niet doorgeven omdat een straatnaam te lang is, vinden wij zeer onwenselijk. Afhankelijk van de datakwaliteit kan het dan een betere keuze zijn om data-opslag en berichtverkeer flexibeler te maken zodat afwijkingen van het datamodel wel opgeslagen en gecommuniceerd kunnen worden. Richting een afnemer kan het dan wenselijk zijn deze communicatie te doen met een extra melding: "in dit bericht voldoen we niet aan het datamodel".

Generieke versus specifieke implementatie​

TODO

Dit moet nog verder uitgewerkt worden

Varianten​

Hieronder volgt een niet-uitputtende lijst van varianten van projecties die gebruikt kunnen worden. Elke projectievariant heeft voor- en nadelen die, afhankelijk van de use case, sterker of minder sterk gelden. Het is niet ons doel om te stellen welk van deze varianten de voorkeur geniet. Wel willen we voor- en nadelen van projectievarianten benoemen.

TemporaliteitRelationele databasePayloadsProjectiegevolgen
actueel
temporeel
bitemporeel

Transactionaliteit​

Een gevolg legt één besluit of gebeuren vast. Dit besluit kan bestaan uit deelbesluiten of anderszins opdeelbare elementen, maar uiteindelijk betreft het nog steeds één besluit, dat (voor zover relevant voor de projectie) in zijn geheel wel of niet verwerkt dient te worden in een projectie, wat in databasetermen betekent dat een gevolg altijd in precies één databasetransactie verwerkt dient te worden. Het verwerken van een door de business erkend gevolg in meerdere (database)transacties kan er namelijk toe leiden dat datacorruptie optreedt: als een gevolg in twee transacties verwerkt wordt, zou de tweede transactie mis kunnen gaan, waarbij een ongeldige situatie ontstaat.

De implementatie-uitdaging bij projecties​

Om van gevolg te komen tot een opvraagbare projectie hebben we een aantal transities nodig, waarbij meestal een vorm van opslag een tussenvorm is tussen het gevolg en wat de afnemer uiteindelijk op zijn scherm ziet. Een simpele flow chart van dit proces ziet er als volgt uit:

Uitgaande van deze architectuur hebben we in feite twee implementatie-uitdagingen:

  1. Hoe vertalen we de gevolgen naar een opgeslagen projectierepresentatie?
  2. Hoe vertalen we de opgeslagen projectierepresentatie (herhaalbaar) naar een leverbaar artefact?

Bij beide uitdagingen hebben we een implementatievorm nodig die robuust te bouwen is zodat deze een lange periode, mogelijk tientallen jaren, betrouwbaar herhaalbare vragen kan beantwoorden. En in die periode hebben we te maken met software die continu blijft evolueren, waar regelmatig (wekelijks, of zelfs dagelijks) nieuwe features aan toegevoegd worden, en de technologie je om de zoveel jaar stuurt naar software- of datamigraties.

Zowel een gevolgenjournaal als een bitemporele store bewaart data (grotendeels) append-only: terwijl software veranderlijk is, blijft onze data behoorlijk statisch. We hebben dus vooral te maken met de veranderlijkheid van software. Hoe garandeer je dan de herhaalbare vraag? Bugs in deze software zijn ook onvermijdelijk. Hoe garandeer je hetzelfde (corrupte) antwoord na het oplossen van een bug? Zoals eerder benoemd kent herhaalbaarheid zijn grenzen, maar welke implementatiekeuzes passen het beste bij dit vraagstuk?

Door bij het opslaan van de transformatie van de gevolgen in de projectiestore te streven naar standaardisatie kunnen we fouten voorkomen: een bitemporele store kunnen bijwerken met standaardoperaties, onafhankelijk van het soort gevolg, en aan de andere kant willen we herhaalbare, simpele queries hebben zodat de kans op softwarefouten bij het beantwoorden van de herhaalbare vraag zo klein mogelijk is. De standaardoperaties voor het opbouwen van een projectie zijn hierboven besproken; ze zijn implementatietechnisch uitgewerkt in de verdieping van het bijwerken van projecties. Als elke projectie en elk gevolg afgehandeld wordt door een reeds eerder geïmplementeerde functie verkleint dat de kans op softwarefouten. Als we de transitie van gevolg naar projectie zoveel als mogelijk doen aan de 'linkerkant', kunnen we beter herhaalbaar leveren: ook de meeste softwarefouten 'persisteren'.

Aan de rechterkant van onze store kunnen we streven naar 'zo min mogelijk' software voor de verstrekking van ons product. Als opvragingen uit onze store eenvoudig zijn, is de kans dat daar softwarefouten in zitten kleiner. Onze architectuur ziet er dan als volgt uit:

Het vereenvoudigen van de opvragingssoftware is niet zonder prijs. Er zal per projectie afgewogen moeten worden welke criteria het zwaarst wegen. De volgende paragraaf gaat daarover.

Implementeringsvormen per projectie-archetype​

We onderscheidden hierboven vijf archetypische projecties: aan de toestandskant de actueel geldige toestand, in domein geldige historische toestanden en alle historische toestanden, aan de gebeurenkant het onderschreven verloop en het volledig verloop. Deze archetypen zeggen wat een projectie bevat; ze zeggen niet hoe je die bouwt. Per archetype zijn meerdere implementeringsvormen mogelijk, die langs twee assen variëren:

  • Temporaliteit: alleen de actuele stand, domeintijd zonder systeemtijd, of domeintijd en systeemtijd samen (bitemporeel).
  • Opslag- en leveringsvorm: rijen in relationele tabellen, payloads, een hybride vorm van cellen en payload, of een stream die het register zelf uitgeeft.

Dit noemen we in dit document overigens geen 'soorten'; die term laten we voor de acht projectiesoorten hierboven, die gaan over het perspectief op de entiteit en niet over de bouwvorm van de projectie.

Deze paragraaf geeft een overzicht van de implementatievormen per archetype. De afwegingen tussen de vormen staan in de ontwerpafwegingen hierboven; de beslisboom hieronder leidt van de ontwerpvraag naar één van deze vormen.

Actueel geldige toestand​

Voor de afnemer die alleen de stand van nu nodig heeft, komen twee vormen in aanmerking:

  • Actuele tabellen: één toestand per entiteit die bij elke nieuwe toestand wordt overschreven. De eenvoudigste en snelste vorm; historie ontbreekt volledig. Deze vorm kan volledig relationeel gemaakt worden.
  • Actuele payload: een payload per entiteit, eveneens herschreven bij elke nieuwe toestand.

Geen van deze vormen kan de herhaalbare vraag beantwoorden: een correctie in de domeintijd schrijft het verleden stilzwijgend over en het eerdere antwoord is weg.

In domein geldige historische toestanden​

Wie enkel vragen over de domeinhistorie wil beantwoorden – "wat gold er op moment t?" – legt geldigheidsperiodes vast:

  • Temporele tabellen: per rij een geldigVanaf- en geldigTot-waarde, met bij elke bijhouding een gesloten oud-periode en een nieuw-op-volg-periode. Vragen naar wat er toen gold zijn flexibel te beantwoorden; vragen naar wat het register toen onderschreef niet, want een correctie met terugwerkende kracht herschrijft de historie.
  • Temporele payloads: payloads per geldigheidsperiode, in dezelfde hoedanigheid als hierboven, maar dan per geldigheidsmoment bewaard.

Deze vorm is passend zolang het antwoord op "wat gold er?" mag meebewegen met wat het register op dit moment voor juist houdt.

Alle historische toestanden​

Dit archetype beantwoordt zowel "wat gold er?" als "wat onderschreef het register toen?", en is per definitie bitemporeel. De keuze uit de beslisboom over opslagvorm komt hier op neer:

  • Bitemporeel tabelmodel (zie bitemporele relationele databases): alle gegevens in cellen, geldigheid en registratie naast elkaar. Maximale query-flexibiliteit; herhaalbaarheid leunt dan voor een (groot) deel op query's en code die over de jaren hetzelfde gedrag moeten blijven vertonen.
  • Bitemporele opslag van snapshots (zie bitemporele opslag van snapshots): per toestandsprojectie-element wordt de payload zoals deze verstrekt kan worden, vastgelegd, per geldigheids- en systeemtijdstip. De query wordt eenvoudig en reduceert tot het opzoeken van één payload op basis van id en tijdstippen; de prijs is een eigen projectie per informatievraag.
  • Hybride oplossing: opslag van payloads, maar dan met extra cellen om in queries te kunnen filteren en te aggregeren.

Volledig verloop​

Het volledig verloop bevat ook de elementen die het register ooit onderschreef maar inmiddels niet meer. Dit verloop kan op twee verschillende manieren verstrekt worden:

  • Als continue stroom van notificaties: elk gevolg (of een subset daarvan) leidt tot één of meerdere notificaties die direct aan afnemers geleverd worden.
  • Als opvraagbare reeks per entiteit of entiteiten.

Omdat al het gebeuren gecommuniceerd wordt inclusief correcties, resulteert deze wijze van leveren in een bitemporeel beeld van het register.

Deze vorm lijkt het sterkst op het gevolgenjournaal zelf – en is het toch niet. Het journaal beschrijft vanuit bijhoudingsperspectief wat er binnen de bounded context gebeurde; een projectie vertaalt die naar een afgeleid gebeuren waar de afnemer naar kijkt, zoals het intermezzo 'van gevolg naar projectie' bij het projectieperspectief hierboven illustreert.

Onderschreven verloop​

Het onderschreven verloop lijkt qua vorm sterk op het volledige verloop, en dus ook op het gevolgenjournaal, maar is dit niet. De vorm is hetzelfde, maar de inhoud niet. Niet langer onderschreven gevolgen worden gefilterd, correcties voegen nieuwe gebeurens toe. Het onderschreven verloop is dus expliciet een gebeuren-projectie.

Het verloop van gebeurenprojectie-elementen die het register nu onderschrijft kan je niet streaming leveren, enkel op verzoek (per entiteit of entiteiten). Indien er behoefte is aan herhaalbaarheid voor deze vraag dienen deze onderschreven gebeurenprojecties bitemporeel vastgelegd te worden bij elk gevolg dat met terugwerkende kracht werkt.

Beslisboom: perspectief, temporaliteit en opslagvorm​

Nu we verschillende implementatievormen en hun kenmerken beschreven hebben is het zaak om te weten welke variant we onder welke omstandigheden zouden kiezen. Om hierbij te helpen hebben we een 'beslisboom' geformuleerd, die leidt tot een projectiekeuze.

Vanuit uit betrouwbare bron kunnen wij niet alle domeinspecifieke context meewegen. Beschouw deze boom daarom als een hulpmiddel om tot een keuze te komen en niet meer dan dat.

Beslisboom: welke projectievorm kies ik?​

Boom 2: bitemporele toestandsprojectie​

Boom 3: opvragen van gebeurenprojectie​