Skip to main content

Intro, scope en uitgangspunten

1.1 Doel van het Technisch Ontwerp

Het Technisch Ontwerp beschrijft hoe OefenHub technisch wordt gerealiseerd op basis van de vastgestelde functionele en requirementbronnen. Het Technisch Ontwerp vertaalt functionele keuzes, datamodelkeuzes en architectuurkaders naar concrete implementatieafspraken voor de .NET/Blazor-applicatie, database, module-opbouw, achtergrondverwerking, realtime communicatie, PDF-export, logging, security, beheerbaarheid en teststrategie.

Het Technisch Ontwerp is geen tweede Functioneel Ontwerp en geen tweede Software Requirements Specification. Functioneel gedrag, rollen, autorisatiegrenzen, acceptatiecriteria en traceability blijven geborgd in Functioneel Ontwerp en Software Requirements Specification. Het Technisch Ontwerp beschrijft welke technische structuur, componenten, contracten, services, datatoegang, transacties, jobs en infrastructuur nodig zijn om die functionele afspraken betrouwbaar te realiseren.

1.2 Scope van het Technisch Ontwerp

De scope van het Technisch Ontwerp is de volledige OefenHub-applicatie. De technische uitwerking omvat minimaal:

OnderwerpTechnische uitwerking
ApplicatiearchitectuurSolution-opbouw, projectstructuur, modulegrenzen, dependency-richting en deploymentvorm.
FrontendBlazor/Razor-structuur, routing, layouts, componentopbouw, state, formulieren, viewmodels en page composition.
Identity en autorisatieKeycloak-/identity-providerintegratie, interne accountcontext, rolcontext, server-side autorisatie en policies.
DomeinmodulesTechnische modulegrenzen, publieke contracts, interne services, entities, events en query-ingangen.
DatabaseDbContext per module, schema-eigenaarschap, migrations, seeddata, constraints, soft links, snapshots en delete behavior.
OefenmodulesModulecontract, module-discovery, configuratie-DTO's, validatie, vraaggeneratie, antwoordcontrole, rendering en PDF-representatie.
Oefenruns en resultatenVoortgangsopslag, runpayloads, statistieken, historische context, PDF-brondata en resultaatuitlezing.
CommunicatieSysteemberichten, privéberichten, notificaties, badges, SignalR-updates en onderdrukking tijdens oefenruns.
Meldingen/ticketsTechnische lifecycle, beheerdersafhandeling, doorzetten naar docent, heropenen, afsluiten en logging.
Realtime meekijkenSignalR als transport, server-side voortgang als bron van waarheid, reconnectgedrag en LiveViewAudit.
PDF-exportQuestPDF, tijdelijke exportbestanden, bestandsnaamlogica, rendering, cleanup en historische reproduceerbaarheid.
Background jobsTickerQ, scheduling, retrybeleid, periodieke verwerking, jobstatussen, joblogging en beheerbaarheid.
Logging en securityCorrelation, technische foutafhandeling, access-denied logging, verdachte toegangspogingen, security headers, HSTS, CSP, secrets en omgevingconfiguratie.
Beheer en operatieMonitoring, backup/restore, deployment, rollback, interne beheerinterfaces en operationele controles.
TeststrategieModuletests, integratietests, architectuurtests, acceptatieherleidbaarheid en kwaliteitsgrenzen.
Privacy en retentieAccountanonimisering, persoonsgegevens in snapshots/logs/exports, retentie, tijdelijke bestanden en gegevensbescherming.

1.3 Bronnen en volgorde van waarheid

Het Technisch Ontwerp werkt vanuit bestaande documentatiebronnen. Wanneer bronnen elkaar raken, geldt de volgende verantwoordelijkheid per documenttype.

BronRol
Software Requirements SpecificationCanonieke bron voor requirements, prioriteiten, acceptatiecriteria en traceability.
Functioneel OntwerpCanonieke bron voor functioneel gedrag, rollen, domeinregels, lifecycle, autorisatiegrenzen en bron-van-waarheid op functioneel niveau.
Database-informatiePrimaire bron voor tabellen, kolommen, relaties, enumwaarden, constraints, snapshots, logische verwijzingen en datamodelafspraken.
ERDOndersteunende visualisatie- en navigatielaag voor relatiebegrip; niet de detailbron voor tabeldefinities.
ArchitectuurBaseline voor C4-context, hoofdcomponenten en architectuurkaders.
Oefenmodule-documentatieBron voor moduleplatform, modulecontracten en concrete module-eisen.
Usecases en schermdocumentatieBron voor scenario's, schermgedrag, labels, lokale UI-context en interactiepatronen.

Bij conflicten geldt:

  1. Functionele eisen of acceptatiecriteria worden niet in het Technisch Ontwerp aangepast, maar teruggelegd naar Functioneel Ontwerp en Software Requirements Specification.
  2. Datamodelwijzigingen worden niet alleen in het Technisch Ontwerp beschreven, maar teruggelegd naar database-informatie en waar nodig ERD.
  3. Technische keuzes die functioneel gedrag beïnvloeden, worden pas definitief wanneer ook Functioneel Ontwerp en Software Requirements Specification-impact is beoordeeld.
  4. Het Technisch Ontwerp mag technische vertaalkeuzes toevoegen, maar dupliceert geen volledige tabeldefinities of relationele brondetails uit database-informatie.

1.4 Architectuuruitgangspunt

OefenHub wordt technisch uitgewerkt als een middel-zware modulaire monoliet:

KeuzeBetekenis voor OefenHub
Eén deploybare applicatieOefenHub wordt als één applicatie gebouwd en uitgerold. Er wordt in de eerste baseline geen microservice-architectuur toegepast.
Eén databaseDe applicatie gebruikt één primaire relationele database. Er worden geen aparte databases of databaseservers per module geïntroduceerd.
Eén connectionstringDe applicatie gebruikt in de eerste baseline één applicatieconnectionstring voor de primaire database.
Meerdere moduleprojectenDomeinen worden als afzonderlijke projecten georganiseerd om eigenaarschap en afhankelijkheden zichtbaar te maken.
DbContext per moduleEen moduleproject heeft maximaal één eigen DbContext en daarmee maximaal één eigen databaseschema.
Schema per moduleDatabase-schema's volgen module-eigenaarschap en worden in kleine letters geschreven.
Contracts als publieke ingangModules spreken elkaar niet aan via interne classes, entities of DbContexts, maar via publieke contracts, services, query-services of expliciete events.
Concrete oefenmodules als projectenTechnische oefenmodules krijgen eigen projecten volgens de vorm OefenHub.Modules.<ModuleCategory>.<ModuleName>.

Deze keuze combineert de eenvoud van één applicatie en één database met strengere interne modulegrenzen. Het doel is om de applicatie beheersbaar te houden zonder de operationele complexiteit van microservices te introduceren.

De globale solution- en projectopbouw wordt uitgewerkt in Architectuuroverzicht en solution-opbouw. De dependency-richting en mapstructuur worden uitgewerkt in Applicatielagen, projectstructuur en dependency-richting.

1.4 Modulegrenzen als ontwerpprincipe

Een domeinmodule is eigenaar van haar eigen datamodel, services, interne regels, mappings, migrations en publieke module-ingangen. Andere modules mogen niet rechtstreeks afhankelijk worden van interne implementatieclasses, EF Core entities, DbContext-types of repositories van die module.

De basisregels zijn:

RegelToelichting
Interne implementatie is internalServices, handlers, helpers, entities en DbContext-implementaties zijn standaard intern, tenzij zij bewust onderdeel zijn van het publieke modulecontract.
Alleen Contracts is publiekInterfaces, contract-DTO's en contract-enums die door andere modules gebruikt mogen worden staan onder Contracts.
Geen directe cross-module data accessEen module leest of muteert geen tabellen/entities van een andere module via diens DbContext.
Query-services voor lezenCross-module leesbehoeften verlopen via publieke query-services of readers.
Command-services voor mutatiesCross-module mutaties verlopen via publieke command-/application-services van de eigenaar-module.
Events voor ontkoppelde vervolgactiesEvents of scheduled jobs worden gebruikt wanneer directe synchroon-koppeling niet nodig of niet wenselijk is.
Web bevat geen domeinlogicaOefenHub.Web stelt UI samen, maar bevat geen businesslogica en geen directe DbContext-toegang.

Voorbeeld van gewenste modulecommunicatie:

await relationshipInvitationService.InviteGuardianAsync(command, cancellationToken);

Voorbeeld van ongewenste modulecommunicatie:

// Niet toegestaan buiten de Relationships-module.
relationshipsDbContext.RelationshipInvitations.Add(invitation);

1.4 Datatoegang, schema's en verwijzingen

De database-informatie blijft de primaire bron voor het datamodel. Het Technisch Ontwerp beschrijft hoe dit datamodel technisch wordt gerealiseerd met EF Core, module-DbContexts, migrations, indexes, constraints en delete behavior.

De uitgangspunten voor verwijzingen zijn:

SituatieStandaard technische keuze
Relatie binnen hetzelfde domein/schemaHarde FK toegestaan en meestal gewenst.
Relatie binnen hetzelfde domein/schema met historische contextFK + snapshot waar historische leesbaarheid nodig is.
Relatie over module-/schemagrensSoft link als standaard.
Relatie over module-/schemagrens met historische contextSoft link + snapshot als standaard.
Cross-module harde FKAlleen als expliciete, gemotiveerde uitzondering.

Voorbeeld binnen een module:

support.Tickets → support.TicketClosures

Deze relatie kan hard met FK's worden afgedwongen, omdat beide tabellen binnen dezelfde module en hetzelfde schema vallen.

Voorbeeld over modulegrens:

practice.ExerciseRuns.UserId → identity.Users.Id

Deze verwijzing bevat functioneel een gebruikers-id, maar wordt standaard als soft link met relevante snapshots behandeld. Hierdoor blijft historische oefenruninformatie leesbaar bij accountdeactivatie, anonimisering of identity-lifecyclewijzigingen.

De detailuitwerking staat in Databaseontwerp, migraties, seeddata en constraints.

1.4 Transacties, workflows en naverwerking

Cross-module workflows worden niet generiek als altijd atomair of altijd retrybaar behandeld. Per workflow wordt expliciet bepaald welke stappen onderdeel zijn van de functionele transactie en welke stappen als naverwerking mogen plaatsvinden.

Een stap hoort in de functionele transactie wanneer falen van die stap leidt tot een ongeldige, onbereikbare, misleidende of niet-herstelbare toestand. Een systeembericht kan dus kritiek zijn wanneer het de primaire ingang is voor een gebruiker, bijvoorbeeld bij een relatie-uitnodiging. Een systeembericht kan ook niet-kritiek zijn wanneer het alleen een extra notificatie is naast een al raadpleegbare bron.

Type stapVoorbeeldBeleid
Kritieke bronmutatieUitnodiging accepteren en relatie aanmaken.Atomair uitvoeren.
Kritieke gebruikersingangSysteembericht dat de enige ingang is om een uitnodiging te verwerken.Atomair met de bronactie of expliciet als verplicht onderdeel van dezelfde workflow.
Niet-kritieke notificatieExtra melding dat een al zichtbare actie is uitgevoerd.Retrybaar of beheerbaar na commit.
Afgeleide projectieDashboardteller, badge of readmodel.Herbouwbaar of retrybaar.
Tijdelijke outputTijdelijke PDF of exportbestand.Niet leidend voor brontransactie, tenzij functioneel anders bepaald.

Retrybare verwerking mag niet onbeperkt en onzichtbaar blijven doorgaan. Iedere retrybare job of naverwerking heeft minimaal begrensde pogingen, statusregistratie, foutregistratie, idempotentie en beheerbare foutafhandeling nodig.

De detailuitwerking van TickerQ, jobs, retries, jobstatussen en scheduling staat in Background jobs, TickerQ en periodieke verwerking.

1.4 Security en logging zonder apart securityproject

Er wordt in de eerste technische baseline geen apart OefenHub.Security project opgenomen. Dit betekent niet dat security of securitylogging buiten scope valt. Securitygerelateerde inrichting wordt expliciet uitgewerkt op de plek waar zij technisch thuishoort.

OnderwerpVerwachte Technische locatie
Rate limitingWeb-pipeline en securityhoofdstuk.
HSTSWeb-pipeline en infrastructuur/securityconfiguratie.
CSPSecurity headers en browserbeveiliging.
Security headersMiddleware of extension methods in Web/Infrastructure.
SecretsEnvironment variables, secretsbeheer en configuratiebinding.
Appsettings-bindingConfiguratie-DTO's en environment-specifieke instellingen.
Verdachte toegangspogingenPipeline, autorisatielaag en technische logging.
Access-denied loggingAutorisatie- en logginghoofdstukken.
CorrelationIdLogging, jobs en cross-module herleidbaarheid.
SecurityloggingTechnische foutafhandeling en operationele loggingafspraken.

Als securityfunctionaliteit in een toekomstige wijziging voldoende groot of zelfstandig wordt, kan een apart project via een nieuw technisch ontwerpbesluit worden overwogen. Voor de eerste baseline worden securityconfiguratie en securitylogging verdeeld over Web, Infrastructure, Identity, Authorization, Scheduling en de relevante domeinmodules.

De detailuitwerking staat in Logging, audit, securitylogging en technische foutafhandeling en Security, infrastructuur, secrets en omgevingen.

1.4 Scheduling als eigen technische lifecycle

OefenHub.Scheduling is eigenaar van de technische lifecycle van jobs nadat een job succesvol is aangemaakt. Scheduling is daarmee verantwoordelijk voor plannen, persistent opslaan, triggeren, pogingentelling, retrybeleid, technische joblogging, foutstatussen en beheerbaarheid.

De domeinmodule blijft eigenaar van de businessactie die door de job wordt uitgevoerd. Een job roept domeinlogica daarom alleen aan via publieke contracten van de betreffende module.

VerantwoordelijkheidEigenaar
Job aanvragenInitiërende domeinmodule via scheduling-contract.
Job persistent opslaanScheduling/TickerQ.
Job triggerenScheduling/TickerQ.
Retryconfiguratie toepassenScheduling.
Domeinactie uitvoerenBetreffende domeinmodule via publiek contract.
BusinessregelsBetreffende domeinmodule.
Technische jobhistorieScheduling.
DomeinhistorieBetreffende domeinmodule.

TickerQ gebruikt persistente opslag zodat herstart van de applicatie geen jobresultaten of pending jobs verliest. Waar technisch ondersteund worden TickerQ-tabellen in het schema scheduling ondergebracht. De TickerQ-webinterface wordt alleen intern beschikbaar gemaakt.

1.4 Readmodels en UI-compositie

Readmodels worden primair geplaatst bij de module die eigenaar is van de data of de functionele uitlezing. Module-eigen readmodels staan onder Models/ReadModels binnen het betreffende project.

Voor samengestelde schermen, zoals frontpages of dashboards, mag OefenHub.Web viewmodels samenstellen via publieke query-services van meerdere modules. Web mag daarvoor geen module-interne entities, repositories of DbContexts gebruiken.

Voorbeeld:

OefenHub.Practice/
Models/
ReadModels/
GuardianResultReadModel.cs
ExerciseRunHistoryReadModel.cs

OefenHub.Web/
ViewModels/
PageComposition/

Er komt in de basis geen apart centraal OefenHub.ReadModels project. Alleen wanneer toekomstige persistente, domeinoverstijgende projecties met een eigen lifecycle ontstaan, wordt via een technisch ontwerpbesluit beoordeeld of een apart composition- of readmodelproject nodig is.

1.4 Technische oefenmodules

Technische oefenmodules worden als eigen projecten opgenomen met naamvorm:

OefenHub.Modules.<ModuleCategory>.<ModuleName>

De technische modulecategorie is een technische ordening en hoeft niet één-op-één overeen te komen met de functionele cataloguscategorie die leerlingen en docenten zien.

Concrete modules gebruiken OefenHub.Modules.Abstractions als primaire afhankelijkheid. Gebruik van OefenHub.SharedKernel is toegestaan wanneer een type of helper aantoonbaar generiek is en duplicatie anders strijdig zou zijn met DRY. Concrete modules mogen niet afhankelijk worden van OefenHub.Catalog, OefenHub.Practice, OefenHub.Web of module-interne platformimplementaties.

De detailuitwerking staat in Oefenmodulecontract en dynamische module-integratie.

1.4 Documentatie-impact bij structurele wijzigingen

Wanneer OefenHub via user stories wordt doorontwikkeld, moet per story expliciet worden beoordeeld of bovenliggende documentatie geraakt wordt. De minimale check is:

DocumentImpactvraag
Functioneel OntwerpWijzigt functioneel gedrag, rolgedrag, lifecycle, domeinregel of schermoverschrijdende afspraak?
Software Requirements SpecificationWijzigt een requirement, acceptatiecriterium, prioriteit of traceability?
Technisch OntwerpWijzigt technische structuur, services, contracten, jobs, transacties, security, logging of frontendopbouw?
Database-informatieWijzigt tabel, kolom, enumwaarde, relatie, constraint, snapshot of datamodelbron?
ERDWijzigt relatiebegrip of visualisatie van het datamodel?
Schermdocumentatie/usecasesWijzigt schermgedrag, route, interactie, flow, label, tab of lokale UI-context?

Een story die structureel iets wijzigt aan functionele regels, technische keuzes, datamodel, autorisatiegrenzen, readmodels, jobs, privacygedrag of modulecontracten is pas documentair afgerond wanneer de relevante bovenliggende documentatie is bijgewerkt of bewust als niet geraakt is beoordeeld.

1.4 Opsplitsingsregel voor hoofdstukken van het Technisch Ontwerp

hoofdstukken van het Technisch Ontwerp moeten volledig genoeg zijn om interpretatieverschillen te voorkomen, maar mogen niet zo groot worden dat zij onleesbaar of slecht onderhoudbaar worden. Wanneer een hoofdstuk meerdere zelfstandige technische onderwerpen bevat, wordt het hoofdstuk opgesplitst in subdocumenten of registers.

Een hoofdstuk komt in aanmerking voor opsplitsing wanneer:

  • één onderwerp eigen ontwerpregels, tabellen, voorbeelden en validatieafspraken nodig heeft;
  • een hoofdstuk structureel meerdere domeinen of technische lagen tegelijk behandelt;
  • een tabel of matrix zo groot wordt dat hergebruik of onderhoud als apart register logischer is;
  • besluiten en aandachtspunten anders in lopende tekst verborgen raken;
  • de tekst niet meer goed als één reviewbaar hoofdstuk te beoordelen is.

Opsplitsen mag geen nieuwe bronhiërarchie introduceren. Subdocumenten blijven onderdeel van het Technisch Ontwerp en verwijzen naar het hoofdhoofdstuk of register waarin zij thuishoren.

1.4 Afbakening

Het Technisch Ontwerp beschrijft technische realisatie en ontwerpkeuzes. Het Technisch Ontwerp introduceert geen nieuwe functionele requirements buiten Software Requirements Specification of Functioneel Ontwerp. Wanneer tijdens uitwerking van het Technisch Ontwerp blijkt dat een functionele regel ontbreekt, onduidelijk is of technisch niet uitvoerbaar blijkt, wordt dit als impact teruggelegd naar Functioneel Ontwerp en Software Requirements Specification en niet stilzwijgend als technische aanname opgelost.

Het Technisch Ontwerp bevat ook geen volledige duplicatie van database-informatie. Tabellen, kolommen, enumwaarden, relationele broninformatie en constraints blijven in database-informatie. Het Technisch Ontwerp beschrijft de technische vertaling naar EF Core, migrations, schema's, DbContexts, projectstructuur, transacties, services, jobs en runtimegedrag.