Skip to main content

Readmodels, tellers, badges, caching en materialisatie

17.1 Doel en afbakening

Dit hoofdstuk beschrijft hoe OefenHub afgeleide weergavegegevens technisch opbouwt, bewaart, ververst en gebruikt. Het gaat om readmodels, tellers, badges, dashboardwaarden, frontpagesamenvattingen, mailboxoverzichten, actie-indicatoren, online-overzichten en andere gegevens die niet de primaire brondata vormen.

De functionele betekenis van tellers en samenvattingen blijft vastgelegd in Functioneel Ontwerp, Software Requirements Specification, schermdocumentatie en database-informatie. Dit hoofdstuk beschrijft de technische realisatie: queryvormen, materialisatie, cachegrenzen, invalidatie, autorisatie, fallbackgedrag, logging en testbaarheid.

Belangrijke uitgangspunten:

  • readmodels zijn afgeleide weergave- of querymodellen en geen zelfstandige functionele bron van waarheid;
  • tellerwaarden mogen niet impliciet of dubbelzinnig zijn;
  • een storing of achterstand in readmodelverversing mag niet stilzwijgend als betrouwbare nulwaarde worden getoond;
  • autorisatie wordt altijd opnieuw server-side toegepast, ook wanneer gegevens uit een readmodel of cache komen;
  • module-eigenaarschap blijft leidend: readmodels horen primair bij de module die de gegevens bezit of functioneel publiceert;
  • er komt in de eerste technische baseline geen centraal OefenHub.ReadModels project.

17.2 Relatie met andere hoofdstukken van het Technisch Ontwerp

HoofdstukRelatie met dit hoofdstuk
Technisch Ontwerp: architectuuroverzicht en solution-opbouwLegt de modulaire monoliet, projectlijst en module-eigenaarschap vast.
Technisch Ontwerp: applicatielagen, projectstructuur en dependency-richtingLegt Contracts, query-services, Models/ReadModels en Web-compositie vast.
Technisch Ontwerp: autorisatie, policies en server-side contextcontroleBepaalt dat readmodels nooit autorisatie vervangen.
Technisch Ontwerp: domeinmodel en datamodel-overzichtPositioneert readmodels als afgeleide data naast brondata, snapshots, logs en tijdelijke output.
Technisch Ontwerp: databaseontwerp, migraties, seeddata en constraintsBeschrijft schema-eigenaarschap, indexes, soft links, snapshots en materialisatietabellen.
Technisch Ontwerp: berichten, systeemberichten, notificaties en privéberichtenBevat mailboxreadmodels, ongelezenbadges en notificatiegedrag.
Technisch Ontwerp: realtime live meekijken met SignalRGebruikt actuele voortgang en online state; SignalR is transport, geen readmodelbron.
Technisch Ontwerp: background jobs, TickerQ en periodieke verwerkingBeschrijft periodieke of retrybare verversing van materialisaties.
Technisch Ontwerp: logging, audit, securitylogging en technische foutafhandelingLegt correlation, foutafhandeling en logging van readmodelverversing vast.
Technisch Ontwerp: frontend Blazor, routing, state en componentopbouwBeschrijft Web-compositie en viewmodels die uit query-services worden opgebouwd.

17.3 Kernbegrippen

BegripBetekenis binnen OefenHubBron van waarheid?
BrondataPrimaire domeintabellen zoals ExerciseRuns, SystemMessages, Tickets, UserRelationships of Levels.Ja
SnapshotHistorische vastlegging van context zoals naam, rol, categorie of moduleversie op een functioneel moment.Ja, voor die historische context
ReadmodelAfgeleid model voor snelle of consistente weergave van gegevens.Nee
TellerAfgeleide numerieke waarde op basis van expliciete scope en filters.Nee
BadgeVisuele indicator van een teller of actiebehoefte.Nee
UI-viewmodelModel dat OefenHub.Web gebruikt voor pagina- of componentweergave.Nee
CacheTijdelijke opslag om herhaalde berekening of query’s te verminderen.Nee
MaterialisatiePersistent opgeslagen afgeleide waarde of projectie.Nee, tenzij expliciet als brondata gemodelleerd

Een readmodel mag functioneel belangrijk zijn voor gebruikerservaring en performance, maar blijft technisch herbouwbaar of verifieerbaar uit brondata. Wanneer een readmodel niet herbouwbaar is zonder informatieverlies, is het waarschijnlijk geen readmodel maar brondata of snapshotdata en hoort het in database-informatie als zodanig te zijn gemodelleerd.

17.4 Eigenaarschap van readmodels

OefenHub gebruikt geen centraal OefenHub.ReadModels project in de eerste technische baseline. Readmodels worden geplaatst bij de module die eigenaar is van de brondata of van de functionele publicatie.

ReadmodelcategorieEigenaarLocatie in project
Oefenrun- en resultaatreadmodelsOefenHub.PracticeModels/ReadModels
Mailbox-, thread- en badge-readmodelsOefenHub.CommunicationModels/ReadModels
Ticketoverzicht en actie-indicatorenOefenHub.SupportModels/ReadModels
CatalogusoverzichtenOefenHub.CatalogModels/ReadModels
Livebeschikbaarheid en online-overzichtOefenHub.LiveMonitoringModels/ReadModels
Beheerfrontpage- en adminoverzichtenOefenHub.Admin of eigenaarmoduleModels/ReadModels
ExportmodellenOefenHub.Reporting, gevoed via broncontractsModels/ReadModels of Models/ExportModels
UI-compositie over meerdere modulesOefenHub.WebPageComposition en ViewModels

17.4.1 Geen centraal readmodelproject

Een centraal readmodelproject wordt in de basis vermeden omdat dit project anders snel meerdere domeinen rechtstreeks met elkaar zou koppelen. Samengestelde pagina’s worden daarom in OefenHub.Web opgebouwd via publieke query-services van de betrokken modules.

17.4.2 Expliciet benoemde readmodel- en eventnamen

De ontwerpbronnen en usecases benoemen een aantal ouder-/voogd- en beheerreadmodels bij naam. Deze namen worden in het Technisch Ontwerp niet als aparte brondata behandeld, maar als publieke readmodel-, exportmodel- of eventcontracten van de eigenaarmodule.

NaamTechnische eigenaarTypeTechnische betekenis
AdminFrontpageReadModelOefenHub.AdminReadmodelAfgeleid beheerfrontpageoverzicht op basis van beheerbare content, recente beheerwijzigingen en relevante beheerindicatoren.
GuardianChildReadModelOefenHub.Relationships, eventueel gecomposeerd in OefenHub.WebReadmodelAfgeleid overzicht van actief gekoppelde kinderen op basis van actieve GuardianStudent-relaties.
GuardianResultSummaryReadModelOefenHub.PracticeReadmodelCompacte resultatensamenvatting per gekoppeld kind op basis van afgeronde, niet-test runs.
GuardianHistoryReadModelOefenHub.PracticeReadmodelGeschiedenisregels van afgeronde runs binnen de toegestane kinddataset.
GuardianHistoryFilterReadModelOefenHub.PracticeReadmodelFilteropties en gefilterde geschiedenisset voor periode, niveau, categorie en oefening, uitsluitend opgebouwd uit toegestane runs.
GuardianRunDetailReadModelOefenHub.PracticeReadmodelRead-only detailweergave van één geautoriseerde afgeronde run met historische context en uniforme totalen.
GuardianResultStatisticsReadModelOefenHub.PracticeReadmodelDetail- en statistiekweergave op basis van opgeslagen runvelden en vraagvoortgang, zonder clientside herberekening.
GuardianPdfExportModelOefenHub.Reporting, gevoed door OefenHub.PracticeExportmodelTijdelijk exportmodel voor PDF-download van een geautoriseerde afgeronde run; geen permanent documentrecord.
GuardianOnlineOverviewReadModelOefenHub.LiveMonitoringReadmodelOnline- en oefenstatusoverzicht van actief gekoppelde kinderen. Het openen van het overzicht maakt geen LiveViewAudit aan.
LiveAvailabilityReadModelOefenHub.LiveMonitoringReadmodelAfgeleide beschikbaarheid van de actie Kijk live mee; de knopstatus is geen autorisatiebron.
GuardianAccessDeniedOefenHub.Authorization en technische logging volgens hoofdstuk 19Security-/access-denied-eventBeperkte registratie van geweigerde ouder-/voogdtoegang zonder resultaatinhoud, antwoorden, tokens of gevoelige payloads.

Voor alle genoemde readmodels geldt dat de actuele server-side autorisatie opnieuw wordt toegepast bij iedere query. Een route-id, filterwaarde of eerder getoonde schermstatus mag geen toegang verruimen.

Voorbeeld:

var catalogSummary = await catalogQueries.GetTeacherCatalogSummaryAsync(context, cancellationToken);
var practiceSummary = await practiceQueries.GetTeacherPracticeSummaryAsync(context, cancellationToken);
var supportIndicator = await supportQueries.GetActionIndicatorAsync(context, cancellationToken);

var viewModel = TeacherFrontpageViewModel.Compose(
catalogSummary,
practiceSummary,
supportIndicator);

OefenHub.Web mag deze gegevens combineren tot een UI-viewmodel, maar mag geen directe databasequery uitvoeren op module-interne tabellen.

17.4.3 Uitzondering via expliciet ontwerpbesluit

Een apart compositie- of readmodelproject mag via een expliciet besluit worden toegevoegd wanneer er een duidelijke technische noodzaak ontstaat, bijvoorbeeld:

  • persistente domeinoverstijgende projecties met eigen lifecycle;
  • complexe rapportageprojecties die niet logisch bij één module horen;
  • schaal- of performance-eisen die Web-compositie ongeschikt maken;
  • hergebruik van dezelfde samengestelde projecties door meerdere niet-Web-ingangen.

Zo’n project mag niet stilzwijgend ontstaan. Het vereist een expliciet besluit in het Technisch Ontwerp en moet een eigen eigenaarschap, dependency-richting en datatoegangsbeleid krijgen.

17.5 Query-services en readmodel-readers

Modules publiceren readmodels via publieke contracten. Andere modules of Web gebruiken niet de interne repositories, DbContexts of entities van de eigenaarmodule.

Voorbeelden van publieke leescontracten:

public interface IExerciseRunResultReader
{
Task<ExerciseRunResultReadModel> GetResultAsync(
ExerciseRunResultQuery query,
CancellationToken cancellationToken);
}

public interface IMessageBadgeReader
{
Task<MessageBadgeReadModel> GetBadgeAsync(
MessageBadgeQuery query,
CancellationToken cancellationToken);
}

public interface ITicketActionIndicatorReader
{
Task<TicketActionIndicatorReadModel> GetIndicatorAsync(
TicketActionIndicatorQuery query,
CancellationToken cancellationToken);
}

Regels:

  • query-services staan als publieke interface onder Contracts;
  • contract-DTO’s en publieke readmodels staan onder Contracts/Models of worden expliciet publiek gemaakt vanuit Models/ReadModels wanneer dat de gekozen moduleconventie is;
  • interne projecties, EF-querytypes en helpermodellen blijven internal;
  • readmodel-readers voeren zelf server-side scoping en autorisatievoorbereiding uit of vragen daarvoor expliciet OefenHub.Authorization aan;
  • een reader geeft geen records terug buiten de geautoriseerde dataset.

17.6 Tellerdefinities en scope

Iedere teller moet expliciet definiëren wat wordt geteld. Een teller zonder scopeomschrijving is technisch niet toegestaan.

Minimale tellerdefinitie:

EigenschapVerplichtToelichting
NaamJaFunctionele en technische naam van de teller.
EigenaarmoduleJaModule die de teller publiceert.
BrondataJaTabellen, snapshots of readmodelbron.
Actor-/rolcontextJaVoor welke gebruiker en rolcontext de teller geldt.
ObjectscopeJaBijvoorbeeld kind, leerling, docent, niveau, categorie of ticket.
StatusfiltersJaWelke statussen tellen mee of worden uitgesloten.
TijdvensterIndien van toepassingBijvoorbeeld week, maand, jaar of alles.
Testdata uitgeslotenIndien van toepassingBijvoorbeeld docenttestruns uitsluiten.
Soft-deleted/inactieve recordsJaExpliciet meenemen of uitsluiten.
ActualiteitseisJaLive, near-realtime, bij paginalaad, periodiek of eventual.
FallbackgedragJaWat gebeurt bij fout of stale materialisatie.

Voorbeelddefinitie:

TellerEigenaarDefinitie
Ongelezen berichtenCommunicationAantal voor de gebruiker zichtbare systeemberichten zonder ReadAtUtc plus privéthreads met thread-events of berichten na de participant-readstate, exclusief participant-verwijderde threads.
Meldingen wacht op mijSupportAantal eigen tickets met backendstatus WaitingForUser, tenzij de gebruiker geen actieve OefenHub-sessie of geen toegang meer heeft tot het account.
Afgeronde oefeningen kindPracticeAantal afgeronde, niet-test exercise runs van een actief gekoppeld kind binnen de geautoriseerde ouder-/voogdrelatie.
Populaire categorie leerlingPractice of Catalog in samenwerkingAantal afgeronde runs binnen het actieve leerlingniveau en toegankelijke categorieën, scoped door actuele server-side autorisatie.

Een teller mag in de UI niet worden hergebruikt voor een andere betekenis zonder aparte definitie.

17.7 Badgebeleid

Badges zijn visuele indicatoren bovenop tellers of actiebehoefte. De badge zelf is geen brondata.

BadgeEigenaarBronBijzonder gedrag
BerichtenbadgeCommunicationOngelezen berichten en thread-eventsTijdens actieve leerling-oefening visueel onderdrukken.
Meldingenactie-indicatorSupportTickets met actie voor gebruikerTijdens actieve leerling-oefening visueel onderdrukken.
BeheerattentieAdmin of eigenaarmoduleBeheerreadmodelsAlleen in beheercontext zichtbaar.
Online/live-beschikbaarheidLiveMonitoringOnline presence en actieve runstatusKnopstatus is geen autorisatiebewijs.
Relatie-uitnodigingRelationships via CommunicationSysteembericht of relationship-readmodelAcceptatie vereist server-side hercontrole.

17.7.1 Afleidingsvrije oefencontext

Tijdens een actieve leerling-oefenrun worden nieuwe badges, meldingsindicaties en systeemnotificatie-overlays niet zichtbaar gemaakt in de leerling-UI. De onderliggende brondata en readstates blijven wel correct opgeslagen.

Technisch betekent dit:

  • Communication en Support mogen server-side tellerwaarden blijven actualiseren;
  • OefenHub.Web onderdrukt zichtbare badgeverversing binnen de actieve oefenlayout;
  • SignalR-updates mogen binnenkomen maar worden niet als afleidende UI-indicatie getoond;
  • na verlaten, onderbreken of afronden van de oefencontext worden badges opnieuw opgehaald via de publieke readers;
  • de tijdelijke UI-onderdrukking wijzigt geen readstate, berichtstatus, ticketstatus of oefenvoortgang.

17.8 Querymatig versus gematerialiseerd

Niet elk readmodel hoeft gematerialiseerd te worden. De keuze wordt per readmodel gemaakt.

VariantGebruikVoordeelNadeel
Directe queryKleine datasets, simpele filters, lage frequentieAltijd actueel, weinig extra opslagKan duur worden bij dashboards of veel gebruikers.
Geoptimaliseerde query-serviceSamengestelde query binnen één moduleModulegrens blijft intactNog steeds runtimebelasting.
Database view/querytypeLeesoptimalisatie binnen schemaDuidelijke SQL-projectieMinder flexibel bij complexe autorisatie.
MaterialisatietabelTellers, badges, dashboards, zware overzichtenSnel en stabiel in UIVereist invalidatie, rebuild en foutafhandeling.
In-memory cacheStabiele referentie- of configuratiedataSnel, eenvoudigProceslokaal en niet bronhoudend.
Web viewmodelPagina-/componentcompositieUI-specifiek en flexibelNiet herbruikbaar als bron.

Keuzeregel:

Begin met query-services zolang performance, eenvoud en consistentie voldoen. Materialiseer pas wanneer een teller, dashboard of overzicht aantoonbaar te duur, te complex of te vaak nodig is om iedere keer live te berekenen.

Uitzondering: wanneer een functionele eis expliciet een stabiel historisch snapshot vereist, wordt dat niet als readmodel maar als snapshotbrondata gemodelleerd.

17.9 Materialisatiebeleid

Wanneer een readmodel persistent wordt opgeslagen, gelden de volgende regels:

  • de materialisatietabel staat in het schema van de eigenaarmodule;
  • de eigenaarmodule beheert migrations, indexes en rebuildlogica;
  • de materialisatie bevat metadata over actualiteit, bijvoorbeeld CalculatedAtUtc of SourceVersion wanneer nodig;
  • de materialisatie mag niet worden bijgewerkt door andere modules;
  • andere modules gebruiken publieke query-services;
  • rebuilds zijn idempotent;
  • falende rebuilds zijn herleidbaar via logging en correlation;
  • bij mismatch tussen brondata en materialisatie heeft brondata voorrang.

Voorbeelden:

MaterialisatieSchemaEigenaarMogelijke verversing
MailboxoverzichtcommunicationCommunicationBij berichtmutatie of periodieke hersteljob.
Ongelezen badgecommunicationCommunicationBij readstate-/threadmutatie.
Ticketactie-indicatorsupportSupportBij ticketstatus- of discussie-mutatie.
Guardian-resultaatsamenvattingpracticePracticeBij afronden van run, relatiecontrole bij query.
Beheerfrontpage-attentieblokadmin of eigenaarmoduleAfhankelijk van bronPeriodiek of querymatig.
Online-overzichtliveLiveMonitoringRuntime/readmodel, beperkt persistent.

17.9.1 Initiële kandidaten voor materialisatie

De ontwerpbronnen, schermdocumentatie, usecases en reeds benoemde readmodelnamen geven voldoende informatie om een initiële materialisatiebaseline te bepalen. Deze baseline sluit niet uit dat een implementatiestory later op basis van meetgegevens een optimalisatie toevoegt of terugdraait, maar voorkomt dat ieder readmodel opnieuw als open architectuurvraag wordt behandeld.

De onderstaande indeling is de V1.0-startpositie:

Readmodel of projectieEigenaarBaselinevormRedenVerversing of herstel
Mailboxoverzicht per gebruikerOefenHub.CommunicationFysiek gematerialiseerde projectieVeelgebruikte lijst en badgebasis, afhankelijk van berichten, threads en participant-readstate.Bij bericht-, thread- en readstate-mutatie; periodieke rebuild via Scheduling mogelijk.
Ongelezen berichtenbadgeOefenHub.CommunicationFysiek gematerialiseerde teller of afgeleide badgeprojectieVeel zichtbaar, contextgevoelig en vaak nodig in de paginaschil.Bij readstate-/threadmutatie; idempotente rebuild vanuit brondata.
Ticketactie-indicatorOefenHub.SupportFysiek gematerialiseerde teller of actieprojectieGebruikt voor meldingen, tickets en aandachtspunten per gebruiker/rolcontext.Bij ticketstatus-, discussie- en eigenaarmutaties; hersteljob mogelijk.
GuardianResultSummaryReadModelOefenHub.PracticeKandidaat voor fysieke materialisatie per leerling/kindscopeResultaatsamenvattingen worden herhaald gebruikt op ouder-/voogdfrontpages en kindoverzichten.Bij afronden, corrigeren of verwijderen/anonimiseren van runs; relatieautorisatie blijft query-time.
AdminFrontpageReadModelOefenHub.Admin of eigenaarmodule per blokGemengde materialisatie: zware attentie- en aggregatieblokken materialiseren, kleine configuratie live queryenBeheerfrontpage combineert meerdere beheerindicatoren; niet ieder blok heeft dezelfde actualiteitseis.Periodiek, bij beheerwijziging of via module-eigen rebuild; blokken blijven afzonderlijk herstelbaar.
Beheerattentieblokken voor content, features, modules en supportOefenHub.Admin, OefenHub.Content, OefenHub.Catalog, OefenHub.SupportKandidaat voor fysieke materialisatie per eigenaarmoduleVoorkomt dure beheerfrontpagequeries over meerdere domeinen.Bij bronmutatie of periodieke refresh; Web composeert alleen via publieke readers.
Catalogusoverzichten voor docent/leerlingOefenHub.CatalogQuery-service met eventuele cache; materialisatie alleen bij meetbare noodzaakCatalogusdata is relatief stabiel, maar autorisatie en beschikbaarheid kunnen contextgevoelig zijn.Cache-invalidatie bij categorie-, niveau-, module- of featurewijziging.
GuardianHistoryReadModelOefenHub.PracticeGeïndexeerde query-service met paging, geen initiële fysieke materialisatieGeschiedenis is filter- en periodegevoelig; brondata en snapshots blijven leidend.Query-time op Practice-brondata met server-side autorisatie.
GuardianHistoryFilterReadModelOefenHub.PracticeQuery-service of korte cache, geen initiële fysieke materialisatieFilteropties moeten aansluiten op de actuele geautoriseerde dataset.Verversen bij paginalaad of korte contextveilige cache.
GuardianRunDetailReadModelOefenHub.PracticeDirecte read-only query, geen fysieke materialisatieEén afgeronde run met opgeslagen snapshots en resultaatvelden is de bron.Query-time met server-side autorisatie.
GuardianResultStatisticsReadModelOefenHub.PracticeQuery-service; alleen afgeleide zware statistieken materialiseren wanneer meetbaar nodigStatistieken moeten reproduceerbaar zijn uit opgeslagen run- en vraagdata.Bij runafronding of query-time; eventuele materialisatie per run herbouwbaar.
GuardianPdfExportModelOefenHub.Reporting, gevoed door OefenHub.PracticeTijdelijk exportmodel, geen materialized readmodelPDF-output is tijdelijke output en geen permanente bron of dashboardprojectie.Op aanvraag genereren; cleanup volgens hoofdstuk 16 en 18.
GuardianOnlineOverviewReadModelOefenHub.LiveMonitoringNear-realtime runtime/readmodel met korte contextveilige cache; geen duurzame materialisatietabel als baselineOnline state en oefenstatus wijzigen snel en zijn SignalR-/sessiegevoelig.Bij presence-/runstatusupdate en reconnect; fallback naar actuele bronstatus.
LiveAvailabilityReadModelOefenHub.LiveMonitoringRuntime/readmodel of korte cache, geen duurzame materialisatietabel als baselineKnopbeschikbaarheid is afgeleid en mag nooit autorisatie vervangen.Query-time hercontrole bij openen of starten van live meekijken.

Voor alle kandidaten gelden de algemene regels uit dit hoofdstuk: de materialisatie is geen bron van waarheid, wordt eigenaarmodule-specifiek beheerd, is herbouwbaar of expliciet herstelbaar, en wordt nooit gebruikt om server-side autorisatie te vervangen.

De volgende onderdelen blijven implementatieverificatie per module, maar zijn geen open architectuurvraag meer:

  • concrete tabel- of viewnamen voor materialisaties;
  • indexdefinities en queryplannen;
  • exacte TTL's voor caches;
  • rebuildfrequenties en retrydefaults;
  • concrete performancegrenzen waarbij een query alsnog wordt gematerialiseerd.

17.10 Invalidatie en verversing

Invalidatie wordt zoveel mogelijk gestuurd vanuit de module die de bronmutatie uitvoert.

17.10.1 Binnen module

Binnen één module mag de bronmutatie direct de bijbehorende materialisatie bijwerken wanneer dit onderdeel is van dezelfde functionele consistentiegrens.

Voorbeeld:

Communication
- privébericht opslaan
- participant-readstate bepalen
- mailboxreadmodel bijwerken
- ongelezenbadge bijwerken

Als deze afgeleide waarden essentieel zijn voor de mailboxweergave en in hetzelfde schema blijven, kan dit binnen dezelfde moduletransactie gebeuren.

17.10.2 Over modulegrens

Wanneer een mutatie in module A een readmodel in module B beïnvloedt, wordt dit niet rechtstreeks via databasewrites opgelost. Mogelijke routes:

RouteGebruik
Publiek contractModule A vraagt module B om een eigen readmodel te verversen.
Application eventModule A publiceert een gebeurtenis binnen de applicatie; module B verwerkt deze.
Scheduling jobVerversing mag uitgesteld of retrybaar plaatsvinden.
Live query bij ophalenGeen materialisatie; module B vraagt bronstatus via contract.
Rebuild/hersteljobPeriodieke correctie van materialisaties.

Cross-module invalidatie mag geen directe update uitvoeren in tabellen van een andere module.

17.10.3 Rebuildstrategie

Materialisaties moeten herbouwbaar zijn uit brondata, tenzij expliciet anders gemotiveerd. Rebuild kan nodig zijn bij:

  • foutieve eerdere verwerking;
  • gewijzigde tellerdefinitie;
  • herstel na jobstoring;
  • deployment/migratie;
  • datacorrectie door beheer;
  • performance- of indexwijzigingen.

Rebuilds draaien via module-eigen services. OefenHub.Scheduling kan de uitvoering plannen en bewaken, maar wordt geen eigenaar van de domeinlogica.

17.11 Cachinglagen

OefenHub onderscheidt meerdere cachinglagen. Geen enkele cache mag autorisatie of brondata vervangen.

CachelaagVoorbeeldToegestaan voorNiet toegestaan voor
Request-scope cacheHergebruik van context binnen één requestRolcontext, herhaalde queryresultatenPersistente beslissingen
In-memory app-cacheModulemetadata, stabiele configuratieKorte performance-optimalisatieBrondata, autorisatie-uitkomst zonder hercontrole
BrowserstateToegankelijkheidswaarde vóór login, UI-selectiePresentatiegedragAutorisatie, persoonsgegevens, rolcontext
SignalR-clientstateLaatste ontvangen live-updateWeergave tijdens verbindingBron van oefenvoortgang
MaterialisatietabelBadge/teller/readmodelSnelle server-side queryEnige bron van waarheid

17.11.1 Cache invalidatie

Caches krijgen altijd een duidelijke invalidatiestrategie:

  • tijdgebaseerde expiratie;
  • expliciete invalidatie na mutatie;
  • verversing bij paginalaad;
  • verversing na SignalR-reconnect;
  • rebuild via Scheduling;
  • fallback naar bronquery wanneer betrouwbaarheid vereist is.

17.11.2 Caching en autorisatie

Autorisatiebeslissingen mogen niet onbeperkt worden gecachet. Wanneer een cache geautoriseerde data bevat, moet minimaal de volgende scope onderdeel zijn van de cache key of de server-side query:

  • UserId of actor-id;
  • actieve rolcontext;
  • relevante relatiecontext;
  • relevante objectscope zoals kind, leerling, niveau of ticket;
  • featurestatus wanneer relevant;
  • eventueel tenant-/omgevingcontext wanneer toekomstig toegevoegd.

17.12 Autorisatie bij readmodels

Een readmodel mag alleen gegevens bevatten of teruggeven die binnen de actuele server-side context zichtbaar mogen zijn. Dit geldt ook als het readmodel technisch al voor meerdere gebruikers of objecten is voorgeselecteerd.

Regels:

  • query-services valideren actor, rolcontext en objectscope;
  • routeparameters of clientfilters zijn nooit autoriserend;
  • readmodels voor ouder-/voogdresultaten hercontroleren de actieve GuardianStudent-relatie;
  • docentreadmodels hercontroleren docentcontext, niveauautorisatie en leerlingrelatie;
  • beheerreadmodels vereisen actieve beheercontext;
  • badges voor verborgen of ontoegankelijke objecten worden niet getoond;
  • bij ontbrekende toegang wordt geen gedeeltelijke resultaatinhoud teruggegeven.

Voorbeeld:

GuardianResultSummaryReadModel
- mag intern gebaseerd zijn op afgeronde runs van een kind;
- wordt alleen teruggegeven als de actuele gebruiker op dat moment een actieve GuardianStudent-relatie heeft;
- filterwaarden uit de browser verruimen de dataset nooit.

17.13 Privacy en persoonsgegevens

Readmodels mogen geen onnodige persoonsgegevens dupliceren. Wanneer persoonsgegevens nodig zijn voor weergave, wordt per readmodel bepaald of live uitlezen, snapshot of geanonimiseerde fallback nodig is.

GegevenVoorkeur
Actuele naamweergave in actieve relatiesVia eigenaarmodule of expliciet readmodel.
Historische naam bij run/exportSnapshot vanuit Practice.
Geanonimiseerde gebruikerVooraf gedefinieerde anonimiseringswaarde.
Technische identifiersNiet tonen aan eindgebruiker tenzij functioneel nodig.
Zoekvelden met persoonsgegevensAlleen waar functioneel nodig, server-side gescoped.

Bij accountanonimisering moeten readmodels die persoonsgegevens bevatten worden:

  • opnieuw opgebouwd;
  • geanonimiseerd via eigenaarmodule;
  • of ongeldig verklaard en bij volgende query opnieuw berekend.

Dit beleid wordt per module uitgewerkt waar persoonsgegevens in readmodels worden opgeslagen.

17.14 Fallbackgedrag

Een readmodelstoring mag niet leiden tot misleidende waarden.

SituatieFallback
Materialisatie ontbreekt maar bronquery is goedkoopBronquery uitvoeren en eventueel materialisatie herstellen.
Materialisatie ontbreekt en bronquery is duurNiet-beschikbaarmelding of beheerbare herstelstatus tonen.
Badge kan niet betrouwbaar worden berekendBadge verbergen of neutrale foutstatus tonen; geen 0 alsof er niets is.
Readmodel is stale maar nog acceptabelWaarde tonen met server-side bekende actualiteit indien relevant.
Readmodel is stale en actiegevoeligBrondata controleren of actie blokkeren tot hercontrole.
Rebuild faaltFailed-status loggen met correlation en beheerbaar herstelpunt.

Voor gebruikersgerichte schermen geldt:

  • geen technische foutdetails tonen;
  • geen gevoelige objectnamen lekken bij autorisatiefouten;
  • lege toestand en fouttoestand duidelijk scheiden;
  • bij twijfel geen inhoudelijke data tonen.

17.15 Realtime en near-realtime waarden

Niet alle waarden hoeven realtime te zijn. Per readmodel wordt een actualiteitsklasse gekozen.

KlasseBetekenisVoorbeelden
Direct consistentWaarde moet direct kloppen bij commandresultaat.Vraagvoortgang na antwoord, kritieke status in ticketflow.
Near-realtimeWaarde mag kort achterlopen maar wordt actief bijgewerkt.Berichtenbadge, live-overzicht, online state.
Bij paginalaad actueelWaarde wordt bij openen van scherm opnieuw opgehaald.Geschiedenisfilters, frontpage-samenvatting.
PeriodiekWaarde mag via job worden ververst.Beheerattentie, cleanup-indicatoren, zware aggregaties.
HerbouwbaarWaarde is vooral performanceoptimalisatie.Dashboardprojecties, rapportagevoorbereiding.

SignalR mag worden gebruikt om clients op een nieuwe waarde te wijzen, maar de client moet zo nodig de actuele waarde opnieuw via een server-side reader ophalen. SignalR-payloads zijn geen autorisatiebewijs en geen primaire brondata.

17.16 Tellers per domein

17.16.1 Practice

OefenHub.Practice publiceert readmodels voor:

  • afgeronde runs;
  • geschiedenisoverzichten;
  • resultaatdetails;
  • statistiekweergaven;
  • ouder-/voogdresultaatsamenvattingen;
  • docentresultaatinzage;
  • gedeelde-oefeningsoverzichten;
  • voortgangssamenvattingen voor live meekijken.

Brondata bestaat uit practice-tabellen zoals exercise runs, voortgang, snapshots en shared exercise records. Niet-afgeronde runs en docenttestruns worden uitgesloten waar de functionele teller dat vereist.

17.16.2 Communication

OefenHub.Communication publiceert readmodels voor:

  • mailboxoverzicht;
  • systeemberichten;
  • privéthreads;
  • participant-readstate;
  • ongelezenbadges;
  • systeemnotificaties.

Participantgebonden zichtbaarheid is leidend. Verwijdering van een privéthread door één deelnemer mag geen readmodelimpact hebben voor andere deelnemers.

17.16.3 Support

OefenHub.Support publiceert readmodels voor:

  • mijn meldingen;
  • beheerdersoverzicht;
  • actie-indicatoren;
  • tickets wachtend op gebruiker;
  • recente ticketactiviteit;
  • detailweergaven met externe of interne zichtbaarheid.

Gebruikersreadmodels tonen uitsluitend eigen meldingen en externe communicatie. Beheerreadmodels vereisen actieve beheercontext.

17.16.4 Catalog

OefenHub.Catalog publiceert readmodels voor:

  • niveaulijsten;
  • categorieën per niveau;
  • oefeningen per categorie;
  • modulebeschikbaarheid;
  • oefeningstatussen;
  • docent-oefenaanbod;
  • beheerimpact bij categorie- en modulemigraties.

Catalogusreadmodels tonen geen leerlingresultaten en gebruiken Practice niet als bron, behalve via expliciete querycontracten voor samenvattende telwaarden wanneer dat nodig is.

17.16.5 LiveMonitoring

OefenHub.LiveMonitoring publiceert readmodels voor:

  • online-overzichten;
  • livebeschikbaarheid;
  • actieve live-sessies;
  • auditstatus van live meekijken.

Online-status is runtime/readmodelgedrag en geen autorisatiebron. Starten van live meekijken hercontroleert altijd de actuele relatie- en oefencontext.

17.16.6 Admin

OefenHub.Admin publiceert beheerreadmodels voor:

  • beheerfrontpage;
  • contentbeheer;
  • recente beheerwijzigingen;
  • systeeminstellingen;
  • popup- en templatebeheer;
  • module- en categorie-impactoverzichten.

Waar deze waarden uit andere domeinen komen, gebruikt Admin publieke query-services en geen directe databasekoppelingen.

17.17 Indexen en queryperformance

Readmodel- en tellerquery’s moeten worden ondersteund door passende indexen in het schema van de eigenaarmodule. Indexkeuzes worden in database-informatie en hoofdstuk 07 verder geconcretiseerd wanneer tabeldefinities worden aangepast.

Algemene indexrichtlijnen:

  • indexeer actor-/ownerkolommen die vaak in server-side scoping worden gebruikt;
  • indexeer statuskolommen die onderdeel zijn van tellerdefinities;
  • indexeer CreatedAtUtc, CompletedAtUtc, UpdatedAtUtc of vergelijkbare tijdvelden wanneer filters op periode bestaan;
  • gebruik samengestelde indexen voor veelgebruikte combinatiequeries;
  • voorkom brede indexes op grote JSON/base64-payloads;
  • gebruik opgeslagen uniforme kolommen voor geschiedenis en statistiekfilters;
  • voorkom query’s die autorisatie pas achteraf in memory toepassen.

Voorbeeld:

practice.ExerciseRuns
- StudentUserId
- LevelId
- ExerciseId
- CompletedAtUtc
- IsCompleted
- IsTestRun

Een geschiedenisquery voor afgeronde leerlingruns moet server-side kunnen filteren op student, context, completion status en periode zonder eerst alle runpayloads te laden.

17.18 Concurrency en idempotentie

Materialisatie en cacheverversing moeten idempotent zijn. Het opnieuw uitvoeren van dezelfde verversing mag niet leiden tot dubbele badges, dubbele readmodelregels of foutieve tellers.

Regels:

  • materialisaties gebruiken stabiele natuurlijke sleutels of unieke technische sleutels;
  • upserts of vervangende rebuilds zijn expliciet ontworpen;
  • eventverwerking houdt rekening met dubbele levering;
  • retrybare jobs registreren pogingnummer en correlation;
  • bij conflicten wint brondata;
  • readmodels mogen geen mutaties uitvoeren op brondata.

Voorbeeld:

Communication verwerkt MessageCreated opnieuw
- SystemMessage bestaat al
- participant-readmodel wordt opnieuw berekend
- badgewaarde wordt overschreven, niet opgehoogd bovenop oude waarde

17.19 Logging en correlation

Readmodelverversing, materialisatie en cacheherstel gebruiken correlation-id’s volgens hoofdstuk 19.

Minimale logging bij falende materialisatie:

VeldDoel
CorrelationIdHerleidbaarheid over request/job/workflow heen.
ModuleNameEigenaarmodule van readmodel.
ReadModelNameNaam van readmodel of teller.
ScopeNiet-gevoelige scopeaanduiding, bijvoorbeeld actor/type/object-id waar toegestaan.
SourceEventMutatie of job die verversing veroorzaakte.
AttemptNumberPoging bij retrybare verwerking.
ErrorCodeClassificatie van fout.
ElapsedMsPerformance-analyse.

Gevoelige inhoud, antwoorden, berichtteksten, volledige ticketbeschrijvingen en tokens worden niet in technische logs opgenomen.

17.20 Securitygrenzen

Readmodels kunnen gevoelige afgeleide informatie bevatten. Daarom gelden securityregels:

  • readmodel-endpoints of query-services vereisen server-side autorisatie;
  • cross-user badges worden nooit publiek uitleesbaar;
  • beheerreadmodels zijn alleen beschikbaar binnen actieve beheercontext;
  • route-id’s in readmodelqueries worden altijd gecontroleerd;
  • readmodels mogen geen objectbestaan lekken bij ontbrekende toegang;
  • technische identifiers worden niet onnodig in UI-viewmodels opgenomen;
  • browsercaches bevatten geen autorisatie- of persoonsgevoelige readmodeldata, tenzij expliciet veilig gemaakt.

17.21 Web-compositie

OefenHub.Web bouwt pagina’s op uit publieke module-readers. Web-compositie mag meerdere modules combineren, maar blijft UI-laag.

Voorbeeld: gecombineerde docent/ouder-frontpage:

OefenHub.Web PageComposition
- vraagt TeacherCatalogSummary op bij Catalog
- vraagt TeacherStudentSummary op bij Practice of Relationships via contract
- vraagt GuardianChildSummary op bij Relationships/Practice
- vraagt TicketActionIndicator op bij Support
- composeert één ViewModel

Regels:

  • Web bevat geen eigen brondata;
  • Web materialiseert geen domeinreadmodels;
  • Web mag UI-viewmodels tijdelijk in memory opbouwen;
  • Web mag geen DbContext gebruiken;
  • Web mag geen module-interne entities gebruiken;
  • Web voert geen autorisatiebeslissing uit op basis van alleen zichtbare acties.

17.22 Scheduling en periodieke verwerking

OefenHub.Scheduling kan readmodeltaken plannen, starten, retryen en monitoren. De eigenaarmodule blijft verantwoordelijk voor de inhoudelijke berekening.

Voorbeelden:

JobEigenaar berekeningSchedulingtaak
Mailboxreadmodel herstellenCommunicationPeriodiek of op foutstatus opnieuw verwerken.
Ticketactie-indicatoren herstellenSupportFailed/pending indicatoren opnieuw berekenen.
Tijdelijke exportbestanden opruimenReportingCleanup triggeren en bewaken.
Testruns opruimenPracticeOpruimjob plannen en status loggen.
Zware beheeraggregaties verversenAdmin of eigenaarmodulePeriodieke verversing uitvoeren.

Scheduling bewaakt jobstatus en retries, maar voert domeinlogica alleen via publieke contracten uit.

17.23 Implementatiepatronen

17.23.1 Direct query readmodel

internal sealed class ExerciseRunHistoryReader : IExerciseRunHistoryReader
{
private readonly PracticeDbContext dbContext;
private readonly IAuthorizationContext authorization;

public async Task<ExerciseRunHistoryReadModel> GetAsync(
ExerciseRunHistoryQuery query,
CancellationToken cancellationToken)
{
var scope = await authorization.RequirePracticeHistoryScopeAsync(query, cancellationToken);

return await dbContext.ExerciseRuns
.Where(run => run.StudentUserId == scope.StudentUserId)
.Where(run => run.IsCompleted && !run.IsTestRun)
.OrderByDescending(run => run.CompletedAtUtc)
.Select(run => new ExerciseRunHistoryItemReadModel(...))
.ToReadModelAsync(cancellationToken);
}
}

De reader blijft binnen Practice. De entity blijft internal. De publieke caller ziet alleen het contract en het readmodel.

17.23.2 Materialisatie na bronmutatie

Command: privébericht versturen
1. Communication valideert deelnemers en relatiecontext.
2. Communication slaat bericht en threadmutatie op.
3. Communication werkt participant-readstate en mailboxprojectie bij.
4. Communication publiceert eventueel badge-update via SignalR.
5. Web toont badge alleen wanneer de context dit toestaat.

17.23.3 Retrybare hersteljob

Job: RebuildCommunicationBadges
1. Scheduling start job met CorrelationId.
2. Communication ontvangt rebuildopdracht via contract.
3. Communication herberekent badges uit brondata.
4. Communication overschrijft materialisatiewaarden idempotent.
5. Scheduling registreert succes of foutstatus.

17.24 Teststrategie

Readmodels en tellers krijgen eigen testdekking.

TesttypeDoel
Unit testsTellerdefinities, filterlogica en fallbackregels.
Module integration testsEF-query’s, indexes, materialisatie en autorisatiescope binnen module.
Contract testsPublieke query-services leveren alleen afgesproken readmodels.
Cross-module integration testsWeb-compositie en workflowgedreven invalidatie via publieke contracten.
Architecture testsGeen directe DbContext/entity-toegang buiten eigenaarmodule.
Security testsGeen IDOR, geen datalek bij readmodels en badges.
Job testsRebuilds, retries, idempotentie en failed-statussen.
Performance testsZware teller- en dashboardquery’s binnen afgesproken grenzen.

Minimale testgevallen:

  • lege toestand is geen autorisatiefout;
  • autorisatiefout toont geen gedeeltelijke data;
  • stale readmodel wordt niet als betrouwbare nulwaarde getoond;
  • retry van dezelfde materialisatie is idempotent;
  • badge-onderdrukking tijdens actieve oefening wijzigt geen readstate;
  • ouder-/voogdreadmodel vervalt direct bij beëindigde relatie;
  • docentreadmodel toont geen resultaten buiten eigen docentcontext;
  • accountanonimisering verwijdert of vervangt persoonsgegevens in readmodels.

17.25 Implementatiechecklist

Bij het toevoegen of wijzigen van een readmodel, teller, badge of materialisatie moet minimaal worden gecontroleerd:

  • is de eigenaarmodule duidelijk;
  • staat het model onder Models/ReadModels of is het een Web-viewmodel;
  • is de brondata expliciet benoemd;
  • is de tellerdefinitie eenduidig;
  • is de autorisatiescope server-side vastgelegd;
  • is bepaald of querymatig of gematerialiseerd wordt gewerkt;
  • zijn indexen of performance-eisen beoordeeld;
  • is invalidatie of rebuild beschreven;
  • is fallbackgedrag vastgelegd;
  • is logging/correlation geregeld;
  • zijn persoonsgegevens en anonimisering beoordeeld;
  • is badgegedrag tijdens actieve oefenruns beoordeeld;
  • is idempotentie voor retries of rebuilds getest;
  • zijn relevante Software Requirements Specification/acceptatiecriteria- en schermverwijzingen bijgewerkt wanneer de functionele betekenis wijzigt.

17.26 Implementatieverificaties

PuntToelichtingBeslismoment
Concrete materialisatiedrempelsPer teller of overzicht meetbaar bepalen vanaf welke datasetgrootte, frequentie of responstijd materialisatie nodig wordt.Per implementatiestory.
CacheproviderBepalen of alleen in-memory cache volstaat of een distributed cache nodig is.Bij performance- of schaalbesluit.
TickerQ-readmodeljobsVaststellen welke readmodelhersteljobs via TickerQ worden ingericht.Bij uitwerking Scheduling.
Badge-actualiteitPer badge bepalen of near-realtime of paginalaad voldoende is.Bij Web-implementatie.
ReadmodeltabellenVoor de vastgelegde materialisatiekandidaten de concrete tabel-, view- of querytypenamen, indexes en migrations uitwerken.Bij databaseontwerp per module.
RebuildbeheerBepalen of beheer UI nodig is voor handmatige rebuilds.Bij beheer/operatie-uitwerking.
PerformancecriteriaConcrete meetwaarden per zwaar dashboard of overzicht bepalen.Bij performancehoofdstuk en implementatie.
AnonimiseringsimpactPer readmodel met persoonsgegevens vastleggen hoe anonimisering doorwerkt.Bij privacyhoofdstuk en module-uitwerking.