Background jobs, TickerQ en periodieke verwerking
18.1 Doel van dit hoofdstuk
Dit hoofdstuk beschrijft hoe OefenHub achtergrondverwerking, periodieke verwerking, uitgestelde acties en retrybare technische verwerking uitvoert. De uitwerking sluit aan op de middel-zware modulaire monoliet uit Technisch Ontwerp: architectuuroverzicht en solution-opbouw en de dependency- en modulegrenzen uit Technisch Ontwerp: applicatielagen, projectstructuur en dependency-richting.
Background jobs worden gebruikt voor verwerking die niet betrouwbaar of wenselijk direct in de gebruikersrequest hoeft plaats te vinden, maar wel technisch herleidbaar, begrensd, idempotent en beheerbaar moet zijn. Voorbeelden zijn periodieke opschoning, verwerken van verlopen termijnen, retrybare notificatieverwerking, geplande technische onderhoudstaken en domeinacties die bewust buiten de directe gebruikersflow worden geplaatst.
Dit hoofdstuk introduceert geen nieuwe functionele eisen. Functionele regels over bijvoorbeeld verlopen relatie-uitnodigingen, testrun-opruiming, meldingenheropentermijnen, systeemcommunicatie of PDF-cleanup blijven afkomstig uit Functioneel Ontwerp, Software Requirements Specification en database-informatie. Dit hoofdstuk beschrijft hoe zulke regels technisch uitgevoerd en bewaakt worden.
18.2 Ontwerpuitgangspunten
Voor background jobs gelden de volgende ontwerpuitgangspunten.
| Uitgangspunt | Technische betekenis |
|---|---|
| Eén applicatie, één database | Background jobs draaien binnen dezelfde deploybare OefenHub-applicatie en gebruiken dezelfde relationele database. |
| TickerQ als scheduler | TickerQ wordt gebruikt voor planning, uitvoering, retrybeleid en periodieke verwerking. |
| Eigen schedulingmodule | OefenHub.Scheduling is eigenaar van de technische joblifecycle. |
| Domeineigenaarschap blijft intact | De businesslogica van een job blijft in de module die functioneel eigenaar is van de actie. |
| Geen externe message broker | RabbitMQ, Azure Service Bus of vergelijkbare message brokers zijn geen uitgangspunt voor de eerste technische baseline. |
| Persistente jobstatus | Een applicatieherstart mag geplande, lopende of gefaalde jobs niet onzichtbaar of onherleidbaar maken. |
| Begrensde retries | Retrybare acties worden niet oneindig opnieuw geprobeerd. |
| Idempotentie | Opnieuw uitvoeren van een job mag geen dubbele of corrupte domeinmutaties veroorzaken. |
| Herleidbaarheid | Iedere jobuitvoering moet via correlation-id, job-id en domeinlogging te reconstrueren zijn. |
| Interne beheerbaarheid | Jobstatussen en gefaalde jobs moeten intern inzichtelijk en waar nodig herstelbaar zijn. |
18.3 Project- en schema-afbakening
OefenHub.Scheduling is het technische project voor background jobs, TickerQ-integratie en periodieke verwerking.
src/
OefenHub.Scheduling/
Contracts/
Data/
Models/
Services/
Extensions/
Wanneer TickerQ persistence en/of aanvullende schedulingtabellen nodig zijn, gebruikt dit project een eigen DbContext en schema.
| Onderdeel | Keuze |
|---|---|
| Project | OefenHub.Scheduling |
| DbContext | SchedulingDbContext, indien eigen persistente jobmetadata nodig is naast TickerQ-tabellen |
| Schema | scheduling |
| TickerQ-tabellen | Bij voorkeur onder het schema scheduling, mits TickerQ dit ondersteunt |
| Connectionstring | Dezelfde applicatieconnectionstring als de overige modules |
| Webinterface | TickerQ-dashboard of webinterface alleen intern beschikbaar maken |
Het schedulingproject wordt geen eigenaar van domeindata. Het bewaart technische jobstatus, planning, pogingen, fouten en correlation-informatie. De feitelijke domeinmutatie wordt altijd uitgevoerd via een publiek contract van de betreffende domeinmodule.
18.4 Verantwoordelijkheidsverdeling
De kernregel is dat scheduling eigenaar is van de technische lifecycle van jobs, terwijl domeinmodules eigenaar blijven van de inhoudelijke actie.
| Verantwoordelijkheid | Eigenaar |
|---|---|
| Bepalen dat een job nodig is | Domeinmodule of technische infrastructuurlaag die de situatie detecteert |
| Job aanvragen | Domeinmodule via scheduling-contract |
| Job persistent registreren | OefenHub.Scheduling / TickerQ |
| Job plannen | OefenHub.Scheduling / TickerQ |
| Retryconfiguratie toepassen | OefenHub.Scheduling |
| Jobuitvoering starten | OefenHub.Scheduling |
| Domeinactie uitvoeren | Functioneel eigenaar via publiek contract |
| Domeinregels valideren | Functioneel eigenaar |
| Technische jobstatus bijwerken | OefenHub.Scheduling |
| Technische jobfout registreren | OefenHub.Scheduling, met correlation naar domeinlogging |
| Domeinlogging/history vastleggen | Functioneel eigenaar van de actie |
| Failed-job beheerbaar maken | OefenHub.Scheduling |
Voorbeeld: wanneer OefenHub.Communication een uitgesteld te verzenden of opnieuw te proberen systeembericht wil verwerken, maakt Communication geen eigen scheduler. De module vraagt via het schedulingcontract een job aan. Wanneer de job triggert, roept Scheduling een publiek contract van Communication aan. Communication bepaalt of het bericht inhoudelijk nog verzonden mag worden en voert de domeinmutatie uit.
18.5 Jobtypen
OefenHub onderscheidt meerdere technische jobtypen. Deze indeling is bedoeld om retrybeleid, logging en beheerbaarheid per soort verwerking helder te houden.
| Jobtype | Omschrijving | Voorbeeld |
|---|---|---|
| Periodieke controlejob | Job die op vaste interval kandidaten zoekt en verwerkt | Verlopen meldingenheropentermijnen controleren |
| Delayed job | Job die éénmalig op een gepland moment uitgevoerd wordt | Tijdelijke export uitgesteld opruimen |
| Retrybare vervolgactie | Vervolgactie na eerdere bronmutatie die bij technische fout opnieuw geprobeerd mag worden | Retry van een communicatieactie |
| Maildelivery job | Uitgestelde of retrybare maildispatch via de geconfigureerde mailprovider | Uitnodigingsmail of notificatiemail opnieuw aanbieden |
| Onderhoudsjob | Technische of beheergerichte periodieke taak | Opruimen van achtergebleven testruns |
| Hersteljob | Expliciet beheer- of supportgestuurd herstel van eerder gefaalde technische verwerking | Failed job opnieuw aanbieden na analyse |
Een jobtype is geen vrij tekstveld zonder betekenis. Per jobtype moet technisch vastliggen welk contract wordt aangeroepen, welke payload verwacht wordt, welk retrybeleid geldt en welke fouten tot definitieve failed-status leiden.
18.5.1 Maildelivery jobs
Maildelivery jobs zijn technische jobs voor e-mailverwerking die door een domeinmodule of door Communication worden aangevraagd wanneer e-maildispatch niet direct of niet betrouwbaar binnen de gebruikersflow moet worden uitgevoerd.
| Onderdeel | Regel |
|---|---|
| Eigenaarschap joblifecycle | OefenHub.Scheduling bewaakt status, retries, pogingenteller, foutregistratie en dashboardzichtbaarheid. |
| Eigenaarschap inhoud | OefenHub.Communication of de veroorzakende domeinmodule bepaalt de mailinhoud, ontvangercontext en functionele geldigheid. |
| Providerintegratie | Via OefenHub.Infrastructure en veilige configuratie. |
| Idempotentie | Iedere maildelivery job heeft een idempotency key zodat retry geen dubbele domeinmutatie veroorzaakt. |
| Falen | Na begrensde retries eindigt de job in beheerbare failed-status met correlation-id en laatste foutcategorie. |
| Herstel | Herstart of handmatige retry mag alleen opnieuw dispatchen wanneer de onderliggende domeinactie nog geldig is. |
Een maildelivery job mag geen workaround zijn voor een ongeldige bronmutatie. Wanneer mail de enige functionele ingang voor een gebruiker is, bepaalt de workflow of de mailaanvraag onderdeel is van de kritieke transactie.
18.6 Job lifecycle
Een job doorloopt technisch een expliciete lifecycle. De exacte statusnamen kunnen worden afgestemd op TickerQ, maar de conceptuele lifecycle is als volgt.
| Status | Betekenis |
|---|---|
Scheduled | Job is geregistreerd en wacht op uitvoering. |
Processing | Job wordt op dit moment uitgevoerd. |
Succeeded | Job is succesvol voltooid. |
RetryScheduled | Job is gefaald maar wordt opnieuw geprobeerd binnen het retrybeleid. |
Failed | Job is definitief gefaald of handmatig in failed-status geplaatst. |
Cancelled | Job is bewust geannuleerd voordat succesvolle uitvoering nodig of toegestaan was. |
Een job mag niet stilzwijgend verdwijnen. Ook wanneer TickerQ intern eigen statussen gebruikt, moet OefenHub voldoende technische en functionele informatie bewaren om de uitvoering te reconstrueren.
18.7 Jobaanvraag via contract
Domeinmodules maken jobs niet rechtstreeks aan via TickerQ-implementatiedetails. Zij gebruiken een publiek schedulingcontract.
Voorbeeld van een conceptueel contract:
public interface ISchedulingJobService
{
Task<ScheduledJobReference> ScheduleAsync(
ScheduleJobRequest request,
CancellationToken cancellationToken = default);
}
Voorbeeld van een aanvraagmodel:
public sealed class ScheduleJobRequest
{
public required string JobType { get; init; }
public required string PayloadJson { get; init; }
public required string CorrelationId { get; init; }
public required string TriggeredByModule { get; init; }
public Guid? TriggeredByUserId { get; init; }
public string? TriggeredByRoleContext { get; init; }
public DateTimeOffset ExecuteAfterUtc { get; init; }
public string? RetryPolicyKey { get; init; }
}
Dit voorbeeld is geen verplichte definitieve C#-implementatie, maar legt het technische principe vast: domeinmodules vragen jobs aan via een contract en geven voldoende context mee voor uitvoering, logging, retrybeleid en herleidbaarheid.
18.8 Jobuitvoering via domeincontract
Wanneer TickerQ een job uitvoert, verwerkt OefenHub.Scheduling de job niet door rechtstreeks domeintabellen of domeinentities te muteren. De scheduler roept een publiek contract van de eigenaarmodule aan.
Voorbeeld:
public interface IPendingMessageDeliveryJobHandler
{
Task<DomainJobResult> ExecuteAsync(
PendingMessageDeliveryJobPayload payload,
JobExecutionContext context,
CancellationToken cancellationToken = default);
}
De handler leeft in de domeinmodule, bijvoorbeeld OefenHub.Communication. De scheduler is alleen verantwoordelijk voor het laden van de job, het doorgeven van context, het vastleggen van technische uitvoering en het bepalen van retry of failed-status.
18.9 Transaction boundaries en consistentie
Background jobs mogen niet worden gebruikt om onveilige halve functionele toestanden te normaliseren. Per workflow moet expliciet bepaald worden welke stappen kritiek zijn voor de functionele uitkomst.
| Situatie | Beleid |
|---|---|
| De actie is zonder stap B functioneel ongeldig | Stap A en B horen in dezelfde transaction boundary of dezelfde kritieke workflow. |
| De vervolgactie is alleen notificerend of afgeleid | De vervolgactie mag retrybaar buiten de gebruikersflow plaatsvinden. |
| Een systeembericht is de enige ingang voor de gebruiker | Het systeembericht is functioneel kritiek en mag niet automatisch als niet-kritiek worden behandeld. |
| Een job raakt meerdere domeinen | Per jobtype vastleggen of atomische uitvoering, compensatie of failed-status nodig is. |
| Falen kan door beheer veilig hersteld worden | Failed-status met beheerbare herstelactie is toegestaan. |
| Falen leidt tot onherstelbare of misleidende data | De stap moet als kritiek behandeld worden. |
Scheduling mag dus zowel niet-kritieke vervolgacties als kritieke geplande workflows ondersteunen, maar het Technisch Ontwerp beschouwt deze niet als hetzelfde patroon.
18.9.1 Kritieke bronmutaties
Kritieke bronmutaties die samen de functionele uitkomst van één actie bepalen, worden niet losjes als meerdere onafhankelijke jobs achter elkaar gezet wanneer falen van stap twee de uitkomst van stap één ongeldig maakt.
Voorbeeld: als een relatie-uitnodiging alleen via een systeembericht bereikbaar is voor de ontvanger, dan kan het aanmaken van de uitnodiging en het aanmaken van het systeembericht onderdeel zijn van dezelfde kritieke workflow. In dat geval mag de uitnodiging niet succesvol lijken wanneer het bericht niet is aangemaakt.
18.9.2 Niet-kritieke vervolgacties
Niet-kritieke vervolgacties mogen buiten de directe request plaatsvinden wanneer zij:
- idempotent zijn;
- begrensd retrybaar zijn;
- bij falen zichtbaar worden in technische logging of beheer;
- geen ongeldige functionele bronstatus achterlaten;
- op een volgend moment opnieuw opgebouwd of hersteld kunnen worden.
Voorbeelden zijn tellerprojecties, readmodelverversing, badges, technische cleanup of informatieve notificaties die niet de enige functionele ingang vormen.
18.10 Retrybeleid
Retry is een hulpmiddel, geen vervanging voor goede transactionele afbakening. Iedere retrybare job heeft een expliciet retrybeleid. Retrybare verwerking blijft binnen TickerQ en de bestaande modulaire monoliet; zij introduceert geen externe message broker of los workflowplatform.
| Aspect | Regel |
|---|---|
| Maximum aantal pogingen | Per jobtype configureren; geen oneindige retries. |
| Backoff | Per jobtype bepalen, bijvoorbeeld vaste interval of oplopende interval. |
| Idempotency key | Vereist bij acties waarbij dubbele uitvoering schade kan veroorzaken. |
| Foutclassificatie | Onderscheid tussen tijdelijke technische fout en definitieve functionele fout. |
| Failed-status | Na overschrijden van retrybeleid blijft de job zichtbaar als gefaald. |
| Beheeractie | Failed jobs moeten intern analyseerbaar en waar toegestaan opnieuw aan te bieden zijn. |
Voorbeeld van retryclassificatie:
| Fouttype | Retry? | Voorbeeld |
|---|---|---|
| Tijdelijke database-lock of timeout | Ja | Verwerking wordt via een expliciet technisch ontwerpbesluit opnieuw geprobeerd. |
| Tijdelijke externe render-/bestandsfout | Ja, begrensd | PDF-cleanup of tijdelijke exportverwerking. |
| Ongeldige payload | Nee | Job gaat naar Failed voor analyse. |
| Domeinobject niet meer geldig | Meestal nee | Uitnodiging verlopen of relatie beëindigd. |
| Autorisatiecontext ongeldig | Nee, tenzij expliciet herstelbaar | Job mag geen rechten omzeilen. |
18.10.1 Retrydefaults per jobtype
De V1.0-baseline gebruikt retrydefaults per jobtype. Deze defaults zijn bewust licht gehouden en mogen per concrete job alleen worden aangescherpt of verruimd wanneer de jobhandler idempotent is en de afwijking expliciet in configuratie, code of documentatie is vastgelegd. Finetuning per concrete job blijft toegestaan tijdens implementatie; het bestaan van finetuning heropent dit ontwerpbesluit niet.
| Jobtype | Maximaal aantal pogingen | Delay/backoff | Failed-status en beheeractie |
|---|---|---|---|
| Periodieke controlejob | 1 retry na de initiële poging | Korte vaste delay, bijvoorbeeld 5 minuten | Daarna Failed of batchfout zichtbaar maken; de volgende periodieke run mag dezelfde kandidaten opnieuw vinden. |
| Onderhoudsjob | 1 retry na de initiële poging | Korte vaste delay, bijvoorbeeld 5 tot 15 minuten | Daarna zichtbaar als failed; technisch beheer beoordeelt of handmatige retry of wachten op volgende interval passend is. |
| Retrybare vervolgactie | 3 pogingen totaal | Oplopend, bijvoorbeeld 5 minuten, 30 minuten, 2 uur | Daarna failed met correlation-id; handmatige retry alleen wanneer de onderliggende domeinactie nog geldig is. |
| Delayed job | 3 pogingen totaal | Oplopend, bijvoorbeeld 5 minuten, 30 minuten, 2 uur | Daarna failed; opnieuw plannen vereist een nieuwe geldige aanvraag of beheeractie. |
| Maildelivery job | 5 pogingen totaal | Oplopend, bijvoorbeeld 5 minuten, 15 minuten, 1 uur, 6 uur, 24 uur | Daarna failed en zichtbaar voor technisch beheer; mail wordt niet onbeperkt opnieuw aangeboden. |
| Hersteljob | 1 poging, tenzij expliciet anders gekozen | Geen automatische backoff of maximaal één korte retry | Bij falen opnieuw analyseren; hersteljobs zijn beheer-/supportgestuurd en mogen geen automatische retryloop worden. |
| Readmodel- of cache-refresh job | 3 pogingen totaal | Oplopend, bijvoorbeeld 1 minuut, 5 minuten, 15 minuten | Daarna failed of rebuild vereist; brondata blijft leidend en afgeleide projectie mag opnieuw worden opgebouwd. |
Regels:
- het aantal pogingen telt de eerste uitvoering mee;
- functionele fouten, ongeldige payloads, verlopen domeinobjecten en ontbrekende autorisatiecontext leiden niet tot blind retryen;
- periodieke controlejobs gebruiken weinig retries, omdat de volgende interval gemiste kandidaten opnieuw kan selecteren;
- maildelivery krijgt meer retries, omdat externe mailproviders tijdelijk onbeschikbaar kunnen zijn;
- iedere retrybare job heeft een idempotency-afspraak voordat retry in productie wordt geactiveerd;
- iedere definitief gefaalde job bevat minimaal jobtype, attemptnummer, correlation-id, foutcategorie en laatste fouttijdstip;
- concrete jobhandlers mogen lagere retrygrenzen gebruiken wanneer dat veiliger is;
- ruimere retrygrenzen dan deze baseline vereisen een expliciete technische motivatie.
18.11 Idempotentie
Iedere jobhandler moet veilig kunnen omgaan met herhaalde uitvoering. Dat is nodig omdat retries, applicatieherstarts, timeouts of onduidelijke uitvoeringsstatus kunnen leiden tot opnieuw uitvoeren.
Voorbeelden van idempotentie-afspraken:
| Actie | Idempotentieafspraak |
|---|---|
| Systeembericht aanmaken | Gebruik een idempotency key of bronverwijzing zodat hetzelfde bericht niet dubbel ontstaat. |
| Testruns opruimen | Alleen records verwerken die nog aan de selectiecriteria voldoen. |
| Heropentermijn verwerken | Alleen actuele sluitcontext verwerken; inmiddels geregistreerde heropeningen uitsluiten. |
| Readmodel verversen | Upsert of rebuild gebruiken in plaats van blind dupliceren. |
| Tijdelijke bestanden verwijderen | Ontbrekend bestand beschouwen als reeds opgeschoond, niet als functionele fout. |
Idempotentie hoort bij de domeinhandler, omdat alleen de eigenaarmodule weet wanneer dubbele uitvoering functioneel schadelijk is.
18.12 Correlation, logging en technische herleidbaarheid
Jobuitvoering moet domeinoverstijgend herleidbaar zijn. Iedere job en iedere domeinactie die door een job wordt gestart, gebruikt dezelfde correlation-id.
Minimaal vast te leggen technische context:
| Veld | Betekenis |
|---|---|
CorrelationId | Verbindt request, domeinactie, jobaanvraag, jobuitvoering en domeinlogging. |
JobId | Technische identifier van de geplande job. |
JobType | Type verwerking dat wordt uitgevoerd. |
AttemptNumber | Huidige poging binnen het retrybeleid. |
TriggeredByModule | Module die de job heeft aangevraagd of gepland. |
TriggeredByUserId | Gebruiker die de oorspronkelijke actie veroorzaakte, indien van toepassing. |
TriggeredByRoleContext | Rolcontext van de oorspronkelijke actie, indien van toepassing. |
StartedAtUtc | Startmoment van deze poging. |
CompletedAtUtc | Succesvol eindmoment. |
FailedAtUtc | Foutmoment bij mislukking. |
LastErrorCode | Technisch of functioneel fouttype. |
LastErrorMessage | Beperkte foutinformatie zonder gevoelige payloads of tokens. |
Logging mag geen wachtwoorden, tokens, volledige identity-providerpayloads, onnodige persoonsgegevens of volledige antwoordpayloads van oefeningen bevatten. Wanneer een job gevoelige domeindata verwerkt, wordt alleen de noodzakelijke technische context gelogd.
18.13 Security en autorisatie bij jobs
Jobs draaien server-side en mogen geen clientstate, routeparameters of UI-context vertrouwen. Wanneer een job namens of naar aanleiding van een gebruikeractie draait, moet de relevante server-side context in de payload of jobcontext zijn vastgelegd en opnieuw door de domeinhandler worden beoordeeld.
| Onderwerp | Regel |
|---|---|
| Autorisatie | Jobs mogen geen domeinregels of autorisatiecontroles omzeilen. |
| Rolcontext | Indien relevant wordt de oorspronkelijke rolcontext als snapshot meegegeven. |
| Gebruikercontext | TriggeredByUserId is contextinformatie, geen automatisch autorisatiebewijs. |
| Payload | Payload bevat alleen noodzakelijke identifiers en parameters. |
| Secrets | Secrets worden niet in jobpayloads opgeslagen. |
| Dashboardtoegang | TickerQ-dashboard is alleen intern en beheerbaar beschikbaar. |
| Handmatige retry | Handmatige retry vereist beheercontext en logging. |
Als een job bij uitvoering ontdekt dat de domeincontext niet langer geldig is, moet de handler een expliciet resultaat teruggeven, bijvoorbeeld Cancelled, NoLongerApplicable of Failed, afhankelijk van de aard van de job.
18.14 TickerQ-dashboard en interne beheerbaarheid
Wanneer TickerQ een webinterface of dashboard biedt voor jobinzage, wordt deze gebruikt als interne beheerfunctie. Deze interface is niet publiek bereikbaar.
| Aspect | Regel |
|---|---|
| Beschikbaarheid | Alleen intern, bijvoorbeeld via beheeromgeving, intern netwerk of afgeschermde route. |
| Autorisatie | Alleen beheerder- of technische beheercontext. |
| Zichtbare informatie | Geen gevoelige payloads, tokens of persoonsgegevens tonen tenzij functioneel noodzakelijk en toegestaan. |
| Handmatige acties | Retry, annuleren of markeren als afgehandeld alleen met logging. |
| Auditbaarheid | Handmatige beheeracties rond jobs worden technisch herleidbaar gelogd. |
Het dashboard is bedoeld voor technische analyse en herstel, niet als gebruikersgerichte functionaliteit.
18.15 Periodieke jobs per domein
De onderstaande tabel geeft de eerste technische ordening van bekende periodieke of achtergrondverwerkingen. De exacte jobnamen en intervallen worden bij implementatie afgestemd op de definitieve domein- en databaseuitwerking.
| Domein | Verwerking | Type | Eigenaar uitvoering | Schedulingrol |
|---|---|---|---|---|
| Relationships | Verlopen relatie-uitnodigingen markeren | Periodieke controlejob | OefenHub.Relationships | Triggert interval en bewaakt status |
| Practice | Achtergebleven docent-testruns opruimen | Periodieke onderhoudsjob | OefenHub.Practice | Triggert interval en registreert fouten |
| Support | Verlopen heropentermijnen verwerken | Periodieke controlejob | OefenHub.Support | Triggert interval; geen losse job per ticket |
| Communication | Retrybare communicatieacties verwerken | Retrybare vervolgactie | OefenHub.Communication | Bewaakt pogingen en failed-status |
| Reporting | Tijdelijke PDF-/exportbestanden opruimen | Periodieke onderhoudsjob | OefenHub.Reporting | Triggert interval en registreert technische fouten |
| Admin | Verlopen systeemnotificaties controleren indien nodig | Periodieke controlejob | OefenHub.Admin | Alleen indien technische verwerking nodig is |
Niet iedere functionele datum vereist een losse scheduled job. Waar veel records op vergelijkbare criteria verlopen, heeft een periodieke idempotente controlejob de voorkeur boven één losse TickerQ-job per record.
18.16 Verlopen meldingenheropentermijnen
Voor meldingen geldt expliciet dat het definitief gebruikersgericht sluiten na verlopen heropentermijn niet wordt ingericht als losse TickerQ-taak per melding. De technische verwerking zoekt periodiek naar kandidaten die op dat moment aan de actuele databasecriteria voldoen.
Technische aanpak:
OefenHub.Schedulingtriggert periodiek de supportjob.OefenHub.Supportzoekt gesloten tickets met een actuele sluitregistratie waarvanReopenDeadlineUtcverlopen is.- Tickets die inmiddels zijn heropend, geaccepteerd of geen actuele sluitcontext meer hebben, worden uitgesloten.
- Per kandidaat wordt idempotent bepaald of een compacte historyregel nodig is.
- Fout bij één kandidaat blokkeert de verwerking van andere kandidaten niet.
- De job rapporteert aantal gevonden, verwerkt, overgeslagen en gefaalde kandidaten.
Deze aanpak voorkomt een grote hoeveelheid losse jobs en vangt gemiste of vertraagde uitvoeringen automatisch op bij een volgende run.
18.17 Verlopen relatie-uitnodigingen
Relatie-uitnodigingen verlopen op basis van domeinregels uit het relatiedomein. De scheduler voert geen relatiebeslissingen uit, maar triggert een publieke handler van OefenHub.Relationships.
Technische aandachtspunten:
- alleen uitnodigingen met status
Pendingkomen in aanmerking; - uitnodigingen die al
Accepted,RejectedofExpiredzijn, worden overgeslagen; - verwerking is idempotent;
- verlopen verwijdert geen uitnodigingshistorie;
- gekoppelde systeemberichten worden niet hard verwijderd door deze job;
- audit/history blijft binnen het relatiedomein.
18.18 Testrun-opruiming
Docenttestruns mogen technisch tijdelijke runstructuren gebruiken, maar horen niet in reguliere leerlinggeschiedenis of permanente resultaten terecht te komen. Opruiming van achtergebleven testruns wordt periodiek uitgevoerd door OefenHub.Practice.
Technische aandachtspunten:
- alleen runs met testmarkering komen in aanmerking;
- afgeronde of verlaten testcontexten worden volgens bewaartermijn of technische criteria opgeschoond;
- reguliere leerlingruns worden nooit door deze job verwijderd;
- cleanup is idempotent;
- fouten bij één run blokkeren andere kandidaten niet;
- logging bevat geen volledige vraag-/antwoordpayload tenzij dit voor technische analyse expliciet en veilig noodzakelijk is.
18.19 Tijdelijke export- en PDF-cleanup
PDF-exportbestanden zijn tijdelijke output. Bij opnieuw downloaden wordt een PDF opnieuw opgebouwd uit databasegegevens, snapshots en modulepresentatie, zoals verder uitgewerkt in 16 PDF-export met QuestPDF.
Opruiming van tijdelijke bestanden wordt als technische onderhoudsjob ingericht.
| Aspect | Regel |
|---|---|
| Brondata | Database en snapshots blijven bron; tijdelijke bestanden niet. |
| Tijdelijke opslag | OefenHub:Reporting:TempExportPath, buiten webroot. |
| Cleanupeligible | Standaard CreatedAtUtc + 24 uur. |
| Planning | Minimaal iedere 6 uur uitvoeren. |
| Hard maximum | Bestanden ouder dan 48 uur als cleanupachterstand loggen/monitoren. |
| Ontbrekend bestand | Beschouwen als reeds opgeschoond wanneer de databasecontext dat ondersteunt. |
| Fout bij verwijderen | Begrensd retrybaar wanneer het om tijdelijke technische fout gaat. |
| Gevoelige inhoud | Tijdelijke bestanden mogen niet langer blijven staan dan technisch nodig. |
| Logging | Bestandsnaam of technische referentie loggen, geen volledige exportinhoud. |
18.20 Scheduling-configuratie
Schedulingconfiguratie wordt expliciet per jobtype vastgelegd. De configuratie mag in code, appsettings of beheerbare technische configuratie worden ondergebracht afhankelijk van de aard van de job.
| Configuratie | Voorbeeld |
|---|---|
| Jobtype | Support.ProcessExpiredReopenDeadlines |
| Interval | Elke 12 uur |
| Retrybeleid | Maximaal 3 pogingen met oplopende wachttijd |
| Timeout | Maximale uitvoeringstijd per poging |
| Concurrency | Eén instance tegelijk voor dezelfde jobfamilie indien nodig |
| Dashboardzichtbaarheid | Intern zichtbaar |
| Payloadtype | Naam van het verwachte payloadmodel |
| Handlercontract | Publiek contract van eigenaarmodule |
Functionele waardes zoals bewaartermijnen of heropentermijnen blijven domeinconfiguratie of functionele instellingen. Schedulingconfiguratie bepaalt wanneer en hoe verwerking draait, niet wat de domeinregel inhoudelijk betekent.
18.21 Foutafhandeling
Foutafhandeling bestaat uit drie lagen.
| Laag | Verantwoordelijkheid |
|---|---|
| Domeinhandler | Geeft domeinresultaat terug, valideert actuele context en voorkomt corrupte mutaties. |
| Scheduling | Registreert poging, fout, retry of failed-status. |
| Logging/monitoring | Maakt technische analyse en beheerherstel mogelijk. |
Een fout bij één kandidaat in een batch mag de verwerking van andere kandidaten niet blokkeren wanneer de kandidaten onafhankelijk verwerkt kunnen worden. De job moet dan een gedeeltelijk resultaat kunnen rapporteren.
Voorbeeld batchresultaat:
Found: 124
Processed: 121
Skipped: 2
Failed: 1
CorrelationId: 9b3d...
Bij definitieve fout wordt de job of kandidaat niet stilzwijgend genegeerd. Er moet een traceerbare failed-status of foutlog aanwezig zijn.
18.22 Concurrency en locking
Periodieke jobs kunnen overlappen door trage uitvoering, herstart of meerdere applicatie-instances. Daarom moet per jobtype worden bepaald of gelijktijdige uitvoering toegestaan is.
| Scenario | Beleid |
|---|---|
| Eén globale verwerking tegelijk | Gebruik TickerQ-locking of database-locking per jobtype. |
| Parallel per kandidaat toegestaan | Gebruik kandidaatniveau-locking of idempotente verwerking. |
| Zelfde record kan door gebruiker wijzigen | Hercontroleer actuele status vlak voor mutatie. |
| Job opnieuw gestart na crash | Gebruik idempotentie en statuscriteria om dubbele verwerking te voorkomen. |
Een job mag niet vertrouwen op gegevens die alleen aan het begin van de uitvoering zijn gelezen wanneer de domeinstatus intussen gewijzigd kan zijn. Vlak voor mutatie voert de domeinhandler opnieuw de relevante status- en autorisatiecontrole uit.
18.23 Relatie met readmodels en badges
Readmodels, tellers en badges zijn vaak geschikt voor achtergrondverwerking of retrybare naverwerking, mits zij geen functionele bron van waarheid zijn. Details over readmodels en materialisatie staan in 17 Readmodels, tellers, badges, caching en materialisatie.
Technische regels:
- readmodelverversing mag retrybaar zijn wanneer het readmodel herbouwbaar is;
- badge-updates mogen achterlopen wanneer de onderliggende bronstatus correct is;
- zichtbare tellerupdates mogen geen autorisatiebron worden;
- falende readmodeljobs moeten zichtbaar zijn voor technische analyse;
- bij twijfel moet het scherm kunnen terugvallen op bronqueries of een veilige lege/vertragingstoestand.
18.24 Relatie met logging, monitoring en beheer
Jobuitvoering raakt direct aan technische foutafhandeling en monitoring. De verdere logginguitwerking staat in 19 Logging, audit, securitylogging en technische foutafhandeling. Operationele inrichting, backup, restore en interne beheerbaarheid staan in 21 Beheerbeleid, monitoring, backup, restore en operatie.
Voor jobs gelden minimaal deze operationele verwachtingen:
- failed jobs zijn intern zichtbaar;
- langdurig falende jobtypes veroorzaken een beheer- of monitoringalert;
- retryloops zijn begrensd;
- technische foutinformatie is voldoende voor analyse;
- gevoelige payloads worden niet onnodig getoond of gelogd;
- handmatige herstelacties zijn herleidbaar.
18.25 Teststrategie voor jobs
Jobs worden niet uitsluitend via end-to-endtests gevalideerd. Testdekking wordt per laag ingericht.
| Testtype | Doel |
|---|---|
| Unit tests domeinhandler | Domeinregels, idempotentie en foutclassificatie testen. |
| Scheduling integration tests | Jobregistratie, payloadbinding en handlerresolving testen. |
| Retry tests | Pogingenteller, backoff en failed-status testen. |
| Concurrency tests | Dubbele uitvoering en lockinggedrag testen waar relevant. |
| Architecture tests | Voorkomen dat Scheduling rechtstreeks domeinentities of DbContexts van andere modules gebruikt. |
| Smoke tests | Controleren dat periodieke jobs na deployment geregistreerd zijn. |
De algemene teststrategie staat in 23 Teststrategie, acceptatieherleidbaarheid en kwaliteitsgrenzen.
18.26 Implementatieverificaties
Bij implementatie van TickerQ en scheduling moeten minimaal de volgende punten concreet worden gecontroleerd:
- of TickerQ-tabellen onder schema
schedulinggeplaatst kunnen worden; - hoe TickerQ migration/persistence wordt ingericht naast modulemigrations;
- hoe de TickerQ-webinterface intern wordt afgeschermd;
- dat retrybeleid per jobtype conform de vastgelegde defaultmatrix wordt geconfigureerd;
- hoe handmatige retry of annulering wordt gelogd;
- hoe jobpayloads worden gevalideerd en beperkt;
- hoe correlation-id wordt doorgegeven naar domeinhandlers;
- hoe meerdere applicatie-instances jobconcurrency voorkomen of veilig afhandelen;
- hoe failed jobs operationeel zichtbaar worden;
- hoe backup/restore omgaat met geplande en reeds uitgevoerde jobs.
Deze punten zijn technische implementatiecontroles binnen het Technisch Ontwerp. Wanneer één van deze controles leidt tot een structurele ontwerpwijziging, moet het relevante hoofdstuk van het Technisch Ontwerp en zo nodig database-informatie worden bijgewerkt.