IAM HR Sync — Externe Opslag Transfer
Architectuur
De externe opslag transfer is een zelfstandig subsysteem naast de sync pipeline. Het exporteert gefinaliseerde audit log batches nachtelijks naar een externe opslaglocatie (blob storage of compatibel). Het is losstaand van de sync pipeline zelf: een mislukte transfer blokkeert nooit een sync run, en een mislukte sync blokkeert nooit een transfer.
Het systeem bestaat uit twee Hangfire background jobs in API/Src/AutoMate.API/Features/IAM/Jobs/Storage/IamEmployeeAuditLogStorageJobs.cs:
Job | Rol | Schedule |
|---|---|---|
| Beheert nachtelijke recurring jobs per tenant | Dagelijks om 00:00 UTC (maakt/actualiseert per-tenant jobs om 02:00 lokale tijd) |
| Voert de daadwerkelijke overdracht uit | Nachtelijk om 02:00 in de tijdzone van de tenant |
Waarom een aparte dispatcher?
De transfer draait op tenant-tijdzone, wat betekent dat elke tenant een eigen Hangfire recurring job nodig heeft met een eigen CRON expression. De dispatcher is verantwoordelijk voor het levenscyclusbeheer van die recurring jobs:
Registreert een nieuwe recurring job wanneer een tenant
ExternalStorageEnabledzet optrueVerwijdert de recurring job wanneer een tenant externe opslag uitschakelt (cleanup van stale jobs)
Dit patroon houdt de Hangfire job store consistent met de actuele tenant configuratie, zonder handmatige tussenkomst.
Job 1: Dispatcher
Job: IamEmployeeAuditLogStorageDispatcherJob
Verantwoordelijkheden:
Loop over alle tenants waarvoor
ExternalStorageEnabled = trueisRegistreer een Hangfire recurring job voor
IamEmployeeAuditLogStorageTransferJobmet CRON0 2 * * *in de timezone van de tenant, indien die nog niet bestaat of verouderd isVerwijder bestaande recurring jobs voor tenants die externe opslag inmiddels hebben uitgeschakeld
Idempotentie: De dispatcher kan vaker draaien dan noodzakelijk; dubbele registraties worden door Hangfire onderdrukt als de CRON en job-key al bestaan.
Job 2: Transfer
Job: IamEmployeeAuditLogStorageTransferJob
Hangfire attributes:
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Fail)][DisableConcurrentExecution(timeoutInSeconds: 900)]
Injectie via Hangfire: PerformContext wordt geïnjecteerd door Hangfire (niet geserialiseerd) en levert de huidige RetryCount.
Volledige flow
Stap-voor-stap breakdown
Stap 1 — Tenant context
Elke job-invocatie ontvangt de tenantId als parameter. Het instellen van de tenant context zorgt dat alle downstream services (repository, email, storage) correct scopten op de juiste tenant.
Stap 2 — Configuratie laden
De repository voert een enkele SELECT TOP 1 uit op IAM_Employee_Sync_Settings:
Stap 3 — Silent-skip guards
Twee condities leiden tot een stille skip (log op info-niveau, geen exception, geen notificatie):
Conditie | Reden |
|---|---|
| Sync is door de tenant of admin uitgeschakeld; er is geen actuele data om over te dragen |
| Race condition: de dispatcher had al een recurring job gepland, maar externe opslag is sindsdien uitgeschakeld |
Een stille skip is bewust gekozen boven een exception. Een exception zou Hangfire aanzetten tot herproberen, wat zinloos is wanneer de conditie bedoeld uitgeschakeld is.
Stap 4 — SystemEmails parsen
SystemEmails is een comma-separated string opgeslagen in IAM_Employee_Sync_Settings.SystemEmails. Ontbrekende of lege kolom resulteert in een lege lijst; notificaties worden dan enkel gelogd maar niet verstuurd.
Stap 5 — Connection precheck
Voordat data opgehaald wordt, wordt de opslagverbinding getest. Bij een mislukte precheck:
Log op error-niveau
Verstuur
AuditLogStorageConnectionFailedemail (best-effort — emailfouten worden gecatcht en gelogd als warning)Return zonder exception
Waarom geen Hangfire retry bij connection failure?
Een verbindingsfout wijst op een configuratieprobleem (verkeerde credentials, endpoint niet beschikbaar) dat niet binnen 15 minuten vanzelf herstelt. Automatisch herproberen zou leiden tot herhaalde mislukte pogingen en noise in het dashboard. De operateur ontvangt een e-mailnotificatie en kan het probleem oplossen; de volgende nacht hervatten de transfers automatisch.
Stap 6 — Batches ophalen
Alleen gefinaliseerde batches worden opgehaald — batches die volledig zijn afgerond na het laatste watermark (LastTransferredAt). Lopende of gefaalde sync batches zijn uitgesloten.
Stap 7 — Uploaden per batch
Voor elke batch:
Het pad yyyy-MM-dd/HH-mm-ss_iam-sync-audit-log.json groepeert bestanden per dag, zodat navigatie in de opslaglocatie intuïtief is.
Stap 8 — Watermark update
Het watermark wordt uitsluitend bijgewerkt nadat alle batches succesvol zijn geüpload. Als er geen nieuwe batches zijn, wordt het watermark niet aangepast — de volgende run gebruikt hetzelfde startpunt. Bij een gedeeltelijke mislukking blijft het watermark eveneens op de vorige waarde staan, zodat de volgende run alle niet-overgedragen batches opnieuw oppakt. Dit garandeert at-least-once delivery zonder gaten in de exporthistorie.
Retry-gedrag en notificaties
Upload failures
MaxUploadAttempts = 3 is gelijk aan het Attempts-getal in [AutomaticRetry]. De e-mail wordt dus precies één keer verstuurd: bij de laatste uitgeputte poging.
Waarom alleen op de laatste poging?
Tijdelijke opslagfouten (netwerktimeout, throttling) herstellen gewoonlijk binnen enkele minuten. Een e-mail bij elke poging zou ruis creëren. Door te wachten op de definitief mislukte poging ontvangt de operateur één actionable melding.
Notificatiematrix
Situatie | Email verstuurd? | Hangfire retry? |
|---|---|---|
| Nee | Nee |
| Nee | Nee |
Connection precheck mislukt | Ja — | Nee |
Upload mislukt, retries resterend | Nee | Ja |
Upload mislukt, retries uitgeput | Ja — | Nee (Fail) |
Email templates
Template | Omschrijving |
|---|---|
| Verbinding naar externe opslag kon niet worden geverifieerd |
| Upload van audit log naar externe opslag is definitief mislukt na alle herpogingen |
E-mails worden verstuurd via IEmailSender.SendEmailAsync (uit AutoMate.API.Plugins.Email). Emailfouten worden gecatcht, gelogd als warning, en blokkeren nooit de rethrow die Hangfire nodig heeft voor retry/fail-afhandeling.
Repository: GetExternalStorageConfigAsync
De methode op IIamEmployeeSyncSettingsRepository retourneert een 5-tuple:
De uitbreiding van de oorspronkelijke 3-tuple met SystemEmails en SyncActive is backward-compatible: de SQL query leest beide kolommen uit in dezelfde SELECT TOP 1, zonder bestaande callers te wijzigen.
Monitoring
Via Hangfire Dashboard
Zoek op job name IamEmployeeAuditLogStorageTransferJob of display name in het Hangfire dashboard:
Succeeded: batch correct overgezet, watermark bijgewerkt
Failed: alle retries uitgeput — check error message en raadpleeg de ontvangen e-mailmelding
Retries: tijdelijke uploadfout — wacht op volgende poging
Watermark controleren
Een ExternalStorageLastTransferredAt die meerdere dagen achterloopt duidt op een structureel probleem.