Applicatielagen, projectstructuur en dependency-richting
3.1 Doel van dit hoofdstuk
Dit hoofdstuk legt vast hoe projecten binnen de OefenHub-solution intern worden opgebouwd, welke lagen en mappen per project worden gebruikt, welke afhankelijkheden zijn toegestaan en hoe modulegrenzen technisch worden bewaakt.
Dit hoofdstuk werkt de solution-opbouw uit naar concrete projectregels, mapstructuren, contractafspraken, dependency-richting, query-services, command-services, events, scheduling-ingangen en frontendafbakening.
Belangrijke inputbronnen en detailhoofdstukken zijn:
- Technisch Ontwerp: Architectuuroverzicht en solution-opbouw.
- Technisch Ontwerp: Frontend, Blazor, routing, state en componentopbouw.
3.2 Ontwerpprincipes
| Principe | Regel |
|---|---|
| Modulegrens boven gemak | Een module mag niet rechtstreeks interne classes, entities, DbContexts of repositories van een andere module gebruiken. |
| Publieke ingang expliciet maken | Alles wat door andere modules gebruikt mag worden, staat onder Contracts of wordt via een expliciet publiek contract aangeboden. |
| Implementatie standaard intern | Implementatieclasses, EF Core entities, DbContexts, configuraties en interne services zijn standaard internal, tenzij er een expliciete publieke reden is. |
| Web bevat geen domeinlogica | OefenHub.Web bevat routing, componenten, formulieren, viewmodels en page composition, maar geen businesslogica en geen directe datatoegang. |
| Eén project, maximaal één DbContext | Een persistent moduleproject heeft maximaal één eigen DbContext en daarmee maximaal één eigen databaseschema. |
| Query's via leespoorten | Cross-module leesvragen lopen via query-services/readers, niet via directe databasequeries op andermans tabellen. |
| Commands via services/facades | Cross-module mutaties lopen via publieke command-services/facades of workflow-ingangen. |
| Geen externe message broker in baseline | Modulecommunicatie blijft binnen dezelfde applicatie. TickerQ wordt gebruikt voor geplande of retrybare verwerking, niet RabbitMQ/Kafka. |
| DRY zonder shared-kernel-vervuiling | Gedeelde types gaan alleen naar SharedKernel wanneer zij aantoonbaar generiek zijn en niet bij één module horen. |
| Testbaarheid per module | Modules moeten zelfstandig testbaar zijn via eigen testprojecten en contracten. |
3.3 Logische lagen
Binnen OefenHub worden lagen niet als aparte Domain, Application en Persistence projecten per module aangemaakt. In plaats daarvan bevat elk moduleproject zelf een compacte interne laagindeling.
| Laag | Fysieke plek | Verantwoordelijkheid |
|---|---|---|
| UI/compositie | OefenHub.Web | Routing, Blazor/Razor pages, componenten, layouts, formulieren, viewmodels en page composition. |
| Publieke module-ingang | <Module>/Contracts | Interfaces, contract-DTO's, contract-enums en eventueel contract-events die andere modules mogen gebruiken. |
| Application/service-laag | <Module>/Services | Usecase-afhandeling, command-services, query-services, validatiecoördinatie en workflowstappen binnen de module. |
| Domeinlogica | <Module>/Services, <Module>/Models, module-interne classes | Domeinregels, statusovergangen, berekeningen en beslislogica van de module. |
| Datatoegang | <Module>/Data | Module-eigen DbContext, entities, EF Core-configuraties en migrations. |
| Technische infrastructuur | OefenHub.Infrastructure, OefenHub.Web, module Extensions | Loggingintegratie, configuratiebinding, middleware-registratie, adapters en technische hostintegratie. |
| Scheduling | OefenHub.Scheduling | TickerQ-integratie, jobregistratie, jobstatussen, retries, joblogging en interne jobbeheerinterface. |
Deze indeling voorkomt een groot aantal kleine projecten per module, maar behoudt wel duidelijke interne grenzen.
3.4 Dependency-richting op hoofdlijn
De dependency-richting loopt van buiten naar binnen en van host/compositie naar domeincontracten. Domeinmodules mogen niet afhankelijk worden van de UI-host.
De pijlen betekenen projectreferenties of toegestane contractafhankelijkheden. Ze betekenen niet dat implementatieclasses of entities van het doelproject vrij gebruikt mogen worden.
3.5 Toegestane projectafhankelijkheden
| Van | Mag referencen | Mag niet referencen |
|---|---|---|
OefenHub.Web | Publieke contracts van modules, SharedKernel, infrastructuurregistratie, schedulingregistratie. | Module-interne entities, module-interne DbContexts, directe SQL/datatoegang. |
| Domeinmodule | SharedKernel, publieke contracts van modules die functioneel nodig zijn, eventueel Infrastructure-abstractions indien bewust gekozen. | OefenHub.Web, interne Data/Entities van andere modules, concrete UI-types. |
OefenHub.Scheduling | Scheduling-contracten, publieke job-handlercontracten van domeinmodules, SharedKernel, TickerQ-integratie. | Module-interne entities of services zonder contract, UI-types. |
OefenHub.ExerciseModuleHost | Modules.Abstractions, modulemetadata-contracten, eventueel Catalog via publiek contract indien nodig. | Concrete module-interne implementatie buiten het modulecontract. |
| Concrete oefenmodule | Modules.Abstractions, beperkt SharedKernel. | Web, Catalog, Practice, ExerciseModuleHost-internals, andere concrete moduleprojecten. |
| Testproject | Het project dat getest wordt, testhelpers en testinfrastructuur. | Productieprojecten buiten het doel van de test, tenzij het een integratie- of architectuurtest is. |
Wanneer een dependency nodig lijkt die niet in deze tabel past, moet eerst worden beoordeeld of het om een ontbrekend publiek contract, een verkeerd gekozen eigenaarschap of een echte architectuuruitzondering gaat.
3.6 Standaardstructuur voor domeinmoduleprojecten
Een domeinmodule gebruikt de volgende basisstructuur. Mappen worden alleen aangemaakt wanneer zij inhoud hebben; lege mappen zijn niet verplicht.
OefenHub.<ModuleName>/
Contracts/
Models/
Enums/
Events/
Data/
<ModuleName>DbContext.cs
Entities/
Configurations/
Migrations/
Models/
Commands/
Queries/
ReadModels/
Enums/
Services/
Interfaces/
Events/
Helpers/
Extensions/
| Map | Doel | Publiek of intern |
|---|---|---|
Contracts | Publieke module-ingang voor andere modules. Bevat interfaces, contract-DTO's, contract-enums en eventueel contract-events. | Publiek waar nodig. |
Contracts/Models | Request-/responsemodellen die onderdeel zijn van publieke contracts. | Publiek. |
Contracts/Enums | Enums die onderdeel zijn van publieke contracts. | Publiek. |
Contracts/Events | Events waarop andere modules mogen reageren of die buiten de module gebruikt mogen worden. | Publiek, alleen indien nodig. |
Data | Module-eigen EF Core datatoegang. Bevat DbContext, entities, configuraties en migrations. | Intern. |
Data/Entities | EF Core entities van de module. | Intern. |
Data/Configurations | EF Core entity configurations. | Intern. |
Data/Migrations | EF Core migrations voor de module-eigen DbContext. | Intern/technisch. |
Models | Module-interne DTO's, commandmodellen, querymodellen, readmodels en enums. | Intern, tenzij bewust anders. |
Models/Commands | Modellen voor interne command-afhandeling binnen de module. | Intern. |
Models/Queries | Modellen voor interne query-afhandeling binnen de module. | Intern. |
Models/ReadModels | Module-eigen readmodels voor schermen, overzichten, tellers of exports waarvoor de module eigenaar is. | Intern of via contract gepubliceerd. |
Models/Enums | Module-interne enumwaarden. | Intern. |
Services | Implementatie van command-services, query-services, validators, calculators en modulelogica. | Intern, behalve expliciet publieke contractimplementaties via DI. |
Services/Interfaces | Interne interfaces voor module-eigen serviceverdeling. Deze map is organisatorisch en hoeft niet als aparte namespace zichtbaar te zijn. | Intern. |
Events | Module-interne events of eventmodellen. | Intern, tenzij verplaatst naar Contracts/Events. |
Helpers | Kleine lokale hulpfuncties zonder domeineigenaarschap. | Intern. |
Extensions | Extension methods, meestal voor dependency injection of lokale technische registratie. | Publiek wanneer hostregistratie dit vereist. |
Naamgeving van DTO's
DTO-klassen die als data-transferobject worden gebruikt krijgen expliciet de suffix Dto. Dit geldt voor publieke contract-DTO's, module-interne command-/querymodellen wanneer zij als DTO worden gebruikt en web-/exportmodellen die als transportmodel over een laag- of modulegrens gaan. Voorbeelden zijn PracticeResultDetailDto, PracticePdfExportDto en moduleconfiguratie-DTO's.
Readmodels, entities, optionsclasses, validators, commands en domeinspecifieke value objects krijgen niet automatisch de suffix Dto; zij volgen hun eigen naamgevingsconventie. De suffix is bedoeld om transportmodellen herkenbaar te houden en te voorkomen dat database-entiteiten of domeinobjecten als DTO worden gebruikt.
3.7 Contracts als publieke module-ingang
Contracts is de enige standaardplek waar andere modules op mogen bouwen. Een contract beschrijft wat de module aanbiedt, niet hoe de module dit intern realiseert.
Voorbeeld voor OefenHub.Relationships:
OefenHub.Relationships/
Contracts/
IRelationshipAccessReader.cs
IRelationshipInvitationService.cs
Models/
RelationshipAccessCheckRequest.cs
RelationshipAccessCheckResult.cs
Enums/
RelationshipAccessDecision.cs
Andere modules mogen dan bijvoorbeeld vragen:
var result = await relationshipAccessReader.CanShareExerciseAsync(request, cancellationToken);
Zij mogen niet rechtstreeks:
// Niet toegestaan buiten OefenHub.Relationships
relationshipsDbContext.UserRelationships.Where(...);
Contracten worden bewust klein gehouden. Een contract mag geen module-interne entitytypes, EF Core types of implementatiedetails lekken.
3.8 Public versus internal
De standaardregel is:
internal class ExerciseRun
{
}
voor entities en implementatieclasses.
Alleen contracttypes zijn publiek wanneer andere projecten ze moeten kunnen gebruiken:
public interface IExerciseRunResultReader
{
Task<ExerciseRunResultReadModel> GetCompletedRunAsync(
CompletedRunQuery query,
CancellationToken cancellationToken);
}
Implementatie blijft intern:
internal sealed class ExerciseRunResultReader : IExerciseRunResultReader
{
}
Dit maakt de gewenste modulegrens compile-time zichtbaar. Andere projecten kunnen het contract gebruiken, maar niet de interne implementatie of entities.
3.9 Data-laag binnen modules
Een persistent moduleproject heeft maximaal één eigen DbContext. De DbContext beheert uitsluitend tabellen binnen het eigen schema van de module.
Voorbeeld:
OefenHub.Practice/
Data/
PracticeDbContext.cs
Entities/
ExerciseRun.cs
ExerciseRunProgress.cs
SharedExercise.cs
Configurations/
ExerciseRunConfiguration.cs
ExerciseRunProgressConfiguration.cs
SharedExerciseConfiguration.cs
Migrations/
De DbContext wordt niet rechtstreeks door OefenHub.Web gebruikt. Web roept modulecontracts of services aan; de module gebruikt intern haar eigen DbContext.
internal sealed class PracticeDbContext : DbContext
{
internal DbSet<ExerciseRun> ExerciseRuns => Set<ExerciseRun>();
internal DbSet<ExerciseRunProgress> ExerciseRunProgress => Set<ExerciseRunProgress>();
}
De exacte technische zichtbaarheid van DbSet-properties kan bij implementatie worden afgestemd op EF Core-beperkingen, maar het ontwerpuitgangspunt blijft dat DbContext en entities niet als cross-module API worden gebruikt.
3.10 Commands, queries en readmodels
Binnen OefenHub wordt onderscheid gemaakt tussen command-services en query-services.
| Type | Doel | Voorbeeld |
|---|---|---|
| Command-service | Voert een mutatie of usecase uit. | IRelationshipInvitationService.InviteGuardianAsync(...) |
| Query-service / reader | Leest gegevens via een publieke module-ingang. | IRelationshipAccessReader.HasActiveGuardianRelationAsync(...) |
| Readmodel-reader | Leest een scherm-, teller-, export- of dashboardmodel. | IExerciseRunResultReader.GetCompletedRunForPdfAsync(...) |
| Interne repository/query | Ondersteunt de module-implementatie. | Alleen binnen dezelfde module toegestaan. |
Een query-service is dus geen externe message queue en geen los infrastructuurproduct. Het is een expliciete leespoort van een module.
Voorbeeld:
public interface IMessageBadgeReader
{
Task<MessageBadgeReadModel> GetBadgeAsync(
UserMessageContext context,
CancellationToken cancellationToken);
}
De implementatie mag intern CommunicationDbContext gebruiken. De aanroepende module of Web krijgt alleen het readmodel terug.
3.11 Module-eigen readmodels
Readmodels staan standaard bij de module die eigenaar is van de gegevens of de functionele uitlezing.
OefenHub.Practice/
Models/
ReadModels/
GuardianResultReadModel.cs
ExerciseRunHistoryReadModel.cs
CompletedRunPdfSourceModel.cs
Samengestelde UI-viewmodels staan niet in een centraal ReadModels project. Zij worden in OefenHub.Web samengesteld via publieke query-services.
Voorbeeld:
OefenHub.Web/
PageComposition/
TeacherFrontpageComposer.cs
ViewModels/
TeacherFrontpageViewModel.cs
De composer mag meerdere query-services aanroepen:
var catalogSummary = await catalogReader.GetTeacherCatalogSummaryAsync(context, cancellationToken);
var practiceSummary = await practiceReader.GetTeacherPracticeSummaryAsync(context, cancellationToken);
var ticketIndicator = await supportReader.GetActionIndicatorAsync(context, cancellationToken);
De composer mag geen DbContext of module-interne entity gebruiken.
3.12 Services en interne interfaces
Services bevat de implementatie van modulelogica. De map Services/Interfaces is toegestaan als organisatorische ordening voor interne interfaces. Deze map hoeft niet één-op-één in de namespace terug te komen.
Voorbeeld:
OefenHub.Practice/
Services/
StartExerciseRunService.cs
ExerciseRunResultReader.cs
ExerciseRunStatisticsCalculator.cs
Interfaces/
IExerciseRunStatisticsCalculator.cs
Interne interfaces zijn nuttig wanneer:
- een service complex genoeg is om intern te splitsen;
- een calculator of validator afzonderlijk getest moet worden;
- implementaties vervangbaar moeten zijn binnen dezelfde module;
- dependency injection binnen de module overzichtelijker wordt.
Interne interfaces worden niet automatisch publieke contracts.
3.13 Events binnen en tussen modules
Events kunnen intern of publiek zijn.
| Eventtype | Locatie | Gebruik |
|---|---|---|
| Module-intern event | <Module>/Events | Alleen binnen dezelfde module. |
| Cross-module event | <Module>/Contracts/Events | Andere modules mogen erop reageren of het via een dispatcher verwerken. |
| Scheduling payload | Meestal <Module>/Contracts/Models of scheduling-contractmodel | Wordt gebruikt om een job uitgesteld of gepland uit te voeren. |
Voorbeeld intern event:
OefenHub.Practice/
Events/
ExerciseRunCompletedInternalEvent.cs
Voorbeeld publiek event:
OefenHub.Relationships/
Contracts/
Events/
RelationshipInvitationAcceptedEvent.cs
Cross-module events mogen geen EF Core entities bevatten. Zij bevatten alleen identifiers, snapshots of kleine contractmodellen die voor de ontvanger bedoeld zijn.
3.14 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, eventueel Identity voor bestaande gebruikercontext. |
| Accountprovisioning en pending uitnodigingen koppelen | OefenHub.Identity | Relationships, Communication. |
| Melding doorzetten naar docent | OefenHub.Support | Relationships, Communication, eventueel Authorization. |
| Gedeelde oefening aanmaken | OefenHub.Practice | Relationships, Communication. |
| Periodiek verlopen heropentermijnen verwerken | OefenHub.Scheduling triggert, Support voert domeinactie uit | Support, eventueel Communication voor vervolgactie indien functioneel nodig. |
Er komt in de basis geen apart OefenHub.Workflows project. Als toekomstige workflows ontstaan zonder duidelijke domeineigenaar of met veel domeinoverstijgende coördinatie, kan een apart workflowproject via technisch ontwerpbesluit worden overwogen.
3.15 Transaction boundaries per workflow
Transaction boundaries worden niet generiek afgeleid uit het type stap. Per workflow wordt bepaald welke mutaties onderdeel zijn van de functionele transactie.
| Vraag | Gevolg |
|---|---|
| Leidt falen van deze stap tot een ongeldige of halve functionele toestand? | Stap hoort bij de kritieke transactie. |
| Is deze stap de enige praktische ingang voor een gebruiker? | Stap is waarschijnlijk kritiek. |
| Kan de stap veilig opnieuw worden uitgevoerd zonder dubbel effect? | Stap kan mogelijk retrybaar zijn. |
| Is dubbele uitvoering schadelijk? | Idempotentie of atomische verwerking verplicht. |
| Moet de eindgebruiker direct weten dat de totale actie mislukt is? | Kritieke stappen moeten vóór succesrespons voltooid zijn. |
Voorbeeld relatie-uitnodiging:
| Stap | Kritiek? | Reden |
|---|---|---|
RelationshipInvitation aanmaken | Ja | Zonder uitnodiging bestaat de actie niet. |
| Systeembericht aan ontvanger maken wanneer dit de primaire ingang is | Ja | Zonder bericht kan de ontvanger de uitnodiging mogelijk niet behandelen. |
| Badge/teller bijwerken | Nee | Kan opnieuw worden afgeleid of hersteld. |
| Technische logging/correlation vastleggen | Ja, voor zover nodig voor herleidbaarheid van de transactie | Moet reconstructie mogelijk maken. |
Voorbeeld gedeelde oefening:
| Stap | Kritiek? | Reden |
|---|---|---|
| Shared exercise record aanmaken | Ja | Dit is de bron van de gedeelde oefening. |
| Relatievoorwaarde controleren | Ja | Zonder actieve vriendschap mag de actie niet plaatsvinden. |
| Systeembericht maken | Afhankelijk van functionele ingang | Kritiek als het bericht de enige ingang is; retrybaar als de gedeelde oefening ook via een eigen overzicht zichtbaar is. |
| Readmodel/teller bijwerken | Nee | Afgeleid en herstelbaar. |
De regel is bewust voorzichtig: bij twijfel wordt een stap als kritiek behandeld totdat is onderbouwd dat retrybare naverwerking veilig is.
3.16 Scheduling en jobcontracten
OefenHub.Scheduling is eigenaar van de technische lifecycle van jobs nadat een job succesvol is aangemaakt. Domeinmodules blijven eigenaar van de domeinlogica die door de job wordt uitgevoerd.
De aanmaak van een job loopt via een scheduling-contract. De uitvoering loopt terug naar het betrokken domein via een publiek contract.
Scheduling beheert:
- jobtype;
- payloadreferentie of payload;
- correlation-id;
- pogingenteller;
- retryconfiguratie;
- laatste fout;
- status;
- technische jobhistorie;
- interne beheerbaarheid.
De domeinmodule beheert:
- businessregels;
- domeinvalidatie;
- domeinmutaties;
- domeintransaction boundary;
- domeinspecifieke audit/history.
Bij multi-domain jobs moet per jobtype expliciet worden vastgelegd of de uitvoering atomair moet zijn, compensatie vereist, retrybaar is of bij fout in een beheerbare failed-status eindigt.
3.17 Logging en correlation door lagen heen
Alle domeinoverstijgende processen moeten herleidbaar zijn via een correlation-id. Dit geldt voor directe HTTP-requests, cross-module workflows en jobs.
Minimaal relevante context:
| Veld | Gebruik |
|---|---|
CorrelationId | Koppelt request, workflow, job en domeinacties. |
ActorUserId | Gebruiker die de actie startte, indien beschikbaar. |
ActorRoleContext | Rolcontext waarin de actie is gestart, indien beschikbaar. |
ModuleName | Module die de actie uitvoert. |
OperationName | Naam van command, query, workflow of jobstap. |
JobId | Alleen bij geplande of retrybare jobs. |
AttemptNumber | Alleen bij retrybare verwerking. |
ObjectType en ObjectId | Functionele verwijzing naar geraakt object, zonder gevoelige payloads. |
Deze loggingafspraak vervangt geen domeinspecifieke historytabellen. Domeinhistory blijft bij het verantwoordelijke domein. Correlation logging zorgt voor technische reconstructie over modules heen.
3.18 Web-projectstructuur op hoofdlijn
OefenHub.Web krijgt een eigen structuur omdat UI, Blazor/Razor-componenten, routing, state en page composition andere ordeningsregels hebben dan domeinmodules.
OefenHub.Web/
Components/
Shared/
Student/
Teacher/
Guardian/
Admin/
Messages/
Tickets/
Pages/
Public/
Account/
Student/
Teacher/
Guardian/
Admin/
Messages/
Tickets/
Profile/
Layouts/
Navigation/
State/
Forms/
Validation/
ViewModels/
PageComposition/
Extensions/
wwwroot/
| Map | Doel |
|---|---|
Components | Herbruikbare UI-componenten, rol- of domeingericht geordend; terugkerende MudBlazor-combinaties worden als OefenHub-wrappercomponenten geïsoleerd. |
Pages | Routebare pagina's of page-level Razor/Blazor-bestanden. |
Layouts | Hoofd-layouts, rolcontextlayouts en shellstructuur. |
Navigation | Menustructuren, navigatiecompositie en presentatielogica voor navigatie. |
State | UI-state, browserstate en tijdelijke componentstate; geen autorisatiebron. |
Forms | Formuliermodellen en formulieropbouw voor UI. |
Validation | UI-validatie en formulierfeedback; domeinvalidatie blijft in modules. |
ViewModels | Samengestelde modellen voor pagina's en componenten. |
PageComposition | Services die pagina-viewmodels samenstellen via modulequery-services. |
Extensions | Webhost-, middleware- en dependency-injectionregistratie. |
wwwroot | Statische assets. |
Regels voor OefenHub.Web:
- geen EF Core DbContexts;
- geen module-interne entities;
- geen businesslogica;
- geen autorisatie op basis van alleen clientstate;
- wel page composition via publieke query-services;
- wel UI-state voor presentatie, formulieren en tijdelijke interactie;
- wel middleware/pipelineconfiguratie, waaronder security headers, HSTS, CSP en rate limiting.
3.19 Concrete technische oefenmodules
Concrete technische oefenmodules volgen een aparte projectvorm:
OefenHub.Modules.<ModuleCategory>.<ModuleName>/
Contracts/
Models/
Enums/
Services/
Interfaces/
Generation/
Evaluation/
Validation/
Rendering/
Pdf/
Helpers/
Extensions/
Voorbeelden:
OefenHub.Modules.Arithmetic.AdditionSubtractionSimple/
OefenHub.Modules.Arithmetic.Fractions/
OefenHub.Modules.Language.Spelling/
De technische modulecategorie is onderdeel van de projectnaam. Zij hoeft niet één-op-één gelijk te zijn aan de functionele categorie in de oefencatalogus. Voor de eerste V1.0-module gebruikt Optellen & Aftrekken (simpel) het technische project OefenHub.Modules.Arithmetic.AdditionSubtractionSimple; het moduledossier staat onder Oefenmodules / Rekenen.
| Map | Doel |
|---|---|
Contracts | Eventuele module-eigen publieke types naast het centrale modulecontract. |
Models | Moduleconfiguratie-DTO's, vraagmodellen, antwoordmodellen en interne modellen. |
Generation | Vraaggeneratie. |
Evaluation | Antwoordcontrole en scorebepaling binnen modulegrens. |
Validation | Configuratie- en inputvalidatie. |
Rendering | Schermrepresentatie van vragen, antwoorden of module-specifieke notatie. |
Pdf | Module-specifieke PDF/exportrepresentatie waar nodig. |
Helpers | Kleine module-lokale hulpfuncties. |
Extensions | Module-registratie of dependency injection. |
Concrete oefenmodules mogen primair afhankelijk zijn van OefenHub.Modules.Abstractions. Gebruik van OefenHub.SharedKernel is toegestaan wanneer dit duplicatie voorkomt en het type aantoonbaar generiek is. Concrete oefenmodules mogen niet afhankelijk worden van OefenHub.Catalog, OefenHub.Practice, OefenHub.Web of interne implementatie van OefenHub.ExerciseModuleHost.
Concrete oefenmodules hebben standaard geen eigen DbContext. Configuratie en runtimegegevens worden generiek opgeslagen via Catalog en Practice, terwijl de module verantwoordelijk blijft voor configuratievalidatie, vraaggeneratie, antwoordcontrole, rendering en PDF-representatie.
3.20 SharedKernel-gebruik
OefenHub.SharedKernel is beperkt bedoeld. Het project mag niet uitgroeien tot een verzameling restlogica.
Toegestaan:
- generieke resulttypes;
- clock/time abstractions;
- correlation-id types;
- eenvoudige generieke value objects;
- marker interfaces wanneer echt projectbreed nodig;
- generieke exceptions of foutcodes wanneer zij niet domeinspecifiek zijn.
Niet toegestaan:
- domeinservices;
- module-specifieke DTO's;
- repositories;
- business rules;
- enumwaarden die bij één module horen;
- UI-modellen;
- EF Core entities.
Wanneer twijfel bestaat, blijft het type in de module totdat meerdere modules aantoonbaar dezelfde generieke bouwsteen nodig hebben.
3.21 Infrastructure-gebruik
OefenHub.Infrastructure bevat technische integratie die niet bij één functionele module hoort. Het project mag geen functionele domeinregels bevatten.
Voorbeelden van toegestane inhoud:
- configuratiebinding voor technische opties;
- adapters voor externe technische voorzieningen;
- loggingintegratie;
- generieke host-extensies;
- infrastructuurabstractions die niet domeinspecifiek zijn;
- technische helpers voor environment/configuratie.
Securityconfiguratie zoals HSTS, CSP, rate limiting en security headers wordt in het Technisch Ontwerp volledig beschreven, maar krijgt in de eerste baseline geen apart OefenHub.Security project. De registratie zit primair in OefenHub.Web en waar nodig in OefenHub.Infrastructure of extension methods.
3.22 Dependency injection en module-registratie
Elke module biedt een extension method voor registratie van de eigen services en, indien van toepassing, DbContext.
OefenHub.Practice/
Extensions/
ServiceCollectionExtensions.cs
Voorbeeld:
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddPracticeModule(
this IServiceCollection services,
IConfiguration configuration)
{
// registreer modulecontracts, interne services en PracticeDbContext
return services;
}
}
OefenHub.Web roept deze registraties aan tijdens applicatiestart. De Web-host hoeft de interne classes niet te kennen; de module registreert zelf de implementaties achter haar publieke contracts.
3.23 Validatie en foutafhandeling per laag
| Laag | Validatie/foutafhandeling |
|---|---|
| UI/Web | Formaatvalidatie, verplichte velden, gebruikersfeedback en aanroepen van modulecontracts. Geen definitieve autorisatie- of domeinbeslissingen. |
| Modulecontract | Controle op requestvorm, actorcontext en minimale invoer. |
| Module-service | Domeinvalidatie, autorisatiecontrole via relevante contracten, statusovergangen en transaction boundary. |
| Data-laag | Constraints, unique indexes, concurrency en persistencefouten. |
| Scheduling | Jobstatus, retrybeleid, pogingenteller, technische foutregistratie en failed-status. |
| Infrastructure | Technische fouten rond configuratie, externe adapters en hostintegratie. |
Gebruikersgerichte foutmeldingen verwijzen naar bestaande popup-/feedbackmechanismen waar die functioneel zijn vastgelegd. Het Technisch Ontwerp beschrijft technische foutafhandeling, logging en propagation, maar introduceert geen nieuwe functionele foutteksten.
3.24 Testprojectstructuur
Testprojecten staan onder de solution folder of fysieke map tests.
tests/
OefenHub.Practice.Tests/
OefenHub.Relationships.Tests/
OefenHub.Support.Tests/
OefenHub.Modules.Arithmetic.Addition.Tests/
OefenHub.ArchitectureTests/
OefenHub.IntegrationTests/
Binnen een testproject mogen tests naar behoefte worden geordend:
OefenHub.Practice.Tests/
Unit/
Integration/
Contracts/
TestData/
De exacte testindeling hoeft niet voor elk project identiek te zijn. Per module wordt gekozen welke testvormen nodig zijn om de lading te dekken.
Architecture tests bewaken aanvullend:
- modules referencen
OefenHub.Webniet; - module-interne namespaces worden niet buiten de module gebruikt;
- contracts lekken geen EF Core entities;
- concrete oefenmodules referencen geen
Catalog,PracticeofWeb; SharedKernelbevat geen module-specifieke types.
3.25 Niet toegestane patronen
| Niet toegestaan | Reden |
|---|---|
| Webcomponent gebruikt direct een module-DbContext. | Doorbreekt modulegrens en autorisatie-/serviceafspraken. |
Module A gebruikt entity uit ModuleB.Data.Entities. | Maakt modules compile-time en persistence-technisch gekoppeld. |
| Cross-module query via directe SQL op andermans schema zonder contract. | Omzeilt eigenaarschap en maakt refactoring risicovol. |
| Publieke DTechnisch Ontwerp bevat EF Core entity. | Lekt persistence-implementatie naar andere modules. |
SharedKernel gebruiken voor domeinspecifieke enums. | Maakt SharedKernel een vuilnisbak en verzwakt eigenaarschap. |
Concrete oefenmodule refereert Practice of Catalog. | Oefenmodulecontract wordt dan afhankelijk van platformimplementatie. |
| Scheduling voert businesslogica zelf uit. | Scheduling is eigenaar van joblifecycle, niet van domeinregels. |
3.26 Verwijzing naar detailhoofdstukken
Dit hoofdstuk legt de algemene structuur en dependency-richting vast. Belangrijke detailhoofdstukken zijn:
- Technisch Ontwerp: Databaseontwerp, migraties, seeddata en constraints voor DbContexts, schemas, migrations, seeddata, constraints, soft links en snapshots.
- Technisch Ontwerp: Oefenmodulecontract en dynamische module-integratie voor technische oefenmodules, modulehost en modulecontracten.
- Technisch Ontwerp: Background jobs, TickerQ en periodieke verwerking voor scheduling, retries, jobstatussen en TickerQ.
- Technisch Ontwerp: Logging, audit, securitylogging en technische foutafhandeling voor correlation, technische logging, domeinhistory en foutreconstructie.
- Technisch Ontwerp: Security, infrastructuur, secrets en omgevingen voor security headers, HSTS, CSP, rate limiting, secrets en omgevingen.
- Technisch Ontwerp: Teststrategie, acceptatieherleidbaarheid en kwaliteitsgrenzen voor testsoorten, architecture tests en kwaliteitsgrenzen.
- Technisch Ontwerp: Frontend, Blazor, routing, state en componentopbouw voor de volledige Blazor/Razor-structuur.