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:
- Oefenmodules: intro
- Oefenmodules: template en minimale verwachtingen
- Oefenmodules: moduleplatform en contract
- Oefenmodules: Optellen & Aftrekken (simpel)
- Software Requirements Specification: oefenmodule-requirements
- Technisch Ontwerp: oefencatalogus, niveaus, categorieën, oefeningen en modules
- Technisch Ontwerp: oefenruns, voortgang, resultaten, statistieken en PDF-brondata
- Technisch Ontwerp: PDF-export met QuestPDF
- Technisch Ontwerp: teststrategie, acceptatieherleidbaarheid en kwaliteitsgrenzen
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.
| Onderdeel | Verantwoordelijkheid |
|---|---|
OefenHub.Modules.Abstractions | Definieert de publieke technische contracten waaraan concrete oefenmodules moeten voldoen. |
OefenHub.ExerciseModuleHost | Registreert, 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.Catalog | Beheert functionele oefeningen, modulekeuze, moduleconfiguratiepayload en beschikbaarheid/status van oefeningen. |
OefenHub.Practice | Beheert oefenruns, voortgang, antwoorden, totalen, statistieken, resultaatbrondata en gedeelde oefeningen. |
OefenHub.Reporting | Bouwt PDF-/rapportage-output op en vraagt module-specifieke exportrepresentaties via het modulecontract op. |
OefenHub.Web | Toont 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
| Naamdeel | Betekenis | Regel |
|---|---|---|
OefenHub.Modules | Vaste 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 modulecategorie | Functionele dekking | Projectnaamdeel | Baselinebesluit |
|---|---|---|---|
| Rekenkundige modules | Rekenen, waaronder eenvoudige optel- en aftrekvormen. | Arithmetic | Onderdeel 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.
| Onderdeel | Waarde |
|---|---|
| Moduledossier | Optellen & Aftrekken (simpel) |
| Functionele modulecategorie | Rekenen |
| Technische modulecategorie | Arithmetic |
| Technische projectnaam | OefenHub.Modules.Arithmetic.AdditionSubtractionSimple |
| Verwachte technische referentie / modulekey | OptellenAftrekken_Simple_v1 |
| Initiële schema-aanduiding | schemaVersion = 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/
| Map | Doel |
|---|---|
Contracts | Alleen module-eigen publieke types wanneer andere onderdelen deze via het modulecontract nodig hebben. Meestal beperkt. |
Models/Configuration | Module-specifieke configuratie-DTO's. |
Models/Questions | Module-specifieke vraagmodellen. |
Models/Answers | Module-specifieke antwoordmodellen. |
Models/Results | Module-specifieke beoordelings- of renderresultaten. |
Models/Enums | Module-specifieke enumwaarden. |
Services | Module-interne services. Implementaties zijn standaard internal. |
Generation | Vraaggeneratie. |
Evaluation | Antwoordcontrole en beoordeling. |
Rendering | UI-/HTML-/tekstrepresentatie van module-inhoud, zonder Blazor-componentafhankelijkheid. |
Pdf | PDF/exportrepresentatie voor QuestPDF-opbouw. |
Validation | Configuratie- en antwoordvalidatie. |
Helpers | Kleine module-lokale hulpfuncties. Geen businesslogica-vuilnisbak. |
Extensions | Registratie- 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.
| Project | Concrete module mag referencen? | Toelichting |
|---|---|---|
OefenHub.Modules.Abstractions | Ja | Primaire contractlaag voor technische oefenmodules. |
OefenHub.SharedKernel | Beperkt | Alleen voor echt generieke types/helpers wanneer duplicatie anders strijdig is met DRY. |
OefenHub.ExerciseModuleHost | Nee | De host kent modules; modules kennen de host niet. |
OefenHub.Catalog | Nee | Catalogusdata wordt via het platform aangeleverd, niet rechtstreeks door de module gelezen. |
OefenHub.Practice | Nee | Runs en voortgang blijven generieke applicatiedata. |
OefenHub.Reporting | Nee | Reporting vraagt representaties aan; modules bouwen geen volledige PDF-flow. |
OefenHub.Web | Nee | Modules bevatten geen Blazor UI en geen routing. |
OefenHub.Infrastructure | Nee, tenzij expliciet gemotiveerd | Concrete 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.
| Onderdeel | Bevat |
|---|---|
Modules.Abstractions | Interfaces, generieke DTO's, resulttypes, moduledescriptoren, contractversies en beperkte moduleplatform-enums. |
ExerciseModuleHost | Module registry, module discovery, module factory/resolver, contractvalidatie, modulemetadata-koppeling en veilige aanroepafhandeling. |
| Concrete module | Implementatie 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:
| Contractonderdeel | Doel |
|---|---|
| Moduledescriptor | Stabiele technische module-identiteit, naam, categorie, versie, capabilities en compatibiliteitsinformatie. |
| Configuratieschema | Beschrijving van module-specifieke configuratievelden voor beheer/configuratie-UI. |
| Configuratievalidatie | Valideren van moduleconfiguratie voordat deze wordt opgeslagen of gebruikt. |
| Vraaggeneratie | Genereren van een set module-specifieke vragen op basis van configuratie en aantal vragen. |
| Antwoordvalidatie | Valideren of een ingevoerd antwoord technisch verwerkbaar is. |
| Antwoordbeoordeling | Bepalen of het antwoord goed/fout is en wat de juiste uitkomst is. |
| Vraagrendering | Module-specifieke representatie van vraaginhoud voor UI/resultaatweergave. |
| Antwoordrendering | Module-specifieke representatie van gegeven en correcte antwoorden. |
| PDF-rendering | Module-specifieke exportrepresentatie voor QuestPDF. |
| Payloadmigratie | Optioneel 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.
| Veld | Doel | Voorbeeld |
|---|---|---|
ModuleKey | Stabiele technische sleutel. | Arithmetic.Addition |
ModuleCategory | Technische modulecategorie. | Arithmetic |
ModuleName | Technische modulenaam. | Addition |
DisplayName | Nederlandstalige beheerweergave. | Optellen |
ContractVersion | Versie van het modulecontract dat de module implementeert. | 1 |
ModuleVersion | Versie van de concrete module-implementatie. | 1.0.0 |
SupportedConfigurationVersions | Payloadversies die gelezen of gemigreerd kunnen worden. | 1, 2 |
Capabilities | Ondersteunde 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.
| Gegeven | Bron | Toelichting |
|---|---|---|
| Technische sleutel | Moduledescriptor | Stabiele koppeling tussen code en catalogusrecord. |
| Contractversie | Moduledescriptor | Nodig om compatibiliteit te bepalen. |
| Beschikbaarheid | Modulehost/modulemetadata | Bepaalt of de module selecteerbaar is voor nieuwe oefeningen. |
| Testzichtbaarheid | Modulehost/modulemetadata | Bepaalt of de module beschikbaar is voor testscenario's. |
| Migratie-informatie | Modulehost/modulemetadata | Ondersteunt modulemigratie en impactanalyse. |
| Beheerweergave | Modulehost/modulemetadata | Ondersteunt 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.
| Aspect | Regel |
|---|---|
| Opslag | Generiek als JSON/base64-payload volgens de database- en catalogusafspraken. |
| Eigenaarschap inhoud | Concrete module. |
| Eigenaarschap opslag | Catalog. |
| Validatie bij opslaan | Via modulecontract. |
| Validatie bij gebruik | Opnieuw via modulecontract wanneer de payload wordt gebruikt. |
| Versieveld | Payload bevat een module-specifieke configuratieversie. |
| Backwards compatibility | Module 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.
| Onderdeel | Eigenaarschap |
|---|---|
| Configuratievelden en validatieregels | Concrete module via contract. |
| Formulierlayout en styling | OefenHub.Web. |
| Opslaan van configuratie | Catalog. |
| Server-side validatie | Concrete module via modulehost. |
| Gebruikersfeedback | Web 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.
| Input | Bron |
|---|---|
| Moduleconfiguratiepayload | Catalog |
| Aantal vragen | Startactie leerling/docenttest |
| Randomisatiecontext/seed indien nodig | Practice |
| Technische module-identiteit | Catalogusrecord/modulemetadata |
| Runcontext zonder persoonsgegevens waar mogelijk | Practice |
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.
| Payloadtype | Eigenaarschap inhoud | Opslagcontext |
|---|---|---|
| Configuratiepayload | Concrete module | Catalog |
| Vraagpayload | Concrete module | Practice |
| Antwoordpayload | Concrete module | Practice |
| Beoordelingsdetails | Concrete module | Practice, voor zover nodig voor reconstructie |
| Renderrepresentatie | Concrete module levert op aanvraag | Niet 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.
| Stap | Eigenaar |
|---|---|
| Controleren of antwoord technisch verwerkbaar is | Concrete module |
| Bepalen of antwoord goed of fout is | Concrete module |
| Bepalen van juiste antwoordrepresentatie | Concrete module |
| Opslaan van ingevuld antwoord | Practice |
| Bijwerken van uniforme totalen | Practice |
| Bijwerken van vraagstatus | Practice |
| SignalR/live update | LiveMonitoring via opgeslagen voortgang |
| Resultaatstatistieken na afronding | Practice |
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.
| Renderingtype | Toepassing |
|---|---|
| Vraagweergave | Oefenscherm en live meekijken. |
| Gegeven antwoord | Resultaatpopup, geschiedenis, PDF. |
| Juiste antwoord | Resultaatpopup, geschiedenis, PDF. |
| Compacte preview | Overzichten of gedeelde oefeningweergave indien nodig. |
| Toegankelijke tekstrepresentatie | Screenreaders, 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.
| Onderdeel | Eigenaar |
|---|---|
| PDF-documentstructuur | Reporting |
| Paginering, headers, footers | Reporting |
| Resultaattabel | Reporting |
| Module-specifieke vraagweergave | Concrete module via contract |
| Module-specifieke antwoordweergave | Concrete module via contract |
| Bestandsnaamlogica | Reporting / generieke exportafspraken |
| Historische runcontext | Practice |
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.
| Versietype | Betekenis |
|---|---|
| Contractversie | Versie van het generieke modulecontract. |
| Moduleversie | Versie van de concrete module-implementatie. |
| Configuratiepayloadversie | Versie van de opgeslagen oefeningconfiguratie. |
| Vraagpayloadversie | Versie van de gegenereerde vraaginhoud. |
| Antwoordpayloadversie | Versie 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.
| Aspect | Regel |
|---|---|
| Initiatief | Alleen via expliciete beheer- of supportflow. |
| Bron | Bestaande oefening/moduleconfiguratie. |
| Doel | Andere module of nieuwe moduleversie. |
| Validatie | Doelmodule moet configuratie kunnen valideren. |
| Historische runs | Worden niet herschreven. |
| Nieuwe runs | Gebruiken de gemigreerde module/configuratie na succesvolle migratie. |
| Audit/history | Migratie wordt volledig vastgelegd. |
| Rollback | Alleen 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/flag | Betekenis |
|---|---|
| Beschikbaar voor nieuwe oefeningen | Docenten/beheerders kunnen de module kiezen bij nieuwe oefeningconfiguratie. |
| Alleen testbaar | Module is beschikbaar voor gecontroleerde testscenario's, niet voor reguliere leerlingruns. |
| In onderhoud | Bestaande configuraties blijven mogelijk leesbaar; nieuwe selectie wordt geblokkeerd. |
| Uitgefaseerd | Niet meer kiesbaar voor nieuwe oefeningen, maar historische data blijft leesbaar. |
| Verwijderd uit code | Alleen 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.
| Foutsituatie | Afhandeling |
|---|---|
| Module niet gevonden | Veilige fout; start of export wordt geblokkeerd tenzij fallback beschikbaar is. |
| Configuratie ongeldig | Validatiefout naar beheer-/docentflow. |
| Vraaggeneratie faalt | Nieuwe run wordt niet aangemaakt; gebruiker krijgt veilige foutmelding. |
| Antwoordbeoordeling faalt | Antwoord wordt niet als verwerkt opgeslagen; gebruiker krijgt veilige foutmelding of retrybare actie volgens oefenflow. |
| Rendering faalt | Fallbackrepresentatie of veilige fout, afhankelijk van schermcontext. |
| PDF-representatie faalt | PDF-export faalt veilig; geen gedeeltelijke corrupte download. |
| Payloadversie niet ondersteund | Fallback 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.
| Integratievorm | Eerste baseline |
|---|---|
| Moduleproject in solution | Ja |
| DI-registratie per module | Ja |
| Moduledescriptor en registry | Ja |
| Runtime resolving via modulehost | Ja |
| Externe plugin-upload door beheerder | Nee |
| Ongeteste module laden in productie | Nee |
| Losse microservice per module | Nee |
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-flow | Modulegebruik |
|---|---|
| Nieuwe run starten | Configuratie lezen en vragen genereren. |
| Antwoord verwerken | Antwoord valideren en beoordelen. |
| Geen idee | Juiste antwoordrepresentatie ophalen. |
| Resultaat tonen | Vraag- en antwoordrepresentaties renderen. |
| Maak opnieuw | Bestaande vraagpayloads opnieuw gebruiken of volgorde wijzigen volgens module-/runregels. |
| Gedeelde oefening starten | Historische vraaginhoud gebruiken en modulecontext valideren. |
| PDF-export | PDF-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.
| Onderdeel | Regel |
|---|---|
| Actuele vraag | Afgeleid uit Practice-voortgang. |
| Vraagweergave | Module-rendering via modulehost. |
| Antwoordstatus | Uniforme status uit Practice. |
| Live updates | LiveMonitoring/SignalR op basis van opgeslagen voortgang. |
| Meekijkmutaties | Niet 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:
| Testcategorie | Doel |
|---|---|
| Configuratievalidatie | Ongeldige en geldige configuraties toetsen. |
| Vraaggeneratie | Aantallen, grenzen, randomisatie en deterministische testcases controleren. |
| Antwoordbeoordeling | Goede, foute en randgevallen beoordelen. |
| Payloadversies | Oudere payloads kunnen lezen of expliciet afwijzen. |
| Rendering | Vraag- en antwoordrepresentaties zijn veilig en correct. |
| PDF-representatie | Exportrepresentatie bevat vereiste informatie zonder layoutverantwoordelijkheid over te nemen. |
| Contracttests | Module 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
SharedKernelis beperkt en gemotiveerd. - Module referenceert geen
Web,Catalog,Practice,Reporting,Infrastructureof databaseprojecten. - Moduledescriptor bevat stabiele
ModuleKeyen 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:
| Punt | Te bepalen |
|---|---|
| Definitieve C#-interfaces | Of het modulecontract één hoofdinterface gebruikt of meerdere kleinere interfaces. |
| Configuratieschemaformaat | Exacte vorm voor generieke configuratievelden, validatieregels en UI-hints. |
| Renderresultaatmodel | Exacte veilige representatie voor tekst, wiskundige notatie, breuken, tabellen en fallbacktekst. |
| PDF-representatiemodel | Hoe QuestPDF-specifiek of QuestPDF-onafhankelijk module-output moet zijn. |
| Modulemetadata persistence | Welke modulemetadata in modules wordt opgeslagen en welke uit code wordt afgeleid. |
| Payloadmigratie | Wanneer migratie automatisch, beheerdergestuurd of niet toegestaan is. |
| Capabilities | Definitieve lijst van module-capabilities. |
| Module discovery | Expliciete registratie versus assembly scanning. |
| Fallback bij ontbrekende module | Welke 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.