Skip to main content

Oefenmodulecontract en dynamische module-integratie

9.1 Doel van dit hoofdstuk

Dit hoofdstuk beschrijft hoe technische oefenmodules binnen OefenHub worden ontworpen, geregistreerd, gevalideerd, aangeroepen, getest en geïntegreerd met catalogus, oefenruns, resultaatweergave en PDF-export.

Een oefenmodule is de technische implementatie van een bepaald oefentype. Voorbeelden zijn optellen, aftrekken, breuken, spelling of een toekomstige taalmodule. De module bepaalt hoe configuratie wordt gevalideerd, vragen worden gegenereerd, antwoorden worden beoordeeld en module-specifieke inhoud wordt gerenderd. De generieke applicatie blijft eigenaar van gebruikers, autorisatie, catalogusrecords, oefenruns, voortgang, resultaten, geschiedenis, live meekijken en exportflows.

Belangrijke inputbronnen zijn:

9.2 Architectuurpositie

Technische oefenmodules vormen een extensielaag binnen de modulaire monoliet. Zij zijn geen microservices, hebben geen eigen deployment, geen eigen database en geen zelfstandige autorisatiecontext. Zij worden als aparte projecten in dezelfde solution opgenomen en door de applicatie geladen via een generiek modulecontract.

OnderdeelVerantwoordelijkheid
OefenHub.Modules.AbstractionsDefinieert de publieke technische contracten waaraan concrete oefenmodules moeten voldoen.
OefenHub.ExerciseModuleHostRegistreert, ontdekt, resolveert en orkestreert concrete oefenmodules.
OefenHub.Modules.<ModuleCategory>.<ModuleName>Bevat de concrete module-implementatie, configuratie-DTO's, validatie, vraaggeneratie, antwoordcontrole, rendering en PDF-representatie.
OefenHub.CatalogBeheert functionele oefeningen, modulekeuze, moduleconfiguratiepayload en beschikbaarheid/status van oefeningen.
OefenHub.PracticeBeheert oefenruns, voortgang, antwoorden, totalen, statistieken, resultaatbrondata en gedeelde oefeningen.
OefenHub.ReportingBouwt PDF-/rapportage-output op en vraagt module-specifieke exportrepresentaties via het modulecontract op.
OefenHub.WebToont moduleconfiguratie, oefenvragen en resultaten via publieke contracten en viewmodels; gebruikt geen module-interne types.

De modulegrens loopt dus niet langs de functionele categorie in de oefencatalogus. Een centrale cataloguscategorie zoals Rekenen kan meerdere technische modules gebruiken, en een technische modulecategorie zoals Arithmetic is primair technische ordening binnen de solution.

9.3 Naamgeving van concrete oefenmoduleprojecten

Concrete technische oefenmodules gebruiken de naamvorm:

OefenHub.Modules.<ModuleCategory>.<ModuleName>

Voorbeelden:

OefenHub.Modules.Arithmetic.AdditionSubtractionSimple
OefenHub.Modules.Arithmetic.Fractions
OefenHub.Modules.Language.Spelling
NaamdeelBetekenisRegel
OefenHub.ModulesVaste projectprefix voor concrete technische oefenmodules.Altijd gelijk.
<ModuleCategory>Technische groepering van modules.Engelstalig, PascalCase, niet noodzakelijk gelijk aan een functionele cataloguscategorie.
<ModuleName>Specifieke technische module.Engelstalig, PascalCase, beschrijvend en stabiel.

Voor de V1.0-baseline zijn de eerste concrete technische modulecategorie en eerste concrete module inmiddels vastgesteld. De technische categorie is een solution-/projectordening; de functionele cataloguscategorie blijft bronhoudend in de oefencatalogus en mag daarvan afwijken.

9.3.1 Initiële V1.0-modulecategorieën

Technische modulecategorieFunctionele dekkingProjectnaamdeelBaselinebesluit
Rekenkundige modulesRekenen, waaronder eenvoudige optel- en aftrekvormen.ArithmeticOnderdeel van V1.0-baseline.

Toekomstige technische categorieën zoals Language, Time of StudySkills worden pas toegevoegd wanneer er een concreet moduledossier, functionele dekking en implementatiebesluit voor bestaat. Zij zijn voorbeelden, geen aangemaakte V1.0-projecten.

9.3.2 Eerste concrete oefenmodule

De eerste concrete technische oefenmodule is Optellen & Aftrekken (simpel). Deze module is voldoende uitgewerkt in het moduledossier om als eerste implementatiekandidaat en referentie voor het modulecontract te dienen.

OnderdeelWaarde
ModuledossierOptellen & Aftrekken (simpel)
Functionele modulecategorieRekenen
Technische modulecategorieArithmetic
Technische projectnaamOefenHub.Modules.Arithmetic.AdditionSubtractionSimple
Verwachte technische referentie / modulekeyOptellenAftrekken_Simple_v1
Initiële schema-aanduidingschemaVersion = 0.9, zoals vastgelegd in het moduledossier

Nieuwe concrete modules volgen dezelfde dossierstructuur en minimale verwachtingen als beschreven in Template en minimale verwachtingen. Een module mag pas als implementatiekandidaat worden opgenomen wanneer het moduledossier minimaal overzicht, requirements/acceptatiecriteria, functioneel gedrag, schermstates, payloads, technische ontwerpafspraken en testgevallen bevat.

9.3.3 Transactionele impact van concrete oefenmodules

Concrete technische oefenmodules krijgen in de V1.0-baseline geen eigen DbContext, schema of persistente brondata. Zij leveren configuratievalidatie, vraaggeneratie, antwoordcontrole en representaties binnen de aanroepende applicatieflow. Persistentie blijft bij Catalog, Practice, Reporting of andere platformmodules die de workflow bezitten.

Daarom introduceert Optellen & Aftrekken (simpel) geen nieuwe cross-module transaction boundary. Dit is afgedekt door TO-BES-051. Wanneer een toekomstige technische oefenmodule wel eigen persistente opslag, externe side effects of een eigen transactionele bron krijgt, is een nieuw Technisch Ontwerp-besluit nodig en moet de workflowmatrix uit hoofdstuk 26 expliciet worden ingevuld.

9.4 Projectstructuur van een concrete oefenmodule

Een concrete oefenmodule heeft in beginsel geen eigen databasecontext. De module levert gedrag en representaties; opslag van configuratie, vraagdata, antwoorden en voortgang loopt via Catalog en Practice.

Standaardstructuur:

OefenHub.Modules.Arithmetic.Addition/
Contracts/
Models/
Configuration/
Questions/
Answers/
Results/
Enums/
Services/
Interfaces/
Generation/
Evaluation/
Rendering/
Pdf/
Validation/
Helpers/
Extensions/
MapDoel
ContractsAlleen module-eigen publieke types wanneer andere onderdelen deze via het modulecontract nodig hebben. Meestal beperkt.
Models/ConfigurationModule-specifieke configuratie-DTO's.
Models/QuestionsModule-specifieke vraagmodellen.
Models/AnswersModule-specifieke antwoordmodellen.
Models/ResultsModule-specifieke beoordelings- of renderresultaten.
Models/EnumsModule-specifieke enumwaarden.
ServicesModule-interne services. Implementaties zijn standaard internal.
GenerationVraaggeneratie.
EvaluationAntwoordcontrole en beoordeling.
RenderingUI-/HTML-/tekstrepresentatie van module-inhoud, zonder Blazor-componentafhankelijkheid.
PdfPDF/exportrepresentatie voor QuestPDF-opbouw.
ValidationConfiguratie- en antwoordvalidatie.
HelpersKleine module-lokale hulpfuncties. Geen businesslogica-vuilnisbak.
ExtensionsRegistratie- en DI-extensies voor module-integratie.

Submappen worden alleen aangemaakt wanneer ze inhoud hebben. De structuur is bedoeld als herkenbare standaard, niet als verplicht lege maptemplate.

9.5 Toegestane projectafhankelijkheden

Concrete oefenmodules moeten zo onafhankelijk mogelijk blijven. Zij mogen de generieke modulecontracten gebruiken, maar niet afhankelijk worden van catalogus-, run-, web- of databaseprojecten.

ProjectConcrete module mag referencen?Toelichting
OefenHub.Modules.AbstractionsJaPrimaire contractlaag voor technische oefenmodules.
OefenHub.SharedKernelBeperktAlleen voor echt generieke types/helpers wanneer duplicatie anders strijdig is met DRY.
OefenHub.ExerciseModuleHostNeeDe host kent modules; modules kennen de host niet.
OefenHub.CatalogNeeCatalogusdata wordt via het platform aangeleverd, niet rechtstreeks door de module gelezen.
OefenHub.PracticeNeeRuns en voortgang blijven generieke applicatiedata.
OefenHub.ReportingNeeReporting vraagt representaties aan; modules bouwen geen volledige PDF-flow.
OefenHub.WebNeeModules bevatten geen Blazor UI en geen routing.
OefenHub.InfrastructureNee, tenzij expliciet gemotiveerdConcrete modules mogen niet afhankelijk worden van hosting- of infrastructuurdetails.

Alle module-interne implementatieclasses zijn standaard internal. Publieke types worden alleen toegevoegd wanneer zij onderdeel zijn van het formele modulecontract of noodzakelijk zijn voor DI-registratie.

9.6 Rollen van Modules.Abstractions en ExerciseModuleHost

OefenHub.Modules.Abstractions bevat de contracten. OefenHub.ExerciseModuleHost gebruikt deze contracten om modules te registreren en aan te roepen.

OnderdeelBevat
Modules.AbstractionsInterfaces, generieke DTO's, resulttypes, moduledescriptoren, contractversies en beperkte moduleplatform-enums.
ExerciseModuleHostModule registry, module discovery, module factory/resolver, contractvalidatie, modulemetadata-koppeling en veilige aanroepafhandeling.
Concrete moduleImplementatie van het contract voor één specifiek oefentype.

De host mag concrete moduleprojecten kennen via registratie, assembly scanning of expliciete DI-registratie. Concrete modules mogen de host niet kennen. Daarmee blijft de afhankelijkheidsrichting eenduidig.

9.7 Modulecontract op hoofdlijnen

Het exacte C#-contract wordt bij implementatie vastgesteld, maar het Technisch Ontwerp legt de functionele contractonderdelen vast.

Een technische oefenmodule moet minimaal kunnen leveren:

ContractonderdeelDoel
ModuledescriptorStabiele technische module-identiteit, naam, categorie, versie, capabilities en compatibiliteitsinformatie.
ConfiguratieschemaBeschrijving van module-specifieke configuratievelden voor beheer/configuratie-UI.
ConfiguratievalidatieValideren van moduleconfiguratie voordat deze wordt opgeslagen of gebruikt.
VraaggeneratieGenereren van een set module-specifieke vragen op basis van configuratie en aantal vragen.
AntwoordvalidatieValideren of een ingevoerd antwoord technisch verwerkbaar is.
AntwoordbeoordelingBepalen of het antwoord goed/fout is en wat de juiste uitkomst is.
VraagrenderingModule-specifieke representatie van vraaginhoud voor UI/resultaatweergave.
AntwoordrenderingModule-specifieke representatie van gegeven en correcte antwoorden.
PDF-renderingModule-specifieke exportrepresentatie voor QuestPDF.
PayloadmigratieOptioneel migreren of lezen van oudere payloadversies.

Voorbeeldvorm, niet als definitieve C#-API bedoeld:

public interface IExerciseModule
{
ExerciseModuleDescriptor Descriptor { get; }

ConfigurationSchema GetConfigurationSchema();
ValidationResult ValidateConfiguration(ModuleConfigurationPayload payload);

GenerateQuestionsResult GenerateQuestions(GenerateQuestionsRequest request);
ValidateAnswerResult ValidateAnswer(ValidateAnswerRequest request);
EvaluateAnswerResult EvaluateAnswer(EvaluateAnswerRequest request);

RenderQuestionResult RenderQuestion(RenderQuestionRequest request);
RenderAnswerResult RenderAnswer(RenderAnswerRequest request);
RenderPdfResult RenderForPdf(RenderPdfRequest request);
}

De implementatie mag uit meerdere kleinere services bestaan. Het platform hoeft niet per se één grote interface letterlijk zo te implementeren, zolang deze verantwoordelijkheden via het formele contract beschikbaar zijn.

9.8 Moduledescriptor

Elke concrete module levert een descriptor met stabiele technische metadata.

VeldDoelVoorbeeld
ModuleKeyStabiele technische sleutel.Arithmetic.Addition
ModuleCategoryTechnische modulecategorie.Arithmetic
ModuleNameTechnische modulenaam.Addition
DisplayNameNederlandstalige beheerweergave.Optellen
ContractVersionVersie van het modulecontract dat de module implementeert.1
ModuleVersionVersie van de concrete module-implementatie.1.0.0
SupportedConfigurationVersionsPayloadversies die gelezen of gemigreerd kunnen worden.1, 2
CapabilitiesOndersteunde functies.SupportsPdfRendering, SupportsShowAnswerAfterSubmit

ModuleKey is stabieler dan de zichtbare naam. Zichtbare namen mogen beheerbaar of vertaalbaar worden, maar de technische sleutel mag niet lichtvaardig wijzigen omdat catalogusrecords en historische runs hiernaar verwijzen.

9.9 Modulemetadata in ExerciseModuleHost

Beschikbare modules worden administratief beheerd via modulemetadata. De technische bron is de module-implementatie; de administratieve weergave bepaalt of en hoe de module beschikbaar is binnen OefenHub.

GegevenBronToelichting
Technische sleutelModuledescriptorStabiele koppeling tussen code en catalogusrecord.
ContractversieModuledescriptorNodig om compatibiliteit te bepalen.
BeschikbaarheidModulehost/modulemetadataBepaalt of de module selecteerbaar is voor nieuwe oefeningen.
TestzichtbaarheidModulehost/modulemetadataBepaalt of de module beschikbaar is voor testscenario's.
Migratie-informatieModulehost/modulemetadataOndersteunt modulemigratie en impactanalyse.
BeheerweergaveModulehost/modulemetadataOndersteunt modules beheren in de beheeromgeving.

Als modulemetadata persistent wordt opgeslagen, hoort deze bij OefenHub.ExerciseModuleHost en het schema modules. Concrete technische modules slaan zelf geen registry-records op.

9.10 Configuratiepayload

Een oefening in de catalogus verwijst naar precies één technische module en bevat een module-specifieke configuratiepayload. De applicatie bewaart deze generiek; de module kent de inhoudelijke structuur.

AspectRegel
OpslagGeneriek als JSON/base64-payload volgens de database- en catalogusafspraken.
Eigenaarschap inhoudConcrete module.
Eigenaarschap opslagCatalog.
Validatie bij opslaanVia modulecontract.
Validatie bij gebruikOpnieuw via modulecontract wanneer de payload wordt gebruikt.
VersieveldPayload bevat een module-specifieke configuratieversie.
Backwards compatibilityModule moet ondersteunde historische configuratieversies kunnen lezen of migreren.

Voorbeeld van een module-specifieke configuratie-DTO:

internal sealed class AdditionConfigurationV1
{
public int MinimumValue { get; init; }
public int MaximumValue { get; init; }
public bool AllowCarry { get; init; }
public int TermsCount { get; init; }
}

De DTechnisch Ontwerp is module-intern. Andere projecten slaan de payload op of geven deze door, maar mogen niet afhankelijk worden van deze concrete class.

9.11 Configuratie-UI

De module levert geen Blazor-component die rechtstreeks in Web wordt ingebouwd, tenzij dit via een expliciet technisch ontwerpbesluit als extensiepatroon wordt toegevoegd. De eerste technische baseline gebruikt een generiek configuratieschema waarmee OefenHub.Web een beheerformulier kan opbouwen.

OnderdeelEigenaarschap
Configuratievelden en validatieregelsConcrete module via contract.
Formulierlayout en stylingOefenHub.Web.
Opslaan van configuratieCatalog.
Server-side validatieConcrete module via modulehost.
GebruikersfeedbackWeb in combinatie met validatieresultaat en popup-/foutafspraken.

Voorbeeld van een generiek configuratieveld:

FieldKey: MinimumValue
FieldType: Integer
Label: Laagste getal
Required: true
Minimum: 0
Maximum: 1000
DefaultValue: 1

Dit voorkomt dat concrete modules afhankelijk worden van de Blazor UI, terwijl zij wel bepalen welke configuratie inhoudelijk geldig is.

9.12 Vraaggeneratie

Bij het starten van een oefenrun vraagt Practice via de modulehost aan de concrete module om vragen te genereren. De module ontvangt alleen de gegevens die voor generatie nodig zijn.

InputBron
ModuleconfiguratiepayloadCatalog
Aantal vragenStartactie leerling/docenttest
Randomisatiecontext/seed indien nodigPractice
Technische module-identiteitCatalogusrecord/modulemetadata
Runcontext zonder persoonsgegevens waar mogelijkPractice

De module genereert module-specifieke vraagpayloads. De generieke applicatie bewaart deze payloads als onderdeel van de run-/progressbrondata.

Voorbeeldstroom:

Leerling kiest Start nieuwe
→ Web roept Practice-contract aan
→ Practice controleert autorisatie en oefeningcontext
→ Practice haalt catalogusconfiguratie op via Catalog-contract
→ Practice vraagt ExerciseModuleHost om module te resolven
→ Module genereert vragen
→ Practice maakt ExerciseRun en progressrecords aan

De module maakt zelf geen ExerciseRun aan en schrijft niet in de database.

9.13 Vraag- en antwoordpayloads

Omdat modules sterk kunnen verschillen, worden vraag- en antwoorddetails module-specifiek opgeslagen. De generieke applicatie bewaart uniforme metadata en totalen relationeel, en bewaart module-specifieke inhoud als payload.

PayloadtypeEigenaarschap inhoudOpslagcontext
ConfiguratiepayloadConcrete moduleCatalog
VraagpayloadConcrete modulePractice
AntwoordpayloadConcrete modulePractice
BeoordelingsdetailsConcrete modulePractice, voor zover nodig voor reconstructie
RenderrepresentatieConcrete module levert op aanvraagNiet standaard persistent, tenzij snapshot/exportregels dit vereisen

Payloads moeten een versieveld bevatten zodat toekomstige modules oudere data kunnen lezen of migreren.

Voorbeeld:

{
"payloadVersion": 1,
"left": 7,
"right": 5,
"operator": "+",
"expected": 12
}

De exacte JSON-structuur is module-intern. Andere modules mogen niet afhankelijk worden van veldnamen zoals left, right of expected.

9.14 Antwoordcontrole en beoordeling

De technische module is eigenaar van inhoudelijke antwoordcontrole. De generieke applicatie is eigenaar van voortgang, vraagstatus, totalen, statistieken en live-meekijkinformatie.

StapEigenaar
Controleren of antwoord technisch verwerkbaar isConcrete module
Bepalen of antwoord goed of fout isConcrete module
Bepalen van juiste antwoordrepresentatieConcrete module
Opslaan van ingevuld antwoordPractice
Bijwerken van uniforme totalenPractice
Bijwerken van vraagstatusPractice
SignalR/live updateLiveMonitoring via opgeslagen voortgang
Resultaatstatistieken na afrondingPractice

Voorbeeld:

Leerling bevestigt antwoord
→ Web stuurt antwoord naar Practice-contract
→ Practice controleert run-, vraag- en autorisatiecontext
→ Practice vraagt module om beoordeling
→ Practice slaat antwoord, status en uniforme totalen op
→ LiveMonitoring kan opgeslagen voortgang publiceren

De module mag geen SignalR-updates sturen en mag geen runstatus muteren.

9.15 Rendering voor Web

Concrete modules leveren module-specifieke renderinformatie, maar geen eigen Blazor page-flow. De Web-laag blijft verantwoordelijk voor componenten, layout, routing, state en interactie.

RenderingtypeToepassing
VraagweergaveOefenscherm en live meekijken.
Gegeven antwoordResultaatpopup, geschiedenis, PDF.
Juiste antwoordResultaatpopup, geschiedenis, PDF.
Compacte previewOverzichten of gedeelde oefeningweergave indien nodig.
Toegankelijke tekstrepresentatieScreenreaders, fallback en export.

De module mag bijvoorbeeld een renderresultaat leveren met:

DisplayText: 7 + 5 = ...
AccessibleText: zeven plus vijf
MathNotation: optioneel
RenderKind: PlainText

Voor toekomstige complexere modules kan RenderKind bijvoorbeeld onderscheid maken tussen gewone tekst, breuken, machten, tabellen of andere veilige representaties. Vrije HTML of actieve content vanuit modules is niet toegestaan zonder aparte securitybeslissing.

9.16 PDF-/exportrepresentatie

PDF-export wordt generiek opgebouwd door Reporting met QuestPDF. De module levert alleen de representatie van module-specifieke inhoud.

OnderdeelEigenaar
PDF-documentstructuurReporting
Paginering, headers, footersReporting
ResultaattabelReporting
Module-specifieke vraagweergaveConcrete module via contract
Module-specifieke antwoordweergaveConcrete module via contract
BestandsnaamlogicaReporting / generieke exportafspraken
Historische runcontextPractice

Voorbeeld:

Reporting bouwt resultaat-PDF
→ Practice levert historische run en vraagpayloads
→ ExerciseModuleHost resolveert gebruikte module
→ Module levert PDF-representatie per vraag/antwoord
→ Reporting plaatst representatie in generieke PDF-layout

Wanneer een module niet meer beschikbaar of niet compatibel is, moet de exportflow een veilige fallback gebruiken op basis van opgeslagen snapshots/renderbare tekstrepresentaties, of een beheerbare fout teruggeven zonder technische payload te lekken.

9.17 Moduleversies en backwards compatibility

Historische runs moeten leesbaar blijven nadat modules worden aangepast. Daarom moet elke module expliciet omgaan met configuratie- en payloadversies.

VersietypeBetekenis
ContractversieVersie van het generieke modulecontract.
ModuleversieVersie van de concrete module-implementatie.
ConfiguratiepayloadversieVersie van de opgeslagen oefeningconfiguratie.
VraagpayloadversieVersie van de gegenereerde vraaginhoud.
AntwoordpayloadversieVersie van de opgeslagen antwoordinhoud.

Regels:

  • Nieuwe moduleversies mogen bestaande afgeronde runs niet onleesbaar maken.
  • Een module moet aangeven welke payloadversies zij ondersteunt.
  • Als een payloadversie niet meer ondersteund wordt, moet migratie of fallback vooraf expliciet worden geregeld.
  • Historische runs worden niet stilzwijgend herschreven bij module-updates.
  • Modulemigratie is een bewuste beheeractie en geen automatische bijwerking bij applicatiestart.

9.18 Modulemigratie

Modulemigratie is een onderhoudsactie waarbij bestaande oefeningen of configuraties naar een andere technische module of moduleversie worden omgezet. Dit is functioneel en technisch gevoelig.

AspectRegel
InitiatiefAlleen via expliciete beheer- of supportflow.
BronBestaande oefening/moduleconfiguratie.
DoelAndere module of nieuwe moduleversie.
ValidatieDoelmodule moet configuratie kunnen valideren.
Historische runsWorden niet herschreven.
Nieuwe runsGebruiken de gemigreerde module/configuratie na succesvolle migratie.
Audit/historyMigratie wordt volledig vastgelegd.
RollbackAlleen via expliciet ontwerp of nieuwe migratieactie, niet impliciet.

Bij migratie moet vooraf impactanalyse mogelijk zijn:

- hoeveel oefeningen gebruiken de bronmodule;
- welke docenten/niveaus/categorieën worden geraakt;
- welke oefeningen actief zijn;
- of bestaande configuraties automatisch omzetbaar zijn;
- welke configuraties handmatige correctie nodig hebben;
- of historische runs leesbaar blijven.

9.19 Modulebeschikbaarheid en status

Modulebeschikbaarheid wordt niet bepaald door het bestaan van een assembly alleen. Administratieve modulemetadata bepaalt of een module selecteerbaar, testbaar of gemigreerd kan worden.

Status/flagBetekenis
Beschikbaar voor nieuwe oefeningenDocenten/beheerders kunnen de module kiezen bij nieuwe oefeningconfiguratie.
Alleen testbaarModule is beschikbaar voor gecontroleerde testscenario's, niet voor reguliere leerlingruns.
In onderhoudBestaande configuraties blijven mogelijk leesbaar; nieuwe selectie wordt geblokkeerd.
UitgefaseerdNiet meer kiesbaar voor nieuwe oefeningen, maar historische data blijft leesbaar.
Verwijderd uit codeAlleen toegestaan wanneer historische leesbaarheid/fallback is geborgd.

Een oefening in de catalogus heeft daarnaast een eigen status, zoals in onderhoud of actief. Modulebeschikbaarheid en oefeningstatus zijn verschillende concepten.

9.20 Foutafhandeling bij moduleaanroepen

Modulefouten mogen niet leiden tot onveilige technische details in de UI. De modulehost vertaalt technische uitzonderingen naar veilige foutresultaten en logt de technische details met correlation-id.

FoutsituatieAfhandeling
Module niet gevondenVeilige fout; start of export wordt geblokkeerd tenzij fallback beschikbaar is.
Configuratie ongeldigValidatiefout naar beheer-/docentflow.
Vraaggeneratie faaltNieuwe run wordt niet aangemaakt; gebruiker krijgt veilige foutmelding.
Antwoordbeoordeling faaltAntwoord wordt niet als verwerkt opgeslagen; gebruiker krijgt veilige foutmelding of retrybare actie volgens oefenflow.
Rendering faaltFallbackrepresentatie of veilige fout, afhankelijk van schermcontext.
PDF-representatie faaltPDF-export faalt veilig; geen gedeeltelijke corrupte download.
Payloadversie niet ondersteundFallback of beheerbare fout; geen stille datamutatie.

De modulehost logt minimaal:

CorrelationId
ModuleKey
ModuleVersion
ContractVersion
Operation
PayloadVersion indien bekend
RunId indien van toepassing
ExerciseId indien van toepassing
Foutcode
Technische foutcontext zonder gevoelige payload

Payloads met antwoorden of persoonsgegevens worden niet volledig in logs opgenomen.

9.21 Securitygrenzen

Concrete oefenmodules draaien binnen dezelfde applicatie. Daardoor zijn technische modulegrenzen belangrijk, maar zij vervangen geen securitycontrole.

Regels:

  • Modules voeren geen autorisatiebeslissingen uit.
  • Modules ontvangen geen onnodige persoonsgegevens.
  • Modules lezen niet rechtstreeks uit databasecontexten.
  • Modules schrijven geen bestanden buiten expliciet aangeleverde exportmechanismen.
  • Modules leveren geen vrije HTML, JavaScript of actieve content aan de UI.
  • Modules gebruiken geen eigen secrets of externe netwerktoegang zonder expliciete beslissing in het Technisch Ontwerp.
  • Modules mogen geen logging doen van volledige antwoordpayloads, tokens of persoonsgegevens.

Autorisatie vindt plaats vóór moduleaanroep in Web, Authorization, Catalog, Practice of de betreffende workflow. De module beoordeelt alleen oefeninhoud.

9.22 Dynamische module-integratie

Dynamische integratie betekent in de eerste technische baseline niet dat eindgebruikers willekeurige externe assemblies uploaden. Modules worden als onderdeel van de solution ontwikkeld, getest en gedeployed.

IntegratievormEerste baseline
Moduleproject in solutionJa
DI-registratie per moduleJa
Moduledescriptor en registryJa
Runtime resolving via modulehostJa
Externe plugin-upload door beheerderNee
Ongeteste module laden in productieNee
Losse microservice per moduleNee

Voorbeeldregistratie:

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddArithmeticAdditionModule(this IServiceCollection services)
{
services.AddSingleton<IExerciseModule, AdditionExerciseModule>();
return services;
}
}

De host kan bij applicatiestart controleren of dubbele ModuleKey-waarden bestaan, of vereiste capabilities ontbreken en of modulemetadata overeenkomt met geregistreerde modules.

9.23 Modulehost resolving

De modulehost is verantwoordelijk voor veilige module-resolving.

Voorbeeldstroom:

Catalogusrecord bevat ModuleKey = Arithmetic.Addition
→ Practice vraagt modulehost om ModuleKey te resolven
→ Modulehost controleert registratie en beschikbaarheid
→ Modulehost retourneert contractinterface
→ Practice voert generatie/beoordeling/rendering via contract uit

Als de module niet gevonden wordt, mag de aanroep niet stilzwijgend naar een andere module vallen. Alleen expliciet vastgelegde migratie- of fallbackregels mogen alternatief gedrag activeren.

9.24 Relatie met catalogus

Catalog beheert functionele oefeningen en koppelt deze aan technische modules. Een concrete oefening bevat onder meer:

ExerciseId
LevelId
CategoryId
ModuleKey of ExerciseModuleId
ModuleConfigurationPayload
IsActive
Naam
Icoon
Historische/auditgegevens

Catalog mag de configuratiepayload alleen opslaan nadat de modulehost de bijbehorende module heeft gevonden en de moduleconfiguratie geldig heeft verklaard.

Catalog mag geen module-interne configuratieclass gebruiken. De payload wordt als generiek transport-/opslagobject behandeld.

9.25 Relatie met oefenruns

Practice gebruikt modules tijdens starten, beantwoorden, afronden, opnieuw maken en exportvoorbereiding.

Practice-flowModulegebruik
Nieuwe run startenConfiguratie lezen en vragen genereren.
Antwoord verwerkenAntwoord valideren en beoordelen.
Geen ideeJuiste antwoordrepresentatie ophalen.
Resultaat tonenVraag- en antwoordrepresentaties renderen.
Maak opnieuwBestaande vraagpayloads opnieuw gebruiken of volgorde wijzigen volgens module-/runregels.
Gedeelde oefening startenHistorische vraaginhoud gebruiken en modulecontext valideren.
PDF-exportPDF-representaties opvragen via modulehost.

De module schrijft geen voortgang weg. Practice blijft bron van waarheid voor runstatus, progress, totalen en statistieken.

9.26 Relatie met live meekijken

Live meekijken gebruikt de server-side opgeslagen voortgang als bron. De technische module kan nodig zijn om een vraag of antwoord leesbaar te renderen, maar live meekijken blijft read-only.

OnderdeelRegel
Actuele vraagAfgeleid uit Practice-voortgang.
VraagweergaveModule-rendering via modulehost.
AntwoordstatusUniforme status uit Practice.
Live updatesLiveMonitoring/SignalR op basis van opgeslagen voortgang.
MeekijkmutatiesNiet toegestaan.

Wanneer module-rendering tijdens live meekijken faalt, mag dit de leerlingrun niet beïnvloeden. De meekijker krijgt een veilige fout of fallbackweergave.

9.27 Relatie met tests

Nieuwe technische oefenmodules krijgen eigen testprojecten:

tests/OefenHub.Modules.Arithmetic.Addition.Tests/

Minimale testdekking per concrete module:

TestcategorieDoel
ConfiguratievalidatieOngeldige en geldige configuraties toetsen.
VraaggeneratieAantallen, grenzen, randomisatie en deterministische testcases controleren.
AntwoordbeoordelingGoede, foute en randgevallen beoordelen.
PayloadversiesOudere payloads kunnen lezen of expliciet afwijzen.
RenderingVraag- en antwoordrepresentaties zijn veilig en correct.
PDF-representatieExportrepresentatie bevat vereiste informatie zonder layoutverantwoordelijkheid over te nemen.
ContracttestsModule voldoet aan Modules.Abstractions.

Daarnaast test ExerciseModuleHost:

- dubbele ModuleKey-detectie;
- ontbrekende modulemetadata;
- resolving van beschikbare modules;
- veilige foutafhandeling bij modulefouten;
- capabilities en contractversiecontrole.

9.28 Voorbeeld: optelmodule

Een eenvoudige optelmodule kan conceptueel als volgt werken.

OefenHub.Modules.Arithmetic.Addition
Descriptor:
ModuleKey = Arithmetic.Addition
ModuleCategory = Arithmetic
ModuleName = Addition

Configuratie:
MinimumValue
MaximumValue
TermsCount
AllowCarry

Vraagpayload:
Left
Right
ExpectedAnswer

Antwoordpayload:
EnteredAnswer

Beoordeling:
EnteredAnswer == ExpectedAnswer

Generieke applicatiegegevens blijven buiten de module:

UserId
ExerciseRunId
LevelId
CategoryId
ExerciseId
StartedAtUtc
CompletedAtUtc
TotalCorrect
TotalIncorrect
AverageDurationSeconds

De optelmodule weet dus niet welke leerling oefent, welke ouder mag meekijken of welke docent autorisatie heeft. Zij weet alleen hoe een optelvraag werkt.

9.29 Implementatiechecklist voor nieuwe modules

Bij het toevoegen van een nieuwe technische oefenmodule moet minimaal worden gecontroleerd:

  • Projectnaam volgt OefenHub.Modules.<ModuleCategory>.<ModuleName>.
  • Module referenceert OefenHub.Modules.Abstractions.
  • Gebruik van SharedKernel is beperkt en gemotiveerd.
  • Module referenceert geen Web, Catalog, Practice, Reporting, Infrastructure of databaseprojecten.
  • Moduledescriptor bevat stabiele ModuleKey en versies.
  • Configuratie-DTO's bevatten versie-informatie.
  • Configuratievalidatie is server-side beschikbaar.
  • Vraaggeneratie werkt deterministisch testbaar.
  • Antwoordbeoordeling is volledig getest.
  • Rendering levert veilige representaties zonder actieve content.
  • PDF-representatie is beschikbaar of expliciet als niet ondersteund gemarkeerd.
  • Payloadversiebeleid is vastgelegd.
  • Module is geregistreerd bij de modulehost.
  • Modulemetadata is beschikbaar voor beheer/selectie.
  • Testproject is toegevoegd onder tests.
  • Module-eisenregister is bijgewerkt wanneer nieuwe module-eisen ontstaan.

9.30 Implementatieverificaties

De volgende punten moeten bij implementatie of verdere detailuitwerking van het Technisch Ontwerp concreet worden besloten:

PuntTe bepalen
Definitieve C#-interfacesOf het modulecontract één hoofdinterface gebruikt of meerdere kleinere interfaces.
ConfiguratieschemaformaatExacte vorm voor generieke configuratievelden, validatieregels en UI-hints.
RenderresultaatmodelExacte veilige representatie voor tekst, wiskundige notatie, breuken, tabellen en fallbacktekst.
PDF-representatiemodelHoe QuestPDF-specifiek of QuestPDF-onafhankelijk module-output moet zijn.
Modulemetadata persistenceWelke modulemetadata in modules wordt opgeslagen en welke uit code wordt afgeleid.
PayloadmigratieWanneer migratie automatisch, beheerdergestuurd of niet toegestaan is.
CapabilitiesDefinitieve lijst van module-capabilities.
Module discoveryExpliciete registratie versus assembly scanning.
Fallback bij ontbrekende moduleWelke schermen fallback mogen tonen en wanneer een harde fout verplicht is.

9.31 Afbakening

Dit hoofdstuk beschrijft de technische integratie van oefenmodules. Het introduceert geen nieuwe functionele oefeneisen. Nieuwe modulefunctionaliteit, nieuwe concrete module-eisen of wijzigingen in het oefengedrag moeten worden verwerkt in de daarvoor bedoelde functionele documentatie en het module-eisenregister.

Volledige tabeldefinities, kolommen en datamodeldetails blijven in de database-informatie. Dit hoofdstuk beschrijft de technische contracten, projectgrenzen, moduleverantwoordelijkheden en integratiepatronen.