Architectuuroverzicht en solution-opbouw
2.1 Doel van dit hoofdstuk
Dit hoofdstuk beschrijft de technische hoofdopbouw van OefenHub op solution- en runtime-niveau. Het legt vast hoe de applicatie als middel-zware modulaire monoliet wordt georganiseerd, welke projecten in de solution worden verwacht, hoe projecten zich tot database-schema's en DbContexts verhouden en welke runtimecomponenten samen de applicatie vormen.
Belangrijke inputbronnen en detailhoofdstukken zijn:
- Technisch Ontwerp: Applicatielagen, projectstructuur en dependency-richting.
- Technisch Ontwerp: Databaseontwerp, migraties, seeddata en constraints.
- Technisch Ontwerp: Background jobs, TickerQ en periodieke verwerking.
- Technisch Ontwerp: Security, infrastructuur, secrets en omgevingen.
- Technisch Ontwerp: Frontend, Blazor, routing, state en componentopbouw.
2.2 Architectuurkeuze
OefenHub wordt gebouwd als een middel-zware modulaire monoliet.
| Aspect | Keuze | Consequentie |
|---|---|---|
| Deployment | Eén deploybare applicatie. | OefenHub wordt als één applicatie gebouwd, getest en uitgerold. |
| Runtime | Eén applicatieproces voor de webapplicatie. | Er is geen distributie over meerdere microservices in de eerste technische baseline. |
| Database | Eén primaire relationele database. | Er worden geen aparte databases of databaseservers per domeinmodule geïntroduceerd. |
| Connectionstring | Eén applicatieconnectionstring voor de primaire database. | Modulegrenzen worden niet afgedwongen via losse databasegebruikers in de eerste baseline. |
| Domeinindeling | Meerdere moduleprojecten. | Functionele domeinen worden als afzonderlijke projecten georganiseerd. |
| Data-eigenaarschap | Maximaal één DbContext en één schema per persistent moduleproject. | Vanuit de database is herleidbaar welk project eigenaar is van welke tabellen. |
| Modulecommunicatie | Via publieke contracts, services, query-services, events of scheduling-contracten. | Modules gebruiken elkaars interne entities, DbContexts of implementatieclasses niet rechtstreeks. |
| Oefenmodules | Concrete technische oefenmodules als aparte projecten. | Oefenlogica blijft vervangbaar en testbaar via het modulecontract. |
| Externe clients/API | Geen publieke functionele API-laag in de eerste baseline. | Primaire interactie loopt via de Blazor-webapp; externe clients of mobiele apps worden niet als apart integratiekanaal ontworpen. |
| Clientscope | Desktop en tablet zijn leidend. | Smartphonegebruik wordt niet als primair ondersteund gebruiksscenario geoptimaliseerd. |
Deze keuze combineert de eenvoud van één deploybare applicatie met duidelijke interne grenzen. OefenHub vermijdt daarmee de operationele complexiteit van microservices, maar voorkomt tegelijk dat alle domeinen in één ongestructureerde monoliet terechtkomen.
2.3 Wat de modulaire monoliet wel en niet betekent
| Wel | Niet |
|---|---|
| Duidelijke projectgrenzen per domein. | Eén grote projectmap met alle domeinen door elkaar. |
| Publieke contracts als module-ingang. | Willekeurige directe aanroepen naar interne classes van andere modules. |
| DbContext per persistent domeinmodule. | Eén gedeelde DbContext waarin alle tabellen van alle domeinen zitten. |
| Schema per persistent domeinmodule. | Losse databases of databaseservers per module. |
| Soft links en snapshots over modulegrenzen als uitgangspunt. | Automatisch overal harde cross-module foreign keys. |
| Tests per moduleproject en aanvullende integratietests. | Alleen end-to-endtests voor alle domeinlogica. |
| Interne scheduling met TickerQ. | RabbitMQ, Kafka of een externe message broker in de eerste baseline. |
De term monoliet gaat in dit Technisch Ontwerp dus uitsluitend over deployment en runtime. De interne structuur blijft modulair.
De scopegrens voor clients hoort bij dezelfde architectuurkeuze: OefenHub wordt niet ontworpen rond een publieke functionele API of aparte mobiele client. Interne endpoints, frameworkroutes, SignalR-hubs en infrastructuurinterfaces mogen bestaan waar de webapp of beheerinrichting die technisch nodig heeft, maar zij vormen geen publieke product-API voor externe applicaties.
2.4 Solutionstructuur
De solution wordt logisch verdeeld in src en tests.
OefenHub.sln
src/
OefenHub.Web/
OefenHub.SharedKernel/
OefenHub.Infrastructure/
OefenHub.Scheduling/
OefenHub.Identity/
OefenHub.Authorization/
OefenHub.Relationships/
OefenHub.Catalog/
OefenHub.Modules.Abstractions/
OefenHub.ExerciseModuleHost/
OefenHub.Practice/
OefenHub.Communication/
OefenHub.Support/
OefenHub.LiveMonitoring/
OefenHub.Admin/
OefenHub.Reporting/
OefenHub.Modules.<ModuleCategory>.<ModuleName>/
tests/
OefenHub.Identity.Tests/
OefenHub.Authorization.Tests/
OefenHub.Relationships.Tests/
OefenHub.Catalog.Tests/
OefenHub.ExerciseModuleHost.Tests/
OefenHub.Practice.Tests/
OefenHub.Communication.Tests/
OefenHub.Support.Tests/
OefenHub.LiveMonitoring.Tests/
OefenHub.Admin.Tests/
OefenHub.Reporting.Tests/
OefenHub.Scheduling.Tests/
OefenHub.Modules.<ModuleCategory>.<ModuleName>.Tests/
OefenHub.Web.Tests/
OefenHub.EndToEndTests/
OefenHub.ArchitectureTests/
OefenHub.IntegrationTests/
De fysieke mapstructuur mag deze indeling volgen. In Visual Studio of Rider mogen src en tests daarnaast als solution folders worden gebruikt zodat applicatieprojecten en testprojecten organisatorisch gescheiden blijven.
2.5 Projecttypen
Niet ieder project heeft dezelfde rol. De solution kent vier hoofdcategorieën.
| Projecttype | Voorbeelden | Rol |
|---|---|---|
| Host/UI | OefenHub.Web | Blazor/Razor-host, routing, layouts, componenten, page composition, pipeline en applicatiestart. |
| Domeinmodule | OefenHub.Practice, OefenHub.Relationships, OefenHub.Support | Eigen functioneel domein met contracts, services, eventueel DbContext, schema, entities en module-eigen readmodels. |
| Technisch platformproject | OefenHub.Infrastructure, OefenHub.Scheduling, OefenHub.ExerciseModuleHost | Technische ondersteuning, hostingintegratie, jobs, module-discovery of infrastructuurkoppelingen. |
| Contract-/sharedproject | OefenHub.SharedKernel, OefenHub.Modules.Abstractions | Beperkte gedeelde types of oefenmodulecontracten. Geen domeinlogica van één specifieke module. |
2.6 Projecten en verantwoordelijkheden
| Project | Verantwoordelijkheid | Persistent eigen schema? |
|---|---|---|
OefenHub.Web | Blazor/Razor-host, routing, componenten, layouts, viewmodels, page composition, middleware, pipelineconfiguratie en applicatiestart. | Nee |
OefenHub.SharedKernel | Zeer beperkte generieke bouwstenen zoals generieke resulttypes, tijd-/clock-abstractions, correlation-types en gedeelde primitives. | Nee |
OefenHub.Infrastructure | Technische infrastructuurkoppelingen, configuratiebinding, loggingintegratie, externe adapters en host-brede technische extensies. | Nee, tenzij via technisch ontwerpbesluit expliciet nodig |
OefenHub.Scheduling | TickerQ-integratie, jobregistratie, joblifecycle, retries, jobstatussen, technische joblogging en interne jobbeheerinterface. | Ja |
OefenHub.Identity | Intern account, externe identity-providerkoppeling, provisioning, accountstatus, profielbasis en account lifecycle. | Ja |
OefenHub.Authorization | Rollen, rolcontext, policies, server-side contextcontrole en autorisatieondersteuning. | Ja |
OefenHub.Relationships | Relaties, relatie-uitnodigingen, relatie-events, friend/guardian/teacher/admin-relaties en relatiegerichte toegangsvragen. | Ja |
OefenHub.Catalog | Niveaus, centrale categorieën, oefeningen, oefeningconfiguratie, contentstatussen, collaborators en onderwijsinhoud. | Ja |
OefenHub.Modules.Abstractions | Contracten voor technische oefenmodules, zoals descriptoren, configuratievalidatie, vraaggeneratie, antwoordcontrole, rendering en PDF-representatie. | Nee |
OefenHub.ExerciseModuleHost | Module-discovery, module-registratie, strategy/factory-resolving en technische modulemetadata. | Ja, indien modulemetadata persistent wordt beheerd |
OefenHub.Practice | Oefenruns, voortgang, resultaten, statistieken, gedeelde oefeningen, historische runcontext en oefenrun-readmodels. | Ja |
OefenHub.Communication | Interne systeemberichten, privéberichten, threads, notificaties, systeemberichttemplates, badges en communicatiereadmodels. | Ja |
OefenHub.Mail | Applicatiegestuurde externe e-mail, mailtemplates, templatehistory, SMTP-dispatch en mailverzendpogingen. | Ja |
OefenHub.Support | Meldingen/tickets, ticketdiscussie, ticketstatussen, closures, reopen requests, doorzetten naar docent en ticketgeschiedenis. | Ja |
OefenHub.LiveMonitoring | Online-status, live meekijken, livebeschikbaarheid, live sessies en LiveViewAudit. | Ja |
OefenHub.Admin | Beheercontent, popups, systeemnotificaties, site-instellingen, beheerteksten, handige links en managementlogs. | Ja |
OefenHub.Reporting | PDF-export, rapportageopbouw, exportmodellen en rapportagegerichte rendering. | Alleen als persistente rapportage-/exportstatus nodig is |
OefenHub.Modules.<ModuleCategory>.<ModuleName> | Concrete technische oefenmodule met moduleconfiguratie, validatie, vraaggeneratie, antwoordcontrole, rendering en PDF-representatie. | Nee, tenzij via technisch ontwerpbesluit expliciet gemotiveerd |
2.7 Projecten die bewust niet als basisproject worden opgenomen
| Niet opgenomen basisproject | Reden |
|---|---|
OefenHub.Audit | Audit en history zijn domeinspecifiek en blijven naast de primaire domeintabellen staan, zoals relatie-events, ticketgeschiedenis, live-view-audit en beheerlogs. |
OefenHub.Security | Securityconfiguratie wordt verdeeld over Web, Infrastructure, Identity en Authorization. Er is nog geen zelfstandige securitymodule met eigen lifecycle of datalaag. |
OefenHub.Workflows | Cross-module workflows worden ondergebracht bij de module die functioneel eigenaar is van de gebruikersactie. |
OefenHub.ReadModels | Module-eigen readmodels blijven bij de eigenaar-module. Samengestelde UI-viewmodels worden in Web opgebouwd via publieke query-services. |
Het ontbreken van deze projecten betekent niet dat audit, security, workflows of readmodels ontbreken. Het betekent alleen dat zij niet als afzonderlijke basisprojecten worden gemodelleerd zolang daar geen duidelijke zelfstandige eigenaar, lifecycle of datalaag voor bestaat.
2.8 Schema- en DbContext-overzicht
Een persistent domeinproject heeft maximaal één eigen DbContext en daarmee maximaal één eigen databaseschema. Schema's worden in kleine letters geschreven. Tabellen en kolommen gebruiken PascalCase.
| Project | DbContext | Schema | Voorbeelden van tabellen |
|---|---|---|---|
OefenHub.Identity | IdentityDbContext | identity | Users, UserProfiles, UserSettings, ProfileAvatars, AccountLifecycleLogs |
OefenHub.Authorization | AuthorizationDbContext | authorization | Roles, UserRoles, RoleContexts, AuthorizationGrants |
OefenHub.Relationships | RelationshipsDbContext | relationships | UserRelationships, RelationshipInvitations, RelationshipEvents |
OefenHub.Catalog | CatalogDbContext | catalog | Levels, Categories, LevelCategories, Exercises, ExerciseHistory, LevelCollaborators |
OefenHub.ExerciseModuleHost | ExerciseModuleHostDbContext | modules | ExerciseModules, ExerciseModuleMigrations, ModuleRegistryEntries |
OefenHub.Practice | PracticeDbContext | practice | ExerciseRuns, ExerciseRunProgress, SharedExercises, RunSnapshots |
OefenHub.Communication | CommunicationDbContext | communication | SystemMessages, PrivateMessageThreads, PrivateMessages, SystemMessageTemplates |
OefenHub.Support | SupportDbContext | support | Tickets, TicketAssignments, TicketClosures, TicketHistory, TicketReopenRequests |
OefenHub.LiveMonitoring | LiveMonitoringDbContext | live | LiveViewAudit, OnlinePresence, LiveSessionStates |
OefenHub.Admin | AdminDbContext | admin | ManagedContentBlocks, PopupDefinitions, SiteNotifications, ManagementLogEntries |
OefenHub.Scheduling | SchedulingDbContext | scheduling | TickerQ-tabellen, JobExecutionLogs, JobFailureRecords |
OefenHub.Reporting | ReportingDbContext, alleen indien nodig | reporting, alleen indien nodig | Alleen persistente rapportage-/exportstatus als die via technisch ontwerpbesluit nodig blijkt |
De tabelvoorbeelden zijn bedoeld als architectuuroverzicht. De exacte tabeldefinities, kolommen, enumwaarden, relaties en constraints blijven onderdeel van de database-informatie. Het Technisch Ontwerp beschrijft hoe deze technisch naar EF Core, schema's, migrations, constraints en modulegrenzen worden vertaald.
2.9 Naamgevingsregels op solution- en database-niveau
| Onderdeel | Regel | Voorbeeld |
|---|---|---|
| Solution | PascalCase | OefenHub.sln |
| Project | PascalCase met prefix OefenHub | OefenHub.Practice |
| Concrete oefenmodule | OefenHub.Modules.<ModuleCategory>.<ModuleName> | OefenHub.Modules.Arithmetic.AdditionSubtractionSimple |
| Testproject | Projectnaam + .Tests | OefenHub.Practice.Tests |
| Schema | Kleine letters, betekenisvol, geen afkortingen tenzij breed gangbaar | communication |
| Tabel | PascalCase | SystemMessages |
| Kolom | PascalCase | CreatedAtUtc |
| DbContext | Projectnaam + DbContext | PracticeDbContext |
De technische modulecategorie in een projectnaam is een technische ordening. Zij hoeft niet één-op-één gelijk te zijn aan een functionele categorie in de oefencatalogus. Een technische modulecategorie Arithmetic kan bijvoorbeeld meerdere functionele cataloguscategorieën ondersteunen of andersom. De eerste V1.0-categorie is Arithmetic; de eerste concrete module is OefenHub.Modules.Arithmetic.AdditionSubtractionSimple, uitgewerkt in het moduledossier Optellen & Aftrekken (simpel).
2.10 Runtime-overzicht
Onderstaande weergave geeft de hoofdcomponenten weer. Het diagram is ondersteunend en vervangt geen C4-diagram of gedetailleerde projectdocumentatie.
De belangrijkste runtimeafspraak is dat OefenHub.Web de applicatie host en UI-compositie uitvoert, maar geen module-interne data-eigenaarschap overneemt. Domeinmodules blijven eigenaar van hun eigen contracts, businessregels, DbContext, schema en data.
2.11 Webhost en UI-compositie
OefenHub.Web is het start- en hostproject van de applicatie. Het project bevat onder meer:
- Blazor/Razor routing;
- layouts;
- pages;
- components;
- forms;
- client- en server-side UI-state;
- page composition;
- middleware en pipelineconfiguratie;
- koppeling van appsettings/env naar configuratie-DTO's;
- registratie van dependency injection;
- security headers, HSTS, CSP en rate limiting;
- interne beheerroute naar TickerQ-dashboard wanneer deze technisch beschikbaar is.
OefenHub.Web mag samengestelde schermen opbouwen door publieke query-services van meerdere modules aan te roepen. Het project mag echter geen EF Core DbContexts of module-interne entities gebruiken.
Voorbeeld van toegestane UI-compositie:
Teacher frontpage
-> Catalog query-service voor niveaus en oefenaanbod
-> Practice query-service voor recente resultaten
-> Communication query-service voor badges
-> Support query-service voor actie-indicaties
-> Web bouwt één viewmodel voor rendering
Voorbeeld van niet-toegestane UI-compositie:
Teacher frontpage
-> Web opent CatalogDbContext rechtstreeks
-> Web joint direct op PracticeDbContext
-> Web leest communication.SystemMessages via EF entity
2.12 Modulecommunicatie op hoofdlijnen
Modules communiceren via expliciete publieke ingangen. De detailregels staan in hoofdstuk 03, maar op solutionniveau gelden al de volgende afspraken.
| Interactie | Toegestane route |
|---|---|
| Mutatie binnen eigen domein | Eigen command/application-service van de module. |
| Read-only vraag aan ander domein | Publieke query-service of reader van die module. |
| UI-readmodel | Module-eigen query-service, samengevoegd in Web page composition. |
| Cross-module workflow | Orchestratie in de functioneel eigenaar-module. |
| Uitgestelde verwerking | Job via OefenHub.Scheduling, uitvoering via domeincontract. |
| Technische oefenmodule | Via OefenHub.Modules.Abstractions en OefenHub.ExerciseModuleHost. |
Niet toegestaan als standaardcommunicatie:
- directe toegang tot een andere module-DbContext;
- directe toegang tot entities uit
Data/Entitiesvan een andere module; - directe aanroep van interne services van een andere module;
- cross-module SQL-queries vanuit willekeurige modules;
- stille afhankelijkheden via gedeelde statische helpers waarin businessregels verdwijnen.
2.13 Cross-module workflows
Een cross-module workflow wordt geplaatst bij de module die functioneel eigenaar is van de gebruikersactie.
| Workflow | Eigenaar | Mogelijke betrokken modules |
|---|---|---|
| Relatie-uitnodiging aanmaken | OefenHub.Relationships | Communication, Identity, Authorization |
| Accountprovisioning na externe login | OefenHub.Identity | Relationships, Communication, Authorization |
| Ticket doorzetten naar docent | OefenHub.Support | Communication, Relationships, Authorization |
| Oefening delen met vriend | OefenHub.Practice | Relationships, Communication, Catalog |
| Live meekijksessie starten | OefenHub.LiveMonitoring | Practice, Relationships, Authorization |
Er wordt in de basis geen apart OefenHub.Workflows project toegevoegd. Wanneer een toekomstige workflow ontstaat zonder duidelijke domeineigenaar of met zodanig veel domeinoverstijgende coördinatie dat module-eigenaarschap onduidelijk wordt, wordt dit opnieuw beoordeeld en als technisch ontwerpbesluit vastgelegd.
2.14 Transaction boundary per workflow
Het Technisch Ontwerp legt geen algemene regel vast dat alle cross-module stappen altijd los of altijd atomair worden uitgevoerd. Per workflow wordt bepaald welke stappen onderdeel zijn van de functionele transactie.
| Vraag | Gevolg |
|---|---|
| Leidt falen van deze stap tot een ongeldige functionele toestand? | De stap hoort bij de kritieke transaction boundary. |
| Is deze stap de enige bruikbare ingang voor een gebruiker? | De stap kan kritiek zijn, ook als het technisch een bericht of notificatie is. |
| Kan de stap veilig opnieuw worden uitgevoerd zonder dubbele effecten? | De stap kan retrybaar worden verwerkt. |
| Is falen beheerbaar zonder dataverlies of misleiding van de gebruiker? | De stap kan eventueel buiten de directe transactie vallen. |
| Is bij twijfel niet goed te bewijzen dat de stap niet kritiek is? | Behandel de stap als kritiek totdat anders is onderbouwd. |
Voorbeeld: een relatie-uitnodiging waarbij het systeembericht de primaire ingang voor de ontvanger is, kan het systeembericht als kritieke stap behandelen. Het bericht is dan niet zomaar een bijzaak, maar onderdeel van de functionele beschikbaarheid van de uitnodiging.
Voorbeeld: een badge-update na het aanmaken van een privébericht is doorgaans afgeleid gedrag. Als de badge via query of retry kan worden hersteld, hoeft die update niet de primaire transactie te blokkeren.
2.15 Scheduling en TickerQ op solutionniveau
OefenHub.Scheduling is het technische project voor background jobs, periodieke verwerking en retrybare uitvoering. Het project krijgt een eigen schema scheduling wanneer TickerQ-persistence en joblogging worden ingericht.
De eigenaarschapsscheiding is als volgt.
| Onderdeel | Eigenaar |
|---|---|
| Job aanmaken vanuit een domeinflow | Initiërende domeinmodule via scheduling-contract. |
| Job persistent opslaan | OefenHub.Scheduling. |
| Job plannen en triggeren | OefenHub.Scheduling. |
| Retrybeleid toepassen | OefenHub.Scheduling. |
| Jobstatus en pogingenteller bewaken | OefenHub.Scheduling. |
| Technische joblogging en correlation | OefenHub.Scheduling. |
| Domeinactie uitvoeren | Betreffende domeinmodule via publiek contract. |
| Businessregels van de actie | Betreffende domeinmodule. |
Een domeinmodule kan dus vragen om een job te plannen, maar zodra de job succesvol is aangemaakt, is OefenHub.Scheduling eigenaar van de technische lifecycle van die job. De domeinmodule blijft eigenaar van de businessactie die tijdens uitvoering wordt aangeroepen.
Voorbeeld:
Support sluit een ticket
-> Support vraagt Scheduling om een follow-up job te plannen
-> Scheduling bewaart job met JobType, payload, CorrelationId en retrybeleid
-> TickerQ triggert de job
-> Scheduling roept Support of Communication aan via publiek contract
-> Scheduling registreert poging, resultaat, fout of retry
De TickerQ-webinterface wordt alleen intern beschikbaar gemaakt. Zij mag niet publiek toegankelijk zijn en moet via hosting, netwerk, autorisatie of beheercontext worden afgeschermd.
2.16 Logging, correlation en herleidbaarheid op solutionniveau
Omdat OefenHub meerdere moduleprojecten en DbContexts gebruikt, moet technische herleidbaarheid vanaf het begin centraal worden meegenomen. Dat betekent niet dat er een apart auditproject komt, maar wel dat elke request-, workflow- en jobketen traceerbaar moet zijn.
Minimaal relevante identifiers:
| Identifier | Doel |
|---|---|
CorrelationId | Verbindt request, workflow, domeinactie, job en loggingregels. |
RequestId | Herleidt technische HTTP-requestafhandeling. |
JobId | Herleidt geplande en uitgevoerde jobs. |
JobType | Maakt zichtbaar welke joblogica is uitgevoerd. |
AttemptNumber | Maakt retrygedrag zichtbaar. |
ActorUserId | Legt vast welke gebruiker een actie initieerde, indien van toepassing. |
ActorRoleContext | Legt vast vanuit welke rolcontext een actie plaatsvond, indien relevant. |
ModuleName | Maakt zichtbaar welke module een logregel of fout produceerde. |
Bij cross-module acties moet dezelfde CorrelationId worden doorgegeven aan de betrokken modules en eventuele jobs. Daardoor moet achteraf te reconstrueren zijn welke gebruikersactie welke domeinmutaties, jobs, notificaties en fouten veroorzaakte.
2.17 Security zonder apart securityproject
Er wordt geen apart OefenHub.Security project opgenomen in de basis. Security wordt wel expliciet technisch uitgewerkt.
| Onderwerp | Primaire plek |
|---|---|
| Rate limiting | OefenHub.Web, pipelineconfiguratie. |
| HSTS | OefenHub.Web, middlewareconfiguratie. |
| CSP | OefenHub.Web, security headerconfiguratie. |
| Security headers | OefenHub.Web, eventueel via extension methods. |
| Secrets | Omgevingsvariabelen, secret store of hostingconfiguratie; binding in Web/Infrastructure. |
| Appsettings | Configuratie-DTO's en options binding in Web/Infrastructure. |
| Identity-providerintegratie | OefenHub.Identity en Web-authenticatieconfiguratie. |
| Server-side policies | OefenHub.Authorization. |
| Access-denied logging | Authorization/Web-pipeline met domeincontext waar relevant. |
| Verdachte toegangspogingen | Web/Authorization logging, uitbreidbaar via technisch ontwerpbesluit. |
Content Security Policy, afgekort CSP, is het beleid waarmee de applicatie vastlegt welke bronnen de browser mag laden of uitvoeren. Dit is relevant voor bescherming tegen onder andere ongewenste scripts en foutieve resource-injectie. HSTS zorgt ervoor dat browsers de applicatie via HTTPS blijven benaderen zodra het beleid actief is.
Als securityconfiguratie in een toekomstige wijziging zo groot wordt dat zij een eigen lifecycle, eigen configuratiemodel of eigen persistente datalaag krijgt, kan een apart securityproject opnieuw worden overwogen. Dat is geen uitgangspunt voor de eerste technische baseline.
2.18 Technische oefenmodules
Concrete oefenmodules worden als eigen projecten toegevoegd volgens de naamvorm:
OefenHub.Modules.<ModuleCategory>.<ModuleName>
Voorbeelden:
OefenHub.Modules.Arithmetic.AdditionSubtractionSimple
OefenHub.Modules.Arithmetic.Fractions
OefenHub.Modules.Language.Spelling
De modulecategorie is een technische ordening. Zij is bedoeld om technische modules vindbaar te houden en hoeft niet één-op-één gelijk te zijn aan de functionele categorieën in de oefencatalogus. Voor V1.0 is Arithmetic vastgesteld als eerste technische modulecategorie en OefenHub.Modules.Arithmetic.AdditionSubtractionSimple als eerste concrete technische oefenmodule.
Concrete oefenmodules gebruiken OefenHub.Modules.Abstractions als primaire afhankelijkheid. Gebruik van OefenHub.SharedKernel is toegestaan wanneer dit duplicatie voorkomt en het gebruikte type aantoonbaar generiek is. Concrete oefenmodules mogen niet afhankelijk worden van OefenHub.Web, OefenHub.Catalog, OefenHub.Practice of interne implementatieprojecten van het platform.
2.19 Testprojecten op solutionniveau
Testprojecten staan onder de solution folder of fysieke map tests. Tests worden primair per module georganiseerd.
| Testproject | Doel |
|---|---|
<Module>.Tests | Unit- en integratietests voor de betreffende module. |
<ConcreteExerciseModule>.Tests | Tests voor concrete technische oefenmodule, inclusief configuratie, generatie, evaluatie en rendering. |
OefenHub.Web.Tests | bUnit-componenttests voor OefenHub-wrappercomponenten, layouts, page composition, validatie- en fouttoestanden. |
OefenHub.EndToEndTests | Playwright .NET end-to-end smoke- en regressietests voor primaire gebruikersflows. |
OefenHub.ArchitectureTests | ArchUnitNET-tests voor dependencyregels, verboden projectreferences, modulegrenzen en interne/public grenzen. |
OefenHub.IntegrationTests | Bewuste cross-module integraties, transaction boundaries, schedulingflows en database-integratie. |
Niet ieder moduleproject hoeft exact dezelfde testmappen te hebben. Per project wordt gekozen welke tests nodig zijn om de lading te dekken. Waar nuttig kunnen testprojecten mappen gebruiken zoals Unit, Integration, Contracts en TestData.
2.20 Integratiepunten
De solution bevat meerdere technische integratiepunten. Per integratiepunt leggen de detailhoofdstukken van het Technisch Ontwerp vast welke configuratie per omgeving nodig is, welk foutgedrag veilig is en of het integratiepunt onderdeel is van een kritieke gebruikersflow.
| Integratiepunt | Technische rol | Kritieke aandachtspunten |
|---|---|---|
| Keycloak / identity provider | Externe authenticatie en credential lifecycle. | OefenHub valideert geen wachtwoorden zelf; interne accountcontext moet na login server-side worden opgebouwd. |
| SQL database | Primaire bron voor domeindata, voortgang, relaties, berichten, tickets, jobs en beheerdata. | Eén database, schema per persistent moduleproject, migrations per module. |
| SignalR | Realtime transport voor live meekijken, badges en updates. | SignalR is transport, niet de bron van waarheid. |
| TickerQ | Periodieke jobs, retries en uitgestelde verwerking. | Persistentie in scheduling schema, interne beheerinterface, begrensde retries. |
| QuestPDF | Genereren van resultaat-PDF's. | PDF's worden opnieuw uit historische rungegevens opgebouwd en zijn geen permanente brondata. |
| Tijdelijke PDF-opslag | Tijdelijke opslag van gegenereerde exports. | Cleanup, retentie en privacy moeten expliciet worden ingericht. |
| Browser storage/cookies | Technische opslag voor bijvoorbeeld toegankelijkheidsinstellingen vóór login. | Geen persoonsgegevens, tokens, autorisatiecontext of rolcontext opslaan. |
| Logging/monitoring | Technische diagnose, foutafhandeling, correlation en operatie. | Geen credentials, tokens of gevoelige payloads loggen. |
| E-mail of systeemcommunicatiekanaal | Alleen indien via Functioneel Ontwerp, Software Requirements Specification en technisch ontwerpbesluit vastgesteld voor notificaties of externe communicatie. | Niet veronderstellen als primaire bron zolang functioneel niet vastgesteld. |
| Interne beheerinterfaces | Bijvoorbeeld TickerQ-dashboard of technische beheerpagina's. | Alleen intern beschikbaar en afgeschermd tegen reguliere gebruikers. |
2.21 Deploymentcontext
De eerste technische baseline gaat uit van één deploybare OefenHub-applicatie met één primaire database en een lokale of omgevinggebonden identity-providerintegratie.
OefenHub.Web deployable
bevat references naar moduleprojecten
gebruikt één primaire databaseconnectionstring
registreert module-DbContexts en modulecontracts
host Blazor/Razor UI
host SignalR hubs
registreert TickerQ jobs
configureert security headers, HSTS, CSP en rate limiting
De operationele hoofdstukken leggen de hostingomgeving, CI/CD-pipeline, rollbackstrategie, monitoring, backup en restore vast.
2.22 Afbakening
Dit hoofdstuk legt de solution-opbouw en technische hoofdcomponenten vast. Het beschrijft geen volledige tabeldefinities, geen gedetailleerde EF Core-configuraties, geen volledige Blazor-componentstructuur en geen uitputtend securitybeleid.
Die detaillering hoort in de betreffende detailhoofdstukken. Wanneer uitwerking daar aantoont dat de gekozen projectindeling onvoldoende is, wordt dat als technisch ontwerpbesluit geregistreerd voordat projecten of schema's structureel worden toegevoegd of hernoemd.