15 KiB
Feature Specification: Backend Architecture Pivot
Feature Branch: 005-backend-arch-pivot
Created: 2025-12-09
Status: Draft
Input: "Backend Architecture Pivot (n8n Removal & BullMQ Implementation) - Remove n8n legacy code, implement BullMQ job queue with Redis, port sync logic to TypeScript worker"
Overview
Migration von einer Low-Code-Backend-Architektur (n8n) zu einem Code-First-Backend mit BullMQ Job Queue und TypeScript Worker. Die komplexe Microsoft Graph Synchronisations-Logik wird direkt in TypeScript implementiert, um Wartbarkeit, Testbarkeit und AI-gestütztes Refactoring zu maximieren.
User Scenarios & Testing (mandatory)
User Story 1 - Manual Policy Sync via Queue (Priority: P1)
Als Intune-Admin möchte ich auf "Sync Now" klicken und erwarten, dass die Synchronisation asynchron in einem Worker-Prozess ausgeführt wird, damit die UI nicht blockiert und ich sofort weiterarbeiten kann.
Why this priority: Core-Funktionalität - ohne funktionierenden Sync ist das gesamte Feature unbrauchbar. Queue-basierte Architektur ist Grundlage für spätere Scheduled Syncs.
Independent Test: Click "Sync Now", check Redis for job, observe worker logs, verify database updates.
Acceptance Scenarios:
- Given der Admin ist auf
/searcheingeloggt, When er auf "Sync Now" klickt, Then wird ein Job in die Redis Queue eingestellt (keine Wartezeit für den User). - Given ein Sync-Job wurde erstellt, When der Worker-Prozess läuft, Then nimmt er den Job aus der Queue und beginnt die Synchronisation.
- Given der Worker führt einen Sync aus, When die Synchronisation erfolgreich abgeschlossen ist, Then werden alle Policy Settings in der Datenbank aktualisiert (Insert oder Update via
onConflictDoUpdate). - Given der Worker synchronisiert Policies, When ein Fehler auftritt (z.B. Graph API Timeout), Then wird der Job in einen "failed" State versetzt und der Fehler wird geloggt (kein Silent Fail).
- Given der Admin hat mehrere Sync-Jobs gestartet, When der Worker mehrere Jobs in der Queue findet, Then werden sie sequenziell abgearbeitet (keine parallelen Syncs pro Tenant).
User Story 2 - Microsoft Graph Data Fetching (Priority: P1)
Als System möchte ich alle relevanten Policy-Typen von Microsoft Graph API abrufen können (Device Configurations, Compliance Policies, Configuration Policies, Intents), damit alle Intune-Settings analysierbar sind.
Why this priority: Datenbeschaffung ist essentiell - ohne vollständigen Fetch fehlen Policies in der Analyse.
Independent Test: Run worker with test tenant, verify all policy types are fetched, check pagination handling.
Acceptance Scenarios:
- Given der Worker startet einen Sync, When er ein Access Token anfordert, Then nutzt er den Azure AD Client Credentials Flow mit
AZURE_AD_CLIENT_IDundAZURE_AD_CLIENT_SECRET. - Given der Worker hat ein gültiges Token, When er Policies abruft, Then fetcht er alle relevanten Endpoints:
/deviceManagement/deviceConfigurations/deviceManagement/deviceCompliancePolicies/deviceManagement/configurationPolicies/deviceManagement/intents
- Given eine Graph API Response hat
@odata.nextLink, When der Worker die Response verarbeitet, Then folgt er dem Link und lädt alle Seiten bis keinenextLinkmehr vorhanden ist. - Given ein Policy Object wird von Graph zurückgegeben, When der Worker es parst, Then extrahiert er
id,displayName,@odata.type,lastModifiedDateTimeund Policy-spezifische Settings. - Given der Graph API Call schlägt fehl (401, 429, 500), When der Fehler auftritt, Then wird ein Retry mit Exponential Backoff durchgeführt (max 3 Versuche).
User Story 3 - Deep Flattening & Data Transformation (Priority: P1)
Als System möchte ich komplexe verschachtelte Policy-Objekte in flache Key-Value-Paare transformieren können, damit sie in der policy_settings Tabelle gespeichert und durchsucht werden können.
Why this priority: Core Transformation Logic - ohne Flattening können verschachtelte Settings nicht analysiert werden.
Independent Test: Run parser with sample Graph responses, verify flattened output matches expected structure.
Acceptance Scenarios:
- Given der Worker hat Policy-Daten von Graph erhalten, When er ein Settings Catalog Policy verarbeitet (
#microsoft.graph.deviceManagementConfigurationPolicy), Then iteriert er übersettings[]und extrahiertsettingDefinitionIdundvalue. - Given ein Policy enthält verschachtelte Objekte (z.B.
value.simple.valueodervalue.children[]), When der Flattening-Algorithmus läuft, Then wird jede verschachtelte Ebene mit Dot-Notation als Key dargestellt (z.B.wifi.ssid.value). - Given der Worker verarbeitet ein OMA-URI Policy, When er
omaSettings[]findet, Then extrahiert eromaUrials Setting Name undvalueals Setting Value. - Given ein Setting Key enthält technische Bezeichner (z.B.
device_vendor_msft_policy_config_wifi_allowwifihotspotreporting), When der Humanizer läuft, Then werden Keys in lesbare Form umgewandelt (z.B.Allow WiFi Hotspot Reporting). - Given ein Policy hat keine Settings (leeres Array), When der Worker es verarbeitet, Then wird trotzdem ein Eintrag erstellt mit
settingName: "(No settings configured)"(damit Policy in UI sichtbar ist).
User Story 4 - Legacy Code Removal (Priority: P1)
Als Entwickler möchte ich alle n8n-spezifischen Artefakte entfernen können, damit der Code sauber und wartbar bleibt.
Why this priority: Technical Debt Reduction - alte Bridge-APIs verursachen Confusion und Maintenance-Overhead.
Independent Test: Search codebase for n8n references, verify all removed, check env validation.
Acceptance Scenarios:
- Given der Code wird überprüft, When nach
POLICY_API_SECRETgesucht wird, Then existieren keine Referenzen mehr (weder in.env, noch inlib/env.mjs, noch in Code). - Given der Code wird überprüft, When nach
N8N_SYNC_WEBHOOK_URLgesucht wird, Then existieren keine Referenzen mehr. - Given das Routing wird analysiert, When nach
/api/policy-settings/route.tsgesucht wird, Then existiert die Datei nicht mehr (gelöscht). - Given das Routing wird analysiert, When nach
/api/admin/tenants/route.tsgesucht wird, Then existiert die Datei nicht mehr (gelöscht). - Given ein Entwickler startet die App, When die Umgebungsvariablen validiert werden, Then werden
POLICY_API_SECRETundN8N_SYNC_WEBHOOK_URLnicht mehr als erforderlich markiert.
Edge Cases
- Was passiert wenn Redis nicht erreichbar ist beim Job-Erstellen? → Fehler werfen mit User-Feedback "Sync service unavailable".
- Was passiert wenn der Worker abstürzt während eines Jobs? → BullMQ Recovery: Job bleibt in "active" state und wird nach Timeout in "failed" verschoben.
- Wie gehen wir mit Rate Limiting von Microsoft Graph um? → Exponential Backoff + Retry (max 3x), dann Job als "failed" markieren mit Retry-Option.
- Was passiert bei parallelen Sync-Requests für denselben Tenant? → Queue stellt sicher, dass Jobs sequenziell abgearbeitet werden (kein Concurrency Issue).
- Wie werden transiente Netzwerkfehler behandelt? → Retry-Logik mit Backoff, nur permanente Fehler (401, 403) führen zu sofortigem Fail.
- Was passiert mit bestehenden Policy Settings während eines Syncs? →
onConflictDoUpdateupdated bestehende Einträge basierend auf(tenantId, graphPolicyId, settingName)Constraint.
Requirements (mandatory)
Functional Requirements
Infrastructure & Queue
- FR-001: System MUSS BullMQ als Job Queue Library verwenden mit Redis als Backend.
- FR-002: System MUSS eine wiederverwendbare Redis Connection in
lib/queue/redis.tsbereitstellen. - FR-003: System MUSS einen Worker-Prozess in
worker/index.tsimplementieren, der auf der Queueintune-sync-queuelauscht. - FR-004: System MUSS Worker-Prozess als separates npm Script bereitstellen (
worker:start). - FR-005: System MUSS
REDIS_URLals Environment Variable validieren.
Authentication & Graph API
- FR-006: System MUSS Access Tokens via Azure AD Client Credentials Flow holen (
@azure/identityoderfetch). - FR-007: System MUSS folgende Graph API Endpoints fetchen:
/deviceManagement/deviceConfigurations/deviceManagement/deviceCompliancePolicies/deviceManagement/configurationPolicies/deviceManagement/intents
- FR-008: System MUSS Pagination via
@odata.nextLinkvollständig abarbeiten (alle Seiten laden). - FR-009: System MUSS Graph API Fehler (401, 429, 500+) mit Exponential Backoff Retry behandeln (max 3 Versuche).
Data Processing & Transformation
- FR-010: System MUSS Settings Catalog Policies parsen (
settings[]Array → flache Key-Value Paare). - FR-011: System MUSS OMA-URI Policies parsen (
omaSettings[]→omaUrials Key,valueals Value). - FR-012: System MUSS Deep Flattening für verschachtelte Objekte implementieren (Dot-Notation für Pfade).
- FR-013: System MUSS technische Setting Keys humanisieren (z.B.
device_vendor_msft_policy_config_wifi→WiFi). - FR-014: System MUSS Policy Typ Detection implementieren (Settings Catalog, OMA-URI, Compliance, etc.).
- FR-015: System MUSS leere Policies mit Placeholder-Setting speichern (
settingName: "(No settings configured)").
Database Persistence
- FR-016: System MUSS Drizzle ORM für alle DB-Operationen verwenden.
- FR-017: System MUSS
onConflictDoUpdatefür Upsert-Logik nutzen (Constraint:tenantId + graphPolicyId + settingName). - FR-018: System MUSS folgende Felder pro Setting speichern:
tenantId,graphPolicyId,policyName,policyType,settingName,settingValue,settingValueType,lastSyncedAt
- FR-019: System MUSS
lastSyncedAtTimestamp bei jedem Sync aktualisieren.
Frontend Integration
- FR-020: System MUSS Server Action
triggerPolicySyncinlib/actions/policySettings.tsanpassen (n8n Webhook → BullMQ Job). - FR-021: System MUSS Job-ID an Frontend zurückgeben für späteres Status-Tracking (optional für MVP, siehe FR-022).
- FR-022: System KANN (optional) Job-Status-Polling-Endpoint bereitstellen (
/api/sync-status/[jobId]).
Legacy Cleanup
- FR-023: System MUSS File
app/api/policy-settings/route.tslöschen (n8n Ingestion API). - FR-024: System MUSS File
app/api/admin/tenants/route.tslöschen (n8n Polling API). - FR-025: System MUSS
POLICY_API_SECRETaus.env,lib/env.mjsund allen Code-Referenzen entfernen. - FR-026: System MUSS
N8N_SYNC_WEBHOOK_URLaus.env,lib/env.mjsund allen Code-Referenzen entfernen.
Key Entities
Neue Strukturen (keine DB-Schema-Änderungen):
-
SyncJobPayload: BullMQ Job Data
tenantId: stringuserId: string (optional, für Audit)triggeredAt: Date
-
GraphPolicyResponse: TypeScript Interface für Graph API Response
id: stringdisplayName: string@odata.type: stringlastModifiedDateTime: stringsettings?: array (Settings Catalog)omaSettings?: array (OMA-URI)- (weitere Policy-spezifische Felder)
-
FlattenedSetting: Internes Transform-Result
settingName: stringsettingValue: stringsettingValueType: stringpath: string (Dot-Notation Pfad im Original-Objekt)
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: User erhält sofortige Bestätigung nach Click auf "Sync Now" (<200ms Response Zeit, kein Warten auf Sync-Completion).
- SC-002: Sync für einen Tenant mit 50 Policies ist innerhalb von 30 Sekunden abgeschlossen.
- SC-003: System lädt alle verfügbaren Policies vollständig (auch bei >100 Policies mit mehreren Datenseiten).
- SC-004: Mindestens 95% aller Policy Settings werden korrekt extrahiert und gespeichert (validiert mit repräsentativen Sample-Daten).
- SC-005: Bei temporären Fehlern (z.B. Service-Überlastung) erfolgt automatische Wiederholung (keine manuellen Eingriffe nötig).
- SC-006: Alte Bridge-Komponenten sind vollständig entfernt (keine toten Code-Pfade oder ungenutzten APIs).
- SC-007: Sync-Prozess läuft stabil über längere Zeiträume (1+ Stunde mit 10+ Sync-Operationen ohne Abstürze).
- SC-008: Re-Sync aktualisiert bestehende Daten korrekt ohne Duplikate oder Datenverluste.
Assumptions
- System nutzt asynchrone Job-Verarbeitung mit Queue-basierter Architektur für Skalierbarkeit.
- TenantPilot hat bereits Azure AD Multi-Tenant Authentication konfiguriert (Client Credentials verfügbar).
- Die bestehende
policy_settingsDatenbank-Tabelle hat bereits einen UNIQUE Constraint auf(tenantId, graphPolicyId, settingName). - Die Flattening-Logik aus der bisherigen n8n-Implementation ist dokumentiert oder nachvollziehbar.
- Sync-Prozess wird in Production als persistenter Background-Service betrieben (nicht nur bei Bedarf gestartet).
- Redis oder vergleichbarer In-Memory Store ist verfügbar für Job Queue Management.
Nicht-Ziele (Out of Scope)
- Kein automatischer Scheduled Sync (zeitbasierte Trigger) in diesem Feature - bleibt manuelle Auslösung.
- Keine Web-UI für Job-Management oder Queue-Monitoring.
- Keine Live-Progress-Updates im Frontend während Sync läuft (kein Echtzeit-Status).
- Keine parallele Verarbeitung mehrerer Tenants gleichzeitig (sequenzielle Abarbeitung).
- Keine erweiterten Retry-Strategien oder Dead Letter Queues in MVP.
- Kein Policy Change Detection oder Diff-Berechnung (nur vollständiger Sync + Update bestehender Daten).
Technical Notes
Note: Detaillierte Implementierungs-Details (Code-Beispiele, API-Calls, Architektur-Patterns) werden in einem separaten Technical Design Document oder im Planning-Phase dokumentiert. Diese Spec fokussiert sich auf das WAS und WARUM, nicht auf das WIE.
High-Level Architecture Overview
Queue-Based Sync Architecture:
- Asynchrone Job-Verarbeitung für nicht-blockierende User Experience
- Worker-Prozess als separater Service für Sync-Operationen
- Persistente Job-Queue für Reliability und Retry-Fähigkeit
Data Flow:
- User triggers sync → Job wird in Queue eingestellt
- Worker nimmt Job aus Queue → Authentifiziert sich bei Microsoft
- Worker fetcht Policy-Daten → Transformiert & flacht verschachtelte Strukturen ab
- Worker speichert Daten → Upsert in Datenbank mit Conflict Resolution
Migration Strategy:
- Phase 1: Neue Infrastruktur aufbauen (Queue, Worker)
- Phase 2: Sync-Logik portieren (Auth, Fetch, Transform, Persist)
- Phase 3: Frontend auf neue Architektur umstellen
- Phase 4: Alte n8n-Komponenten entfernen
- Phase 5: End-to-End Validierung mit Production-Daten
Dependencies
- Job Queue System (z.B. BullMQ, Bee-Queue, oder vergleichbar)
- In-Memory Data Store (z.B. Redis, KeyDB, oder vergleichbar)
- Microsoft Graph API Client Library (z.B. @azure/identity oder vergleichbar)
- TypeScript Runtime für Worker-Prozess (z.B. tsx, ts-node, oder vergleichbar)