237 lines
15 KiB
Markdown
237 lines
15 KiB
Markdown
# 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**:
|
|
|
|
1. **Given** der Admin ist auf `/search` eingeloggt, **When** er auf "Sync Now" klickt, **Then** wird ein Job in die Redis Queue eingestellt (keine Wartezeit für den User).
|
|
2. **Given** ein Sync-Job wurde erstellt, **When** der Worker-Prozess läuft, **Then** nimmt er den Job aus der Queue und beginnt die Synchronisation.
|
|
3. **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`).
|
|
4. **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).
|
|
5. **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**:
|
|
|
|
1. **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_ID` und `AZURE_AD_CLIENT_SECRET`.
|
|
2. **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`
|
|
3. **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 keine `nextLink` mehr vorhanden ist.
|
|
4. **Given** ein Policy Object wird von Graph zurückgegeben, **When** der Worker es parst, **Then** extrahiert er `id`, `displayName`, `@odata.type`, `lastModifiedDateTime` und Policy-spezifische Settings.
|
|
5. **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**:
|
|
|
|
1. **Given** der Worker hat Policy-Daten von Graph erhalten, **When** er ein Settings Catalog Policy verarbeitet (`#microsoft.graph.deviceManagementConfigurationPolicy`), **Then** iteriert er über `settings[]` und extrahiert `settingDefinitionId` und `value`.
|
|
2. **Given** ein Policy enthält verschachtelte Objekte (z.B. `value.simple.value` oder `value.children[]`), **When** der Flattening-Algorithmus läuft, **Then** wird jede verschachtelte Ebene mit Dot-Notation als Key dargestellt (z.B. `wifi.ssid.value`).
|
|
3. **Given** der Worker verarbeitet ein OMA-URI Policy, **When** er `omaSettings[]` findet, **Then** extrahiert er `omaUri` als Setting Name und `value` als Setting Value.
|
|
4. **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`).
|
|
5. **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**:
|
|
|
|
1. **Given** der Code wird überprüft, **When** nach `POLICY_API_SECRET` gesucht wird, **Then** existieren keine Referenzen mehr (weder in `.env`, noch in `lib/env.mjs`, noch in Code).
|
|
2. **Given** der Code wird überprüft, **When** nach `N8N_SYNC_WEBHOOK_URL` gesucht wird, **Then** existieren keine Referenzen mehr.
|
|
3. **Given** das Routing wird analysiert, **When** nach `/api/policy-settings/route.ts` gesucht wird, **Then** existiert die Datei nicht mehr (gelöscht).
|
|
4. **Given** das Routing wird analysiert, **When** nach `/api/admin/tenants/route.ts` gesucht wird, **Then** existiert die Datei nicht mehr (gelöscht).
|
|
5. **Given** ein Entwickler startet die App, **When** die Umgebungsvariablen validiert werden, **Then** werden `POLICY_API_SECRET` und `N8N_SYNC_WEBHOOK_URL` nicht 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? → `onConflictDoUpdate` updated 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.ts` bereitstellen.
|
|
- **FR-003**: System MUSS einen Worker-Prozess in `worker/index.ts` implementieren, der auf der Queue `intune-sync-queue` lauscht.
|
|
- **FR-004**: System MUSS Worker-Prozess als separates npm Script bereitstellen (`worker:start`).
|
|
- **FR-005**: System MUSS `REDIS_URL` als Environment Variable validieren.
|
|
|
|
#### Authentication & Graph API
|
|
|
|
- **FR-006**: System MUSS Access Tokens via Azure AD Client Credentials Flow holen (`@azure/identity` oder `fetch`).
|
|
- **FR-007**: System MUSS folgende Graph API Endpoints fetchen:
|
|
- `/deviceManagement/deviceConfigurations`
|
|
- `/deviceManagement/deviceCompliancePolicies`
|
|
- `/deviceManagement/configurationPolicies`
|
|
- `/deviceManagement/intents`
|
|
- **FR-008**: System MUSS Pagination via `@odata.nextLink` vollstä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[]` → `omaUri` als Key, `value` als 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 `onConflictDoUpdate` fü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 `lastSyncedAt` Timestamp bei jedem Sync aktualisieren.
|
|
|
|
#### Frontend Integration
|
|
|
|
- **FR-020**: System MUSS Server Action `triggerPolicySync` in `lib/actions/policySettings.ts` anpassen (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.ts` löschen (n8n Ingestion API).
|
|
- **FR-024**: System MUSS File `app/api/admin/tenants/route.ts` löschen (n8n Polling API).
|
|
- **FR-025**: System MUSS `POLICY_API_SECRET` aus `.env`, `lib/env.mjs` und allen Code-Referenzen entfernen.
|
|
- **FR-026**: System MUSS `N8N_SYNC_WEBHOOK_URL` aus `.env`, `lib/env.mjs` und allen Code-Referenzen entfernen.
|
|
|
|
### Key Entities
|
|
|
|
Neue Strukturen (keine DB-Schema-Änderungen):
|
|
|
|
- **SyncJobPayload**: BullMQ Job Data
|
|
- `tenantId`: string
|
|
- `userId`: string (optional, für Audit)
|
|
- `triggeredAt`: Date
|
|
|
|
- **GraphPolicyResponse**: TypeScript Interface für Graph API Response
|
|
- `id`: string
|
|
- `displayName`: string
|
|
- `@odata.type`: string
|
|
- `lastModifiedDateTime`: string
|
|
- `settings?`: array (Settings Catalog)
|
|
- `omaSettings?`: array (OMA-URI)
|
|
- (weitere Policy-spezifische Felder)
|
|
|
|
- **FlattenedSetting**: Internes Transform-Result
|
|
- `settingName`: string
|
|
- `settingValue`: string
|
|
- `settingValueType`: string
|
|
- `path`: 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_settings` Datenbank-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**:
|
|
1. User triggers sync → Job wird in Queue eingestellt
|
|
2. Worker nimmt Job aus Queue → Authentifiziert sich bei Microsoft
|
|
3. Worker fetcht Policy-Daten → Transformiert & flacht verschachtelte Strukturen ab
|
|
4. 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)
|