diff --git a/DocumentOperator.API/PHASENPLAN.md b/DocumentOperator.API/PHASENPLAN.md new file mode 100644 index 0000000..7b2667b --- /dev/null +++ b/DocumentOperator.API/PHASENPLAN.md @@ -0,0 +1,463 @@ +# ?? DocumentOperator - Phasenplan (Feature-Driven Development) + +> **Stand:** 17.01.2025 | **Aktuell:** Feature 1 - ValidatePDF (Step 1.1 NEXT) | **Projektdauer:** 6 Wochen + +--- + +## ?? Übersicht + +| Woche | Features / Concerns | Status | Fortschritt | +|-------|---------------------|--------|-------------| +| **W1** | Feature 1: ValidatePDF | ?? In Progress | 50% (Infrastructure fertig, Application NEXT) | +| **W2** | Feature 2: ExtractAttachments | ? Geplant | 0% | +| **W2** | Feature 3: ApplyStamp | ? Geplant | 0% | +| **W3** | Feature 4: EmbedCertificate | ? Geplant | 0% | +| **W3** | Feature 5: ConcatenatePDFs (Async) | ? Geplant | 0% | +| **W4** | Multi-Tenancy (X-API-Key Header) | ? Geplant | 0% | +| **W5** | Health Checks + Polly + Logging | ? Geplant | 0% | +| **W6** | Production Deployment | ? Geplant | 0% | + +--- + +## ?? NEUE VORGEHENSWEISE + +**Was hat sich geändert?** + +? **Feature-by-Feature Development** statt Layer-by-Layer +- Jedes Feature wird KOMPLETT umgesetzt (Domain ? Infrastructure ? Application ? API ? Tests ? Swagger) +- Feature ist erst "DONE" wenn es im Swagger testbar ist +- Dann nächstes Feature + +? **Kleine Schritte** (1 Layer pro Step) +- Nach jedem Step: ROADMAP + PHASENPLAN aktualisieren +- Commit nach jedem Step +- Dann weiter + +? **Multi-Tenancy & Cross-Cutting Concerns später** +- Erst alle synchronen Features (1-4) +- Dann Multi-Tenancy für ALLE Endpoints +- Dann Health Checks, Polly, Logging + +--- + +## ?? DETAILLIERTER PLAN + +### WOCHE 1 - Feature 1: ValidatePDF | ?? In Progress - 50% + +**Ziel:** POST /api/v1/documents/validate Endpoint im Swagger testbar + +#### ? Step 1.0: Foundation (ABGESCHLOSSEN) +**Dauer:** ~2 Tage (bereits erledigt) + +**Was wurde erstellt:** +- ? Solution Structure (4 Projekte) +- ? Domain Layer (Exceptions, Enums, Value Objects) +- ? Infrastructure Layer (DevExpressPdfProcessor.ValidateAsync) +- ? Tests (DevExpressPdfProcessorTests.cs - 6 Tests) +- ? Build erfolgreich + +--- + +#### ?? Step 1.1: Application Layer (MediatR Setup + ValidatePDF Feature) - **NEXT** +**Dauer:** ~4 Stunden +**Status:** ? NEXT + +**Was wird erstellt:** +1. **MediatR Setup** + - `Application/DependencyInjection.cs` (Service Registration) + - `Application/Common/Behaviors/ValidationBehavior.cs` (FluentValidation Pipeline) + - `Application/Common/Behaviors/LoggingBehavior.cs` (Logging Pipeline) + +2. **ValidatePDF Feature (Vertical Slice)** + - `Application/Features/Documents/ValidatePdf/ValidatePdfQuery.cs` + - `Application/Features/Documents/ValidatePdf/ValidatePdfHandler.cs` + - `Application/Features/Documents/ValidatePdf/ValidatePdfValidator.cs` + +3. **DTOs** + - `Application/Common/DTOs/ValidatePdfRequest.cs` + - `Application/Common/DTOs/ValidatePdfResponse.cs` + +4. **Tests** + - `Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs` + +**Akzeptanzkriterien:** +- ? Build erfolgreich +- ? Tests grün (Handler Tests) +- ? MediatR Pipeline funktioniert (Validation + Logging) + +--- + +#### ? Step 1.2: API Layer (Endpoint + Exception Middleware) +**Dauer:** ~3 Stunden +**Status:** ? Pending + +**Was wird erstellt:** +1. **Exception Middleware** + - `API/Middleware/ExceptionHandlingMiddleware.cs` + - Exception ? HTTP Status Code Mapping (400, 404, 500) + - RFC 7807 Problem Details + +2. **Minimal API Endpoint** + - `API/Endpoints/v1/DocumentEndpoints.cs` + - POST /api/v1/documents/validate + +3. **Program.cs Updates** + - Exception Middleware registrieren + - DocumentEndpoints registrieren + - Application + Infrastructure Services registrieren + +4. **Integration Tests** + - `Tests/Integration/API/DocumentEndpointsTests.cs` + +**Akzeptanzkriterien:** +- ? Build erfolgreich +- ? Integration Tests grün +- ? Endpoint gibt korrekte HTTP Status Codes zurück + +--- + +#### ? Step 1.3: Swagger Dokumentation +**Dauer:** ~1 Stunde +**Status:** ? Pending + +**Was wird erstellt:** +1. **Swagger Configuration** + - `API/Configuration/SwaggerConfiguration.cs` + - XML Comments aktivieren + +2. **Endpoint Dokumentation** + - XML Comments für ValidatePdf Endpoint + +**Akzeptanzkriterien:** +- ? Swagger UI läuft unter `/swagger` +- ? Endpoint `/api/v1/documents/validate` ist sichtbar und testbar +- ? Request/Response Schemas dokumentiert + +--- + +#### ? Feature 1 ABGESCHLOSSEN! +**Gesamtdauer:** ~1 Tag + +**Ergebnis:** +- ? POST /api/v1/documents/validate im Swagger testbar +- ? Unit Tests + Integration Tests grün +- ? Clean Architecture eingehalten +- ? TDD angewendet + +--- + +### WOCHE 2 - Features 2 & 3 | ? Geplant - 0% + +#### Feature 2: ExtractAttachments (Synchron) +**Dauer:** ~1 Tag +**Status:** ? Pending + +**Endpoint:** POST /api/v1/documents/extract-attachments + +**Steps:** +- ? Step 2.1: Infrastructure Layer (DevExpressPdfProcessor.ExtractAttachmentsAsync) +- ? Step 2.2: Application Layer (ExtractAttachmentsCommand + Handler + Validator) +- ? Step 2.3: API Layer (Endpoint) +- ? Step 2.4: Swagger Dokumentation + +**Akzeptanzkriterien:** +- ? Endpoint im Swagger testbar +- ? Tests grün + +--- + +#### Feature 3: ApplyStamp (Synchron) +**Dauer:** ~1 Tag +**Status:** ? Pending + +**Endpoint:** POST /api/v1/documents/apply-stamp + +**Steps:** +- ? Step 3.1: Infrastructure Layer (DevExpressPdfProcessor.ApplyStampAsync) +- ? Step 3.2: Application Layer (ApplyStampCommand + Handler + Validator) +- ? Step 3.3: API Layer (Endpoint) +- ? Step 3.4: Swagger Dokumentation + +**Akzeptanzkriterien:** +- ? Endpoint im Swagger testbar +- ? Stamp wird korrekt angewendet +- ? Tests grün + +--- + +### WOCHE 3 - Features 4 & 5 | ? Geplant - 0% + +#### Feature 4: EmbedCertificate (Synchron) +**Dauer:** ~1 Tag +**Status:** ? Pending + +**Endpoint:** POST /api/v1/documents/embed-certificate + +**Steps:** +- ? Step 4.1: Infrastructure Layer (DevExpressPdfProcessor.EmbedCertificateAsync) +- ? Step 4.2: Application Layer (EmbedCertificateCommand + Handler + Validator) +- ? Step 4.3: API Layer (Endpoint) +- ? Step 4.4: Swagger Dokumentation + +**Akzeptanzkriterien:** +- ? Endpoint im Swagger testbar +- ? Zertifikat wird korrekt eingebettet +- ? Tests grün + +--- + +#### Feature 5: ConcatenatePDFs (Asynchron) +**Dauer:** ~2 Tage +**Status:** ? Pending + +**Endpoints:** +- POST /api/v1/documents/concatenate (Async, gibt JobId zurück) +- GET /api/v1/jobs/{jobId} (Job-Status abfragen) +- GET /api/v1/jobs/{jobId}/download (Ergebnis herunterladen) + +**Steps:** +- ? Step 5.1: Infrastructure Layer (In-Memory Queue + Background Worker) +- ? Step 5.2: Application Layer (SubmitConcatenateJobCommand + GetJobStatusQuery) +- ? Step 5.3: API Layer (Async Endpoints) +- ? Step 5.4: Swagger Dokumentation + +**Akzeptanzkriterien:** +- ? POST /concatenate gibt JobId zurück +- ? GET /jobs/{jobId} zeigt Status (Pending, Processing, Success, Failed) +- ? GET /jobs/{jobId}/download gibt PDF zurück +- ? Background Worker verarbeitet Jobs korrekt +- ? Tests grün + +--- + +### WOCHE 4 - Multi-Tenancy | ? Geplant - 0% + +**Ziel:** X-API-Key Header für ALLE Endpoints + +**Was wird gebaut:** +- EF Core + SQLite (Tenant-Datenbank) +- Redis Cache (API-Key Lookups - optional) +- TenantResolutionMiddleware (X-API-Key ? Tenant) +- BCrypt API-Key Hashing +- Admin API (Tenant CRUD) + +**Steps:** + +#### Step MT.1: EF Core Setup +**Dauer:** ~3 Stunden + +**Was wird erstellt:** +- `Infrastructure/Data/TenantDbContext.cs` +- `Infrastructure/Data/Entities/Tenant.cs` +- `Infrastructure/Data/Entities/TenantSettings.cs` +- EF Core Migration (InitialCreate) +- SQLite Database erstellen + +**Akzeptanzkriterien:** +- ? Datenbank erstellt +- ? Tenant-Tabelle existiert +- ? Build erfolgreich + +--- + +#### Step MT.2: TenantResolutionMiddleware +**Dauer:** ~2 Stunden + +**Was wird erstellt:** +- `API/Middleware/TenantResolutionMiddleware.cs` +- `Application/Common/Interfaces/ITenantContext.cs` +- `Infrastructure/Services/TenantContext.cs` + +**Akzeptanzkriterien:** +- ? X-API-Key Header wird gelesen +- ? Tenant aus DB geladen +- ? ITenantContext im Request Scope verfügbar +- ? Ungültiger API-Key ? HTTP 401 + +--- + +#### Step MT.3: Redis Cache Integration (Optional) +**Dauer:** ~1 Stunde + +**Was wird erstellt:** +- Redis Cache für API-Key Lookups +- TTL: 1 Stunde + +**Akzeptanzkriterien:** +- ? API-Key Lookup cached (weniger DB-Calls) +- ? Cache Invalidation funktioniert + +--- + +#### Step MT.4: Admin API (Tenant Management) +**Dauer:** ~2 Stunden + +**Was wird erstellt:** +- POST /api/v1/admin/tenants (Create Tenant) +- PUT /api/v1/admin/tenants/{id}/rotate-key (API-Key rotieren) +- PATCH /api/v1/admin/tenants/{id}/deactivate (Tenant deaktivieren) +- GET /api/v1/admin/tenants (Liste aller Tenants) + +**Akzeptanzkriterien:** +- ? Endpoints im Swagger testbar +- ? API-Key wird gehashed (BCrypt) +- ? Tests grün + +--- + +#### Step MT.5: Alle Endpoints mit X-API-Key absichern +**Dauer:** ~1 Stunde + +**Was wird geändert:** +- Alle Feature-Endpoints bekommen X-API-Key Header Requirement +- Swagger zeigt API-Key Security Scheme + +**Akzeptanzkriterien:** +- ? Alle Endpoints erfordern X-API-Key Header +- ? Swagger zeigt Security Scheme +- ? Tests aktualisiert (mit API-Key) + +--- + +### WOCHE 5 - Health Checks + Polly + Logging | ? Geplant - 0% + +#### Health Checks +**Dauer:** ~2 Stunden + +**Was wird gebaut:** +- `/health` Endpoint (Liveness/Readiness Probes) +- DevExpressPdfHealthCheck (Smoke Test) +- Database Health Check (SQLite) +- Redis Health Check (optional) + +**Akzeptanzkriterien:** +- ? /health gibt HTTP 200 wenn alles OK +- ? /health gibt HTTP 503 wenn DevExpress nicht funktioniert + +--- + +#### Polly Resilience +**Dauer:** ~3 Stunden + +**Was wird gebaut:** +- Retry Policy (3x mit Exponential Backoff) +- Circuit Breaker (nach 5 Fehlern 30s öffnen) +- Timeout Policy (30s max) + +**Akzeptanzkriterien:** +- ? DevExpress Calls werden mit Polly gewickelt +- ? Retry funktioniert bei Transient Errors +- ? Circuit Breaker öffnet bei vielen Fehlern + +--- + +#### Logging & Monitoring +**Dauer:** ~3 Stunden + +**Was wird gebaut:** +- CorrelationIdMiddleware (X-Correlation-ID Header) +- Seq Sink (Log-Browsing UI) +- File Logging (Production) +- LoggingBehavior erweitert (Performance-Tracking) + +**Akzeptanzkriterien:** +- ? Correlation IDs in allen Logs +- ? Seq UI zeigt Logs (Development) +- ? File Logging funktioniert (Production) + +--- + +### WOCHE 6 - Production Deployment | ? Geplant - 0% + +**Ziel:** Service ist produktionsreif + +**Was wird gebaut:** +- appsettings.Production.json (Production Settings) +- IIS Web.config (Kestrel Settings) +- SSL/TLS Zertifikat konfigurieren +- Deployment-Skript (PowerShell) + +**Steps:** + +#### Deployment Vorbereitung +**Dauer:** ~4 Stunden + +**Was wird erstellt:** +- `appsettings.Production.json` (Prod-Settings) +- `Web.config` (IIS Integration) +- PowerShell Deploy-Skript +- Dokumentation (README.md) + +**Akzeptanzkriterien:** +- ? Build in Release Mode erfolgreich +- ? IIS Deployment funktioniert +- ? HTTPS funktioniert + +--- + +#### Production Testing +**Dauer:** ~4 Stunden + +**Was wird getestet:** +- Alle Endpoints im Production-Modus +- Health Checks +- Multi-Tenancy +- Performance (Load Testing) + +**Akzeptanzkriterien:** +- ? Alle Features funktionieren in Production +- ? Health Checks grün +- ? Performance OK (< 1s Response Time) + +--- + +## ?? FORTSCHRITTS-TRACKING + +### Gesamt-Fortschritt + +| Kategorie | Status | Fortschritt | +|-----------|--------|-------------| +| **Foundation** | ? Abgeschlossen | 100% | +| **Feature 1** | ?? In Progress | 50% | +| **Feature 2-5** | ? Pending | 0% | +| **Multi-Tenancy** | ? Pending | 0% | +| **Cross-Cutting** | ? Pending | 0% | +| **Production** | ? Pending | 0% | + +--- + +## ?? NEXT STEPS + +### Heute (17.01.2025) + +**Feature 1 - Step 1.1: Application Layer** +1. ? MediatR Setup (DependencyInjection.cs) +2. ? ValidationBehavior.cs erstellen +3. ? LoggingBehavior.cs erstellen +4. ? ValidatePDF Feature erstellen (Query, Handler, Validator) +5. ? DTOs erstellen (Request, Response) +6. ? Tests schreiben (ValidatePdfHandlerTests.cs) +7. ? Build + Tests grün +8. ? ROADMAP + PHASENPLAN aktualisieren +9. ? Commit + +**Danach:** +? Feature 1 - Step 1.2: API Layer (Endpoint + Exception Middleware) + +--- + +## ?? UPDATE LOG + +| Date | Feature/Step | Changes | +|------|--------------|---------| +| 2024-XX-XX | Foundation | Project setup, dependencies, folder structure | +| 2024-XX-XX | Domain Layer | Exceptions, Enums, Value Objects | +| 17.01.2025 | Infrastructure | DevExpressPdfProcessor.ValidateAsync implementiert | +| 17.01.2025 | Tests | DevExpressPdfProcessorTests.cs erstellt (6 Tests) | +| 17.01.2025 | **PHASENPLAN** | ?? **Komplett umstrukturiert** (Feature-basiert + Datum korrigiert 23.06.2026 ? 17.01.2025) | + +--- + +**END OF PHASENPLAN** + +*This document is a living document and will be updated after each completed step.* diff --git a/DocumentOperator.API/PROJECT_STATUS.md b/DocumentOperator.API/PROJECT_STATUS.md deleted file mode 100644 index 3d8dd52..0000000 --- a/DocumentOperator.API/PROJECT_STATUS.md +++ /dev/null @@ -1,203 +0,0 @@ -# DocumentOperator - Projekt Status & Zeitplan -# DocumentOperator - Project Status - -> **Stand:** 17.01.2025 | **Phase:** 4 (Application Layer - NEXT) | **Fortschritt:** ~30% | **Go-Live:** KW 8 (Ende Februar) - ---- - -## ?? Projekt -REST API für PDF-Operationen (Validierung, Konkatenation, Stempel, Attachments) - Multi-Tenant DMS -**Stack:** .NET 8, DevExpress PDF, MediatR, EF Core + SQLite, Redis, Polly - ---- - -## ?? Phasen (11 Total, ~25-30 Tage) - -| Phase | Name | Status | Deadline | -|-------|------|--------|----------| -| 1-3 | Foundation + Domain + Infrastructure | ? Fertig | - | -| 4 | Application Layer (MediatR) | ?? **AKTUELL** | KW 4 | -| 5 | API Layer (REST Endpoint) | ? | KW 4 | -| 5.5 | Health Checks + Resilience | ? | KW 4 | -| 6 | Weitere Features (4x) | ? | KW 5-6 | -| 6.5 | Async Processing (Queue) | ? | KW 6 | -| 7 | Swagger & Docs | ? | KW 6 | -| 8 | **Multi-Tenancy (DB + Redis)** | ? | **KW 7** | -| 9 | File Storage (lokal) | ? | KW 7 | -| 10 | Logging & Monitoring | ? | KW 7 | -| 11 | Production Deployment | ? | KW 8 | - ---- - -## ? Abgeschlossen -- Solution-Struktur (Clean Architecture, 4 Projekte) -- Domain Layer (Value Objects, Enums, Exceptions) -- **DevExpressPdfProcessor** (TDD, 6 Tests grün, PDF-Validierung funktioniert) - ---- - -## ?? Diese Woche (KW 4) -**Phase 4-5:** Erster funktionierender API-Endpoint (`POST /api/v1/documents/validate`) -- MediatR Setup + ValidationBehavior -- REST Endpoint + Exception Middleware -- Swagger UI - ---- - -## ?? Kritisch für Production -- **KW 6:** Async Processing (Queue) - verhindert HTTP Timeouts bei großen PDFs -- **KW 7:** Multi-Tenancy (EF Core + SQLite) - API-Key Management, Redis Cache - -**Deliverables:** -- ? Alle CRUD-Operationen (ValidatePdf, ExtractAttachments) -- ? Multi-Tenancy (DB-based, API-Key Management) -- ? Health Checks (/health Endpoint) -- ? Resilience (Polly) -- ? Swagger Documentation -- ? Production-ready Logging (Correlation IDs) - ---- - -### Milestone 3: Full Feature Set ?? -**Ziel:** Alle Features + Async Processing + File Storage -**Phasen:** Alle 11 Phasen abgeschlossen -**Zeitaufwand:** ~24-30 Arbeitstage -**ETA:** Ende KW 8 / Anfang KW 9 - -**Deliverables:** -- ? Alle PDF-Operationen (ValidatePdf, ExtractAttachments, ConcatenatePdfs, ApplyStamp, EmbedCertificate) -- ? Async Processing (In-Memory Queue für große Operationen) -- ? Lokale Temp-Ordner (File Storage mit automatischem Cleanup) -- ? Logging & Monitoring (Seq, File Logging) -- ? IIS Deployment-ready -- ? Rate-Limiting (Redis-based) - ---- - -## ?? Empfehlung für Projekt-Planung - -### Option 1: Agile Iterationen (Empfohlen ?) -**Strategie:** 3 Milestones iterativ ausrollen - -**Vorteile:** -- ? Frühes Feedback (MVP nach 2 Wochen testbar) -- ? Risiken früh erkannt (DevExpress Integration in Milestone 1) -- ? Flexibel (Anforderungen können sich ändern) - -**Zeitplan:** -- **Sprint 1 (2 Wochen):** Milestone 1 (MVP) - Phase 1-5 -- **Sprint 2 (3 Wochen):** Milestone 2 (Production-Ready) - Phase 5.5, 6, 7, 8 -- **Sprint 3 (1-2 Wochen):** Milestone 3 (Full Feature Set) - Phase 6.5, 9, 10, 11 - -**Gesamtdauer:** ~6 Wochen - ---- - -### Option 2: Wasserfall (Nicht empfohlen ??) -**Strategie:** Alle Phasen komplett abarbeiten, dann erst testen - -**Nachteile:** -- ? Spätes Feedback (erst nach 6 Wochen testbar) -- ? Risiken spät erkannt -- ? Keine Flexibilität - -**Gesamtdauer:** ~6-7 Wochen (gleich, aber höheres Risiko) - ---- - -## ?? Ressourcen-Bedarf - -### Entwickler -- **Aktuell:** 1 Entwickler (außerhalb der Kernarbeitszeit) -- **Empfehlung:** 1 Entwickler ausreichend (bei agilen Sprints) -- **Alternative:** 2 Entwickler = Halbierung der Zeit (~3 Wochen statt 6) - -### Infrastruktur (Production) -- **Lokaler File Server** (für Temp-Ordner) - ODER Shared Network Drive bei Multi-Server -- **Redis Server** (für Tenant-Caching, Rate-Limiting) -- **SQL Server** (optional, aktuell SQLite) - erst bei > 10.000 Requests/Sekunde - -### Kosten-Schätzung -- **Redis Cache (Basic):** ~15 EUR/Monat (optional: In-Memory Cache für Single-Server) -- **Seq (Self-Hosted):** Kostenlos (oder Seq Cloud: ~20 EUR/Monat) -- **File Storage:** Kostenlos (lokale Festplatte) - -**Gesamt:** ~0-35 EUR/Monat (abhängig von Redis + Seq Cloud) - ---- - -## ? Nächste Schritte (diese Woche) - -### Priorität 1: Phase 3 abschließen -- [ ] DevExpressPdfProcessor.cs implementieren (TDD Green Phase) -- [ ] Alle Tests grün machen -- [ ] Code Review - -**Zeitaufwand:** 1-2 Tage -**ETA:** Dienstag/Mittwoch - ---- - -### Priorität 2: Phase 4 starten (Application Layer) -- [ ] MediatR Setup -- [ ] ValidatePdf Feature (Query, Handler, Validator) - -**Zeitaufwand:** 2-3 Tage -**ETA:** Ende der Woche (Freitag) - ---- - -### Priorität 3: Milestone 1 erreichen (MVP) -- [ ] Phase 5 (API Layer) -- [ ] Erster testbarer Endpoint via Swagger - -**Zeitaufwand:** 2 Tage -**ETA:** Anfang nächste Woche (Montag/Dienstag) - ---- - -## ?? Fragen / Entscheidungen erforderlich - -1. **Redis Server:** - Ist Redis-Instanz verfügbar? (Benötigt für Phase 8 - Tenant-Caching, Phase 11 - Rate-Limiting) - **Alternative:** In-Memory Cache für Dev, Redis erst für Production - -2. **Deployment-Ziel:** - IIS? Docker/Kubernetes? - **Impact:** Beeinflusst Phase 11 (Production Deployment) - **Empfehlung:** IIS (Windows Server) - einfachste Lösung für .NET 8 APIs - -3. **Priorität Async Processing (Phase 6.5):** - Ist In-Memory Queue-basiertes Async Processing Pflicht oder Nice-to-have? - **Impact:** Kann Zeit sparen (~2 Tage), wenn nicht benötigt - **Hinweis:** In-Memory Queue = nicht persistent (Server-Neustart löscht Jobs) - -4. **Multi-Server Setup:** - Wird Load Balancer mit mehreren API-Instanzen benötigt? - **Impact:** Multi-Server = Shared Network Drive statt lokale Temp-Ordner notwendig - **Empfehlung:** Single-Server Setup vorerst (einfacher) - ---- - -## ?? Fazit & Empfehlung - -**Projekt-Status:** ? Auf gutem Weg (25% abgeschlossen) -**Architektur:** ? Solide (Clean Architecture, TDD, Vertical Slices) -**Technologie-Stack:** ? Modern (.NET 8, Minimal APIs, Polly, EF Core) - **OHNE Azure-Abhängigkeiten** -**Risiken:** ?? Gering (mit Polly Resilience abgefedert) - -**Empfehlung:** -1. ? **Phase 3 abschließen** (diese Woche) -2. ? **Milestone 1 (MVP) erreichen** (nächste Woche) -3. ? **Agile Iterationen** nutzen (3 Sprints à 2-3 Wochen) -4. ? **Frühes Feedback** einholen (nach Milestone 1) - -**ETA für Production-Ready (Milestone 2):** Ende KW 7 / Anfang KW 8 (bei 1 Entwickler) - -**Änderung (22.06.2026):** Azure Services (Blob Storage, Storage Queue) vollständig entfernt - stattdessen lokale Temp-Ordner + In-Memory Queue (einfacher, keine Cloud-Abhängigkeiten). Alle Azure-Referenzen in der Dokumentation bereinigt. - ---- - -**Erstellt von:** DocumentOperator Entwicklungsteam -**Datum:** 22.06.2026 -**Version:** 1.3 (Azure-Referenzen vollständig entfernt) diff --git a/DocumentOperator.API/ROADMAP.md b/DocumentOperator.API/ROADMAP.md index f1ca298..f13dba4 100644 --- a/DocumentOperator.API/ROADMAP.md +++ b/DocumentOperator.API/ROADMAP.md @@ -1,44 +1,465 @@ -# ?? DocumentOperator - Project Roadmap (Pragmatic Edition) +# ?? DocumentOperator - Project Roadmap (Feature-Driven Development) -> **Last Updated:** 17.01.2025 (Phase 3 abgeschlossen - DevExpressPdfProcessor) | **Status:** In Development | **Phase:** 4 (Application Layer - NEXT) +> **Last Updated:** 17.01.2025 | **Status:** In Development | **Current Feature:** Feature 1 - ValidatePDF (NEXT) --- -## ?? MAJOR UPDATE - Production-Ready Features Added! +## ?? NEW APPROACH: Feature-by-Feature Development -**Was ist neu in diesem Update?** +**Was hat sich geändert?** -1. **? Multi-Tenancy:** Database-based (EF Core + SQLite + Redis Cache) statt appsettings.json -2. **? Async Processing:** In-Memory Queue-based + Background Worker für große Operationen -3. **? File Storage:** Lokale Temp-Ordner mit IFileStorage Abstraction -4. **? Resilience:** Polly (Retry, Circuit Breaker, Timeout) für DevExpress Calls -5. **?? Health Checks:** FRÜH implementieren (Phase 5.5 statt Phase 9) -6. **? Logging:** Correlation IDs + Seq + File Logging -7. **? 9 neue NuGet Packages:** EF Core, Polly, BCrypt, Seq, Correlation IDs -8. **? 6 neue Phasen:** 5.5, 6.5, 8, 9, 10, 11 (insgesamt 11 Phasen statt 9) -9. **? 11 Key Learnings:** Dokumentiert (statt 5) -10. **? Technology Stack:** Komplett aktualisiert mit allen neuen Dependencies +? **Feature-Driven Development** statt Layer-by-Layer +- Jedes Feature wird KOMPLETT umgesetzt (Domain ? Infrastructure ? Application ? API ? Tests ? Swagger) +- Feature ist erst "DONE" wenn es im Swagger testbar ist +- Dann nächstes Feature -**Warum diese Änderungen?** -- **Einfachheit:** Lokale Temp-Ordner (keine Cloud-Abhängigkeiten) -- **Security:** API-Key Hashing (BCrypt), Rotation möglich -- **Performance:** Async Processing für große Operationen (keine HTTP Timeouts) -- **Resilience:** Production-ready (Polly Retry/Circuit Breaker) -- **Monitoring:** Correlation IDs, Seq, File Logging -- **Wartbarkeit:** Clean Architecture bleibt pragmatisch, aber production-ready! +? **Kleine Schritte** (1 Layer pro Step) +- Besser überschaubar +- Weniger Merge-Konflikte +- Schnelleres Feedback + +? **Multi-Tenancy & Cross-Cutting Concerns später** +- Erst alle synchronen Features implementieren +- Dann Multi-Tenancy für ALLE Endpoints (kein Wiederholungsaufwand!) +- Dann Health Checks, Polly, Logging (einmal für alle!) --- ## ?? TABLE OF CONTENTS -1. [Project Overview](#project-overview) -2. [Architecture & Design Decisions](#architecture--design-decisions) -3. [Development Philosophy](#development-philosophy) +1. [Feature Roadmap](#feature-roadmap) +2. [Project Overview](#project-overview) +3. [Architecture & Design Decisions](#architecture--design-decisions) 4. [Technology Stack](#technology-stack) 5. [Project Structure](#project-structure) -6. [Development Roadmap](#development-roadmap) -7. [Testing Strategy](#testing-strategy) -8. [Current Status](#current-status) +6. [Testing Strategy](#testing-strategy) +7. [Current Status](#current-status) +8. [Key Learnings & Decisions](#key-learnings--decisions) + +--- + +## ?? FEATURE ROADMAP + +### ?? Feature Overview + +| Feature | Type | Status | Swagger Testbar? | +|---------|------|--------|------------------| +| **1. ValidatePDF** | Synchron | ?? NEXT | ? | +| **2. ExtractAttachments** | Synchron | ? Pending | ? | +| **3. ApplyStamp** | Synchron | ? Pending | ? | +| **4. EmbedCertificate** | Synchron | ? Pending | ? | +| **5. ConcatenatePDFs** | Asynchron | ? Pending | ? | + +### ?? Cross-Cutting Concerns (nach Features 1-4) + +| Concern | Status | +|---------|--------| +| **Multi-Tenancy** (X-API-Key Header) | ? Pending | +| **Health Checks** (/health Endpoint) | ? Pending | +| **Polly Resilience** (Retry, Circuit Breaker) | ? Pending | +| **Logging & Monitoring** (Correlation IDs, Seq) | ? Pending | + +--- + +## ?? FEATURE 1: ValidatePDF (Synchron) - **NEXT** + +**Was macht dieses Feature?** +- Client sendet PDF als Base64 (JSON) +- API validiert PDF +- API gibt Metadaten zurück (Seitenzahl, Dateigröße, PDF-Version, Anhänge) + +**Endpoint:** +``` +POST /api/v1/documents/validate +Request: { "base64Pdf": "JVBERi0xLjQK..." } +Response: { "pageCount": 5, "fileSizeBytes": 1024, "pdfVersion": "1.4", "hasAttachments": false } +``` + +--- + +### ? Step 1.0: Foundation (bereits erledigt!) + +**Was wurde bereits erstellt:** +- ? Domain Layer (Exceptions, Enums, Value Objects) +- ? Infrastructure Layer (`DevExpressPdfProcessor.ValidateAsync` - FERTIG!) +- ? Tests für Infrastructure (`DevExpressPdfProcessorTests.cs` - 6 Tests) + +**Was wir wiederverwenden:** +- `Base64String` Value Object (Domain) +- `PdfMetadata` Value Object (Domain) +- `IPdfProcessor` Interface (Application) +- `DevExpressPdfProcessor.ValidateAsync` (Infrastructure) + +--- + +### ?? Step 1.1: Application Layer (MediatR Setup + ValidatePDF Feature) - **NEXT** + +**Ziel:** MediatR + FluentValidation + ValidatePDF Handler + +**Was wird erstellt:** + +#### 1.1.1: MediatR Setup +- **Datei:** `Application/DependencyInjection.cs` + - Registriert MediatR + - Registriert FluentValidation + - Registriert Pipeline Behaviors (Validation + Logging) + +#### 1.1.2: Pipeline Behaviors +- **Datei:** `Application/Common/Behaviors/ValidationBehavior.cs` + - Führt FluentValidation automatisch aus (vor jedem Handler) + - Wirft `ValidationException` bei Fehler + +- **Datei:** `Application/Common/Behaviors/LoggingBehavior.cs` + - Loggt jeden Request (mit Performance-Tracking) + - Nutzt Serilog + +#### 1.1.3: ValidatePDF Feature (Vertical Slice) +- **Ordner:** `Application/Features/Documents/ValidatePdf/` + +- **Datei:** `ValidatePdfQuery.cs` + ```csharp + public record ValidatePdfQuery(Base64String PdfContent) : IRequest; + ``` + +- **Datei:** `ValidatePdfHandler.cs` + ```csharp + public class ValidatePdfHandler : IRequestHandler + { + private readonly IPdfProcessor _processor; + + public async Task Handle(ValidatePdfQuery query, CancellationToken ct) + { + byte[] bytes = query.PdfContent.ToByteArray(); + return await _processor.ValidateAsync(bytes); + } + } + ``` + +- **Datei:** `ValidatePdfValidator.cs` + ```csharp + public class ValidatePdfValidator : AbstractValidator + { + public ValidatePdfValidator() + { + RuleFor(x => x.PdfContent).NotNull().WithMessage("PDF content is required"); + } + } + ``` + +#### 1.1.4: DTOs +- **Ordner:** `Application/Common/DTOs/` + +- **Datei:** `ValidatePdfRequest.cs` + ```csharp + public record ValidatePdfRequest(string Base64Pdf); + ``` + +- **Datei:** `ValidatePdfResponse.cs` + ```csharp + public record ValidatePdfResponse( + int PageCount, + long FileSizeBytes, + double FileSizeMB, + string PdfVersion, + bool HasAttachments, + int AttachmentCount); + ``` + +#### 1.1.5: Tests +- **Datei:** `Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs` + - Test: `Handle_ValidPdf_ReturnsPdfMetadata` + - Test: `Handle_NullPdfContent_ThrowsValidationException` + +**Akzeptanzkriterien:** +- ? Build erfolgreich +- ? Tests grün (alle 2 Tests) +- ? MediatR Pipeline funktioniert (Validation + Logging) + +--- + +### ?? Step 1.2: API Layer (Endpoint + Exception Middleware) + +**Ziel:** HTTP Endpoint + zentrale Exception Handling + +**Was wird erstellt:** + +#### 1.2.1: Exception Handling Middleware +- **Datei:** `API/Middleware/ExceptionHandlingMiddleware.cs` + - Fängt alle Exceptions + - Mappt zu HTTP Status Codes (400, 404, 500) + - Gibt RFC 7807 Problem Details zurück + +#### 1.2.2: Minimal API Endpoint +- **Datei:** `API/Endpoints/v1/DocumentEndpoints.cs` + ```csharp + public static class DocumentEndpoints + { + public static void MapDocumentEndpoints(this IEndpointRouteBuilder app) + { + var group = app.MapGroup("/api/v1/documents") + .WithTags("Documents") + .WithOpenApi(); + + group.MapPost("/validate", ValidatePdf) + .WithName("ValidatePdf") + .WithSummary("Validates a PDF document and returns metadata"); + } + + private static async Task ValidatePdf( + ValidatePdfRequest request, + IMediator mediator, + CancellationToken ct) + { + var query = new ValidatePdfQuery(Base64String.Create(request.Base64Pdf)); + var metadata = await mediator.Send(query, ct); + + var response = new ValidatePdfResponse( + metadata.PageCount, + metadata.FileSizeBytes, + metadata.FileSizeMB, + metadata.PdfVersion, + metadata.HasAttachments, + metadata.AttachmentCount + ); + + return Results.Ok(response); + } + } + ``` + +#### 1.2.3: Program.cs Updates +- Registriert Exception Middleware +- Registriert DocumentEndpoints +- Registriert Application + Infrastructure Services + +#### 1.2.4: Integration Tests +- **Datei:** `Tests/Integration/API/DocumentEndpointsTests.cs` + - Test: `POST_ValidatePdf_ValidPdf_Returns200` + - Test: `POST_ValidatePdf_InvalidBase64_Returns400` + - Test: `POST_ValidatePdf_CorruptedPdf_Returns500` + +**Akzeptanzkriterien:** +- ? Build erfolgreich +- ? Integration Tests grün (alle 3 Tests) +- ? Exception Middleware funktioniert +- ? Endpoint gibt korrekte HTTP Status Codes zurück + +--- + +### ?? Step 1.3: Swagger Dokumentation + +**Ziel:** API-Dokumentation + Swagger UI testbar + +**Was wird erstellt:** + +#### 1.3.1: Swagger Configuration +- **Datei:** `API/Configuration/SwaggerConfiguration.cs` + - AddSwaggerGen mit XML Comments + - Konfiguriert API-Versioning + - Fügt Beispiel-Schemas hinzu + +#### 1.3.2: XML Comments +- Aktivieren in `API/DocumentOperator.API.csproj`: + ```xml + + true + $(NoWarn);1591 + + ``` + +- XML Comments für `ValidatePdf` Endpoint: + ```csharp + /// + /// Validates a PDF document and returns metadata + /// + /// PDF as Base64 string + /// PDF metadata (page count, file size, etc.) + /// PDF is valid, metadata returned + /// Invalid PDF or Base64 format + /// Internal server error during validation + ``` + +**Akzeptanzkriterien:** +- ? Swagger UI läuft unter `/swagger` +- ? Endpoint `/api/v1/documents/validate` ist sichtbar +- ? Request/Response Schemas sind dokumentiert +- ? Endpoint ist im Swagger UI testbar (manuelle Verifikation!) + +--- + +### ? Feature 1 ABGESCHLOSSEN! + +**Was haben wir erreicht?** +- ? ValidatePDF Feature komplett implementiert +- ? Domain ? Infrastructure ? Application ? API ? Tests ? Swagger +- ? Endpoint ist im Swagger UI testbar +- ? Unit Tests + Integration Tests grün +- ? Clean Architecture eingehalten +- ? TDD angewendet + +**Nächstes Feature:** +? **Feature 2: ExtractAttachments** + +--- + +## ?? FEATURE 2: ExtractAttachments (Synchron) - **PENDING** + +**Was macht dieses Feature?** +- Client sendet PDF als Base64 (JSON) +- API extrahiert eingebettete Anhänge +- API gibt Anhänge als Base64 zurück (oder Download-Links) + +**Endpoint:** +``` +POST /api/v1/documents/extract-attachments +Request: { "base64Pdf": "JVBERi0xLjQK..." } +Response: { "attachments": [{ "name": "invoice.xml", "base64Content": "..." }] } +``` + +**Steps:** +- ?? Step 2.1: Infrastructure Layer (DevExpressPdfProcessor.ExtractAttachmentsAsync) +- ?? Step 2.2: Application Layer (ExtractAttachmentsCommand + Handler + Validator) +- ?? Step 2.3: API Layer (Endpoint) +- ?? Step 2.4: Swagger Dokumentation + +--- + +## ?? FEATURE 3: ApplyStamp (Synchron) - **PENDING** + +**Was macht dieses Feature?** +- Client sendet PDF + Stamp-Konfiguration (Text, Position) +- API fügt Stamp hinzu (Logo, Text, Wasserzeichen) +- API gibt gestempeltes PDF zurück + +**Endpoint:** +``` +POST /api/v1/documents/apply-stamp +Request: { "base64Pdf": "...", "text": "CONFIDENTIAL", "position": "TopRight" } +Response: { "base64Pdf": "JVBERi0xLjQK..." } +``` + +**Steps:** +- ?? Step 3.1: Infrastructure Layer (DevExpressPdfProcessor.ApplyStampAsync) +- ?? Step 3.2: Application Layer (ApplyStampCommand + Handler + Validator) +- ?? Step 3.3: API Layer (Endpoint) +- ?? Step 3.4: Swagger Dokumentation + +--- + +## ?? FEATURE 4: EmbedCertificate (Synchron) - **PENDING** + +**Was macht dieses Feature?** +- Client sendet PDF + Zertifikat (PFX als Base64) +- API bettet Zertifikat als Attachment ein +- API gibt PDF mit Zertifikat zurück + +**Endpoint:** +``` +POST /api/v1/documents/embed-certificate +Request: { "base64Pdf": "...", "base64Certificate": "..." } +Response: { "base64Pdf": "JVBERi0xLjQK..." } +``` + +**Steps:** +- ?? Step 4.1: Infrastructure Layer (DevExpressPdfProcessor.EmbedCertificateAsync) +- ?? Step 4.2: Application Layer (EmbedCertificateCommand + Handler + Validator) +- ?? Step 4.3: API Layer (Endpoint) +- ?? Step 4.4: Swagger Dokumentation + +--- + +## ?? FEATURE 5: ConcatenatePDFs (Asynchron) - **PENDING** + +**Was macht dieses Feature?** +- Client sendet mehrere PDFs (Array von Base64) +- API startet asynchronen Job (gibt JobId zurück) +- Client pollt Job-Status +- Wenn fertig: Client lädt Ergebnis herunter + +**Endpoints:** +``` +POST /api/v1/documents/concatenate (Async) +Request: { "pdfFiles": ["JVBERi0x...", "JVBERi0y..."] } +Response: { "jobId": "abc-123", "status": "Pending" } + +GET /api/v1/jobs/{jobId} +Response: { "jobId": "abc-123", "status": "Processing", "progress": 50 } + +GET /api/v1/jobs/{jobId}/download +Response: PDF-Datei (Binary) +``` + +**Steps:** +- ?? Step 5.1: Infrastructure Layer (In-Memory Queue + Background Worker) +- ?? Step 5.2: Application Layer (SubmitConcatenateJobCommand + GetJobStatusQuery) +- ?? Step 5.3: API Layer (Async Endpoints) +- ?? Step 5.4: Swagger Dokumentation + +--- + +## ?? CROSS-CUTTING CONCERNS + +**Nach Features 1-4 abgeschlossen:** + +### ?? Multi-Tenancy (X-API-Key Header) + +**Was wird gebaut:** +- EF Core + SQLite (Tenant-Datenbank) +- Redis Cache (API-Key Lookups) +- TenantResolutionMiddleware (X-API-Key ? Tenant) +- BCrypt API-Key Hashing +- Admin API (Tenant CRUD) + +**Steps:** +- ?? Step MT.1: EF Core Setup (Entities, DbContext, Migrations) +- ?? Step MT.2: TenantResolutionMiddleware +- ?? Step MT.3: Redis Cache Integration +- ?? Step MT.4: Admin API (Tenant Management) +- ?? Step MT.5: Alle Endpoints mit X-API-Key absichern + +--- + +### ?? Health Checks + +**Was wird gebaut:** +- `/health` Endpoint (Liveness/Readiness Probes) +- DevExpressPdfHealthCheck (Smoke Test) +- Database Health Check (SQLite) +- Redis Health Check + +**Steps:** +- ?? Step HC.1: DevExpressPdfHealthCheck +- ?? Step HC.2: Database Health Check +- ?? Step HC.3: Redis Health Check (optional) + +--- + +### ??? Polly Resilience + +**Was wird gebaut:** +- Retry Policy (3x mit Exponential Backoff) +- Circuit Breaker (nach 5 Fehlern 30s öffnen) +- Timeout Policy (30s max) + +**Steps:** +- ?? Step PR.1: Polly Policies in DevExpressPdfProcessor +- ?? Step PR.2: Logging für Resilience Events + +--- + +### ?? Logging & Monitoring + +**Was wird gebaut:** +- Correlation IDs (X-Correlation-ID Header) +- Seq Sink (Log-Browsing UI) +- File Logging (Production) +- LoggingBehavior (MediatR Pipeline) + +**Steps:** +- ?? Step LM.1: CorrelationIdMiddleware +- ?? Step LM.2: Seq Sink konfigurieren +- ?? Step LM.3: File Logging konfigurieren +- ?? Step LM.4: LoggingBehavior erweitern (Performance-Tracking) --- @@ -59,85 +480,11 @@ **Lösung:** - **Ein** zentraler Service für alle PDF-Operationen - Wiederverwendbar über HTTP REST API -- Mandantenfähig (Multi-Tenancy) +- Mandantenfähig (Multi-Tenancy - später!) - Wartbar an einer Stelle --- -### Core Features - -Der Service bietet folgende PDF-Operationen: - -#### 1. **PDF Validierung** -- Prüfung auf gültiges PDF-Format -- Korruptions-Erkennung -- Metadaten-Extraktion (Seitenzahl, Größe, Version, Anhänge) - -#### 2. **Attachment-Extraktion** -- Erkennung von eingebetteten Anhängen -- Extraktion in temporären Ordner -- Rückgabe als Base64 oder Download-Link - -#### 3. **PDF-Konkatenation** -- Zusammenführen mehrerer PDFs -- Reihenfolge konfigurierbar -- Seitenzahl-Optimierung - -#### 4. **Stempel/Wasserzeichen** -- Aufbringen von Stamps (Logo, Text) -- Positions-Konfiguration -- Mandanten-spezifische Logos - -#### 5. **Zertifikat-Einbettung** -- PFX-Zertifikate als Attachment einbetten -- Digitale Signatur-Vorbereitung -- Workflow-Integration (Ergebnisbericht ? Zertifikat ? Siegel) - ---- - -### Business Workflow - -**Synchroner Flow (kleine Operationen < 5 Sekunden):** -``` -Client Application - ? -[HTTP Request] - JSON mit Base64-PDF + X-API-Key Header - ? -Tenant Resolution Middleware ? API-Key ? Tenant aus DB (Redis Cache) - ? -DocumentOperator API (Minimal API Endpoint) - ? -[FluentValidation] ? [MediatR Handler] ? [DevExpress Service] ? [Ergebnis] - ? -[HTTP Response] - JSON mit verarbeitetem PDF (Base64) -``` - -**Asynchroner Flow (große Operationen > 5 Sekunden):** -``` -Client Application - ? -[HTTP POST /api/v1/documents/concatenate] - Große Operation - ? -[Returns: { "jobId": "abc123", "status": "Pending" }] - Sofort - ? -Background Worker (IHostedService) ? In-Memory Queue ? Verarbeitung - ? -Client Poll: [GET /api/v1/jobs/abc123] ? { "status": "Processing", "progress": 45% } - ? -Client Poll: [GET /api/v1/jobs/abc123] ? { "status": "Success", "resultUrl": "/download/xyz" } -``` - -**Typischer Ablauf (Synchron):** -1. Client sendet PDF als Base64 in JSON + API-Key Header -2. Tenant Resolution Middleware validiert API-Key (DB-Lookup mit Redis Cache) -3. API validiert Input (FluentValidation in MediatR Pipeline) -4. Handler konvertiert PDF ? Byte-Array -5. DevExpress Service führt Operation durch (mit Polly Retry/Circuit Breaker) -6. Ergebnis wird in lokalem Temp-Ordner gespeichert -7. Ergebnis wird als Base64 zurückgegeben (oder Download-Link) - ---- - ## ??? ARCHITECTURE & DESIGN DECISIONS ### Clean Architecture (Pragmatisch!) @@ -145,15 +492,15 @@ Client Poll: [GET /api/v1/jobs/abc123] ? { "status": "Success", "resultUrl": "/d Wir verwenden **Clean Architecture** mit 4 Layers - **ABER: pragmatisch, nicht dogmatisch!** ``` -??????????????????????????????????????? +????????????????????????????????????? ? API Layer (Endpoints) ? ? HTTP Entry Point -??????????????????????????????????????? +????????????????????????????????????? ? Application Layer (Use Cases) ? ? MediatR Handlers, DTOs -??????????????????????????????????????? +????????????????????????????????????? ? Infrastructure Layer (Tech Stack) ? ? DevExpress, File I/O -??????????????????????????????????????? +????????????????????????????????????? ? Domain Layer (MINIMAL!) ? ? Nur Enums + Value Objects -??????????????????????????????????????? +????????????????????????????????????? ``` #### Dependency Rule @@ -181,46 +528,6 @@ Application ? NUR Domain --- -### Domain Layer - Warum so minimal? - -**Was wir NICHT haben:** -- ? Keine Datenbank / EF Core -- ? Keine komplexen Entities mit Business-Logik -- ? Keine Aggregate Roots, Repositories, etc. - -**Was wir SIND:** -- ? Ein **Service** (nicht eine Domain-lastige Business-Anwendung) -- ? PDF-Operationen = technische Operationen (nicht fachliche Geschäftslogik) -- ? Daten fließen durch (Input ? Verarbeitung ? Output) - -**Deshalb: Domain Layer minimal!** - -**Was bleibt in Domain:** -1. **Enums** (DocumentOperationType, ProcessingStatus) - - Pure Business-Konzepte - - Technologie-unabhängig - - Wiederverwendbar über alle Layer - -2. **Value Objects** (Base64String, TenantId, PdfMetadata) - - Typsicherheit (Base64String statt string) - - Selbst-validierend (Fehler werfen im Constructor) - - Immutable (keine Änderungen nach Erstellung) - -3. **Domain Exceptions** (DomainValidationException, PdfProcessingException, etc.) - - Für fachliche Fehler - - Exception Middleware mapped zu HTTP Status Codes - -**Was wir NICHT in Domain haben:** -- ? Domain Models (PdfDocument, DocumentAttachment) ? DTOs in Application reichen! -- ? Constants (ErrorCodes) ? erst wenn wirklich mehrfach gebraucht (YAGNI) -- ? Services (? Infrastructure) - -**Fazit:** -- Domain = **so viel wie nötig, so wenig wie möglich** -- Wenn wir später merken "das fehlt" ? dann erst hinzufügen (iterativ!) - ---- - ### CQRS with MediatR **Pattern:** Command Query Responsibility Segregation @@ -232,7 +539,7 @@ Application ? NUR Domain - ? Kein aufgeblähter Service mit 20 Methoden **CQRS in unserem Kontext:** -- **Command:** Ändert Daten (ProcessDocument, ApplyStamp, etc.) +- **Command:** Ändert Daten (ApplyStamp, EmbedCertificate, etc.) - **Query:** Liest Daten (ValidatePdf ? gibt nur Metadata zurück) **Beispiel:** @@ -254,11 +561,6 @@ public class ValidatePdfHandler : IRequestHandler } ``` -**Warum Value Objects in Query/Command?** -- ? Typsicherheit (Base64String vs string) -- ? Validierung bereits beim Erstellen der Query (nicht im Handler) -- ? Handler bleibt schlank (keine Validierungs-Boilerplate) - --- ### Vertical Slice Architecture @@ -304,7 +606,7 @@ Features/ ### Exception-based Error Handling -**Entscheidung:** Keine Result Pattern Library (Ardalis.Result entfernt) +**Entscheidung:** Keine Result Pattern Library **Stattdessen:** 1. **FluentValidation** für Input-Validierung (DTO-Ebene) @@ -316,7 +618,6 @@ Features/ - ? Weniger Boilerplate (kein Result Wrapping) - ? Standard .NET Exception-Flow (jeder kennt es) - ? Zentrales Error Handling = wartbar an **einer** Stelle -- ? Ein Package weniger (keine Extra-Lib) **Flow:** ``` @@ -336,164 +637,9 @@ Middleware (Exception Handler) HTTP Response (JSON) ``` -**Exception Types:** -- `FluentValidation.ValidationException` ? HTTP 400 (Bad Request) -- `DomainValidationException` ? HTTP 400 (Bad Request) -- `NotFoundException` ? HTTP 404 (Not Found) -- `PdfProcessingException` ? HTTP 500 (Internal Server Error) -- `Exception` (Catch-All) ? HTTP 500 - -**Warum zentral?** -- Alle Fehler an **einer** Stelle behandelt -- Konsistente Error-Responses (Problem Details Format) -- Handler bleiben schlank (kein Try/Catch in jedem Handler) -- Logging zentral (Serilog) - --- -### Minimal APIs (statt Controllers) - -**Warum Minimal APIs?** -- ? .NET 8 Best Practice (Microsoft empfiehlt es) -- ? Weniger Boilerplate (keine Controller-Klassen) -- ? Direkte Endpoint-Definition (funktionaler Stil) -- ? Swagger funktioniert 1:1 (WithOpenApi()) -- ? Bessere Performance (weniger Abstraktion) - -**Beispiel:** -```csharp -app.MapPost("/api/v1/documents/validate", async ( - ValidatePdfRequest request, - IMediator mediator, - CancellationToken ct) => -{ - // DTO ? Query (Value Objects erstellen) - var query = new ValidatePdfQuery( - Base64String.Create(request.Base64Pdf) - ); - - // MediatR Handler aufrufen - var result = await mediator.Send(query, ct); - - // HTTP 200 + JSON Response - return Results.Ok(result); -}) -.WithName("ValidatePdf") -.WithTags("Documents") -.WithOpenApi(); -``` - -**Flow:** -1. HTTP Request kommt rein -2. ASP.NET Core deserialisiert JSON ? DTO -3. Endpoint ruft MediatR auf -4. MediatR Pipeline: Validation ? Handler ? Response -5. Endpoint gibt Result zurück (Results.Ok()) - ---- - -### Multi-Tenancy via API-Keys (Database-based) - -**Konzept:** -- Jeder Mandant (Customer A, B, C...) hat eigenen API-Key -- API-Key wird in HTTP Header gesendet: `X-API-Key: customer-a-key-12345` -- API-Keys werden in **Datenbank** gespeichert (SQLite für Einfachheit) -- Redis Cache für schnelle API-Key Lookups (Performance!) -- Middleware resolved API-Key ? Tenant-Context -- Tenant-spezifische Einstellungen (Logo für Stamps, Zertifikat, etc.) - -**Warum Database-based (statt appsettings.json)?** -- ? **Skalierbar:** Neue Tenants ohne Neustart hinzufügen -- ? **Security:** API-Key Rotation möglich (gehashed in DB!) -- ? **Audit-Log:** Wer hat wann was aufgerufen? -- ? **Rate-Limiting:** Pro Tenant konfigurierbar (Redis Sliding Window) -- ? **Tenant-Management:** CRUD-API für API-Keys (Admin-Endpoint) - -**Warum SQLite (statt SQL Server)?** -- ? Einfache Deployment (keine separate DB-Server) -- ? Wenige Daten (nur Tenant-Tabelle + Settings) -- ? Migrations-Support (EF Core) -- ? Production-ready (für < 10.000 Requests/Sekunde ausreichend) - -**Flow:** -``` -HTTP Request mit Header "X-API-Key: abc123" - ? -TenantResolutionMiddleware - ? -Redis Cache Lookup (Key: "tenant:abc123") - ? Cache Hit: Tenant-Info geladen (1ms) - ? Cache Miss: DB Lookup ? Redis Cache befüllen (TTL: 1 Stunde) - ? -API-Key Hash validieren (BCrypt) - ? -Tenant.IsActive prüfen (inaktive Tenants ? HTTP 403) - ? -ITenantContext setzen (Scoped Service) - ? -Handler nutzt ITenantContext.TenantId -``` - -**Datenbank-Schema:** -```csharp -// Tenant-Tabelle (EF Core Entity) -public class Tenant -{ - public Guid Id { get; set; } - public string Name { get; set; } // "Customer A" - public string ApiKeyHash { get; set; } // BCrypt Hash - public bool IsActive { get; set; } - public DateTime CreatedAt { get; set; } - public DateTime? LastUsedAt { get; set; } - - // Navigation - public TenantSettings Settings { get; set; } -} - -// Tenant-Settings (1:1 Beziehung) -public class TenantSettings -{ - public Guid Id { get; set; } - public Guid TenantId { get; set; } - public string LogoFilePath { get; set; } // Lokaler Dateipfad (z.B. "logos/tenant-a/stamp.png") - public string CertificateFilePath { get; set; } // Lokaler Dateipfad (z.B. "certs/tenant-a/cert.pfx") - public int RateLimitPerMinute { get; set; } // Rate-Limiting - - // Navigation - public Tenant Tenant { get; set; } -} -``` - -**Beispiel - Tenant-spezifischer Stamp:** -```csharp -public class ApplyStampHandler : IRequestHandler -{ - private readonly ITenantContext _tenantContext; - private readonly IFileStorage _fileStorage; // Lokaler File Storage - - public async Task Handle(ApplyStampCommand command, CancellationToken ct) - { - // Tenant-spezifisches Logo aus DB Settings laden - var logoPath = _tenantContext.CurrentTenant.Settings.LogoFilePath; - - // Logo aus lokalem Dateisystem laden - var logoBytes = await _fileStorage.GetAsync(logoPath); - - // Stamp mit Logo anwenden - // ... - } -} -``` - -**Vorteile:** -- ? Neue Tenants via Admin-API hinzufügen (ohne Neustart!) -- ? API-Key Rotation (alten Key invalidieren, neuen generieren) -- ? Audit-Log (LastUsedAt pro Tenant tracken) -- ? Rate-Limiting pro Tenant (Redis Counter) - ---- - -## ??? TECHNOLOGY STACK +## ?? TECHNOLOGY STACK ### Core Framework @@ -514,11 +660,9 @@ public class ApplyStampHandler : IRequestHandler | **Swashbuckle.AspNetCore** | 6.6.2 | Swagger/OpenAPI Documentation | | **Serilog.AspNetCore** | 10.0.0 | Strukturiertes Logging | | **Serilog.Sinks.File** | 7.0.0 | Log-Datei-Output | -| **Serilog.Sinks.Seq** | 8.0.0 | **NEU:** Log-Browsing UI (Development) | -| **Serilog.Enrichers.Environment** | 3.0.1 | Log-Enrichment (MachineName, etc.) | -| **Serilog.Enrichers.CorrelationId** | 3.0.1 | **NEU:** Correlation IDs für Request-Tracking | +| **Serilog.Sinks.Seq** | 8.0.0 | Log-Browsing UI (Development) | +| **Serilog.Enrichers.CorrelationId** | 3.0.1 | Correlation IDs für Request-Tracking | | **Asp.Versioning.Http** | 8.1.1 | API Versioning (/api/v1/, /api/v2/) | -| **Microsoft.Extensions.Caching.StackExchangeRedis** | 8.0.28 | Redis Cache (Tenant-Lookups, Rate-Limiting) | #### Application Layer @@ -533,28 +677,10 @@ public class ApplyStampHandler : IRequestHandler | Package | Version | Purpose | |---------|---------|---------| | **DevExpress.Pdf.Core** | 25.2.8 | PDF-Operationen (Merge, Extract, Sign, etc.) | -| **DevExpress Universal License** | ? Verfügbar | **Vollzugriff auf alle DevExpress Bibliotheken** | -| **Microsoft.EntityFrameworkCore** | 8.0.0 | **NEU:** ORM für Tenant-Datenbank | -| **Microsoft.EntityFrameworkCore.Sqlite** | 8.0.0 | **NEU:** SQLite Provider (Tenant-DB) | -| **Microsoft.EntityFrameworkCore.Tools** | 8.0.0 | **NEU:** Migrations-Support | -| **Polly** | 8.5.0 | **NEU:** Resilience (Retry, Circuit Breaker, Timeout) | -| **BCrypt.Net-Next** | 4.0.3 | **NEU:** API-Key Hashing (Security) | -| **Microsoft.Extensions.Options.ConfigurationExtensions** | 8.0.0 | Options Pattern | - -**Hinweis zur DevExpress Lizenz:** -- ? Universal License vorhanden - wir können **ALLE** DevExpress Pakete nutzen -- Neben `DevExpress.Pdf.Core` können wir auch weitere Pakete integrieren: - - `DevExpress.Office.Core` (Word, Excel) - - `DevExpress.Document.Processor` (erweiterte Dokumenten-Verarbeitung) - - `DevExpress.Blazor` (falls UI später benötigt wird) - - Alle weiteren DevExpress Produkte nach Bedarf - -**Warum diese neuen Pakete?** -- **EF Core + SQLite:** Tenant-Datenbank (API-Keys, Settings) - skalierbar ohne SQL Server -- **Polly:** Resilience für DevExpress Calls (Retry bei Transient Errors, Circuit Breaker) -- **BCrypt:** Sichere API-Key Hashes (NICHT Klartext in DB!) -- **Seq:** Log-Browsing UI für Development -- **Correlation IDs:** Request-Tracking über alle Logs (Debugging leichter) +| **Polly** | 8.5.0 | Resilience (Retry, Circuit Breaker, Timeout) - SPÄTER! | +| **Microsoft.EntityFrameworkCore** | 8.0.0 | ORM für Tenant-Datenbank - SPÄTER! | +| **Microsoft.EntityFrameworkCore.Sqlite** | 8.0.0 | SQLite Provider - SPÄTER! | +| **BCrypt.Net-Next** | 4.0.3 | API-Key Hashing - SPÄTER! | #### Domain Layer @@ -562,7 +688,7 @@ public class ApplyStampHandler : IRequestHandler |---------|---------|---------| | - | - | **Keine Dependencies!** (Clean Architecture) | -#### Tests (neu!) +#### Tests | Package | Version | Purpose | |---------|---------|---------| @@ -570,7 +696,6 @@ public class ApplyStampHandler : IRequestHandler | **FluentAssertions** | 7.0.0 | Assertions (result.Should().Be(expected)) | | **Moq** | 4.20.72 | Mocking (für Services) | | **Microsoft.NET.Test.Sdk** | 17.11.1 | Test SDK | -| **xunit.runner.visualstudio** | 2.8.2 | Visual Studio Test Runner | --- @@ -584,8 +709,9 @@ DocumentOperator/ ??? DocumentOperator.Application/ ? Use Cases (MediatR Handlers) ??? DocumentOperator.Infrastructure/ ? Technical Implementations ??? DocumentOperator.Domain/ ? Business Logic (MINIMAL!) -??? DocumentOperator.Tests/ ? Unit & Integration Tests (NEU!) +??? DocumentOperator.Tests/ ? Unit & Integration Tests ??? ROADMAP.md ? This file +??? PHASENPLAN.md ? Project timeline ``` --- @@ -594,11 +720,6 @@ DocumentOperator/ **Purpose:** HTTP Entry Point, Routing, Middleware -**References:** -- ? Application -- ? Infrastructure -- ? Domain - **Folder Structure:** ``` @@ -609,197 +730,93 @@ DocumentOperator.API/ ??? Middleware/ ? ??? ExceptionHandlingMiddleware.cs ? Zentrale Exception Handling ? ??? Configuration/ -? ??? SwaggerConfiguration.cs ? Swagger Setup (API-Key Support) +? ??? SwaggerConfiguration.cs ? Swagger Setup ??? appsettings.json ? Base Configuration ??? appsettings.Development.json ? Dev Overrides ??? Program.cs ? Application Entry Point ``` -**Was gehört hierher:** -- ? HTTP Routing (Minimal APIs) -- ? Middleware (Exception, Logging) -- ? Swagger Configuration -- ? Dependency Injection Setup -- ? appsettings.json - -**Was NICHT hierher gehört:** -- ? Business Logic (? Application) -- ? PDF-Verarbeitung (? Infrastructure) -- ? Validierung (? Application: FluentValidation) - --- ### ?? Application Layer (DocumentOperator.Application) **Purpose:** Use Cases, Business Logic Orchestration -**References:** -- ? Domain (ONLY!) - **Folder Structure:** ``` DocumentOperator.Application/ ??? Features/ ? Vertical Slices ? ? ??? Documents/ -? ? ??? ValidatePdf/ -? ? ? ??? ValidatePdfQuery.cs -? ? ? ??? ValidatePdfHandler.cs -? ? ? ??? ValidatePdfValidator.cs -? ? ??? ExtractAttachments/ -? ? ? ??? ExtractAttachmentsCommand.cs -? ? ? ??? ExtractAttachmentsHandler.cs -? ? ? ??? ExtractAttachmentsValidator.cs -? ? ??? ... (weitere Features iterativ) -? ??? Jobs/ ? **NEU:** Async Processing Features -? ??? GetJobStatus/ -? ? ??? GetJobStatusQuery.cs -? ? ??? GetJobStatusHandler.cs -? ??? SubmitConcatenateJob/ -? ??? SubmitConcatenateJobCommand.cs -? ??? SubmitConcatenateJobHandler.cs +? ??? ValidatePdf/ +? ? ??? ValidatePdfQuery.cs +? ? ??? ValidatePdfHandler.cs +? ? ??? ValidatePdfValidator.cs +? ??? ExtractAttachments/ +? ? ??? ExtractAttachmentsCommand.cs +? ? ??? ExtractAttachmentsHandler.cs +? ? ??? ExtractAttachmentsValidator.cs +? ??? ... (weitere Features iterativ) ??? Common/ ? ??? Interfaces/ ? Abstractions für Infrastructure ? ? ??? IPdfProcessor.cs -? ? ??? IFileStorage.cs ? **NEU:** File Storage Abstraction -? ? ??? IJobQueue.cs ? **NEU:** Queue Abstraction (In-Memory Queue) -? ? ??? ITenantRepository.cs ? **NEU:** Tenant-DB Abstraction ? ??? Behaviors/ ? MediatR Pipeline Behaviors ? ? ??? ValidationBehavior.cs ? FluentValidation Integration -? ? ??? LoggingBehavior.cs ? **NEU:** Structured Logging mit Correlation IDs -? ??? DTOs/ ? Data Transfer Objects (API Contracts) -? ? ??? ValidatePdfRequest.cs -? ? ??? ValidatePdfResponse.cs -? ? ??? JobStatusResponse.cs ? **NEU:** Async Job Status -? ??? Models/ ? **NEU:** Shared Models -? ??? TenantContext.cs ? Tenant-Info (TenantId, Name, Settings) +? ? ??? LoggingBehavior.cs ? Structured Logging +? ??? DTOs/ ? Data Transfer Objects +? ??? ValidatePdfRequest.cs +? ??? ValidatePdfResponse.cs ??? DependencyInjection.cs ? Service Registration ``` -**Was gehört hierher:** -- ? MediatR Commands & Queries (pro Feature) -- ? Handlers (orchestrieren Domain + Infrastructure) -- ? FluentValidation Validators -- ? DTOs (API Contracts) -- ? Interfaces für Infrastructure (Dependency Inversion!) -- ? Pipeline Behaviors (Validation, Logging) - -**Was NICHT hierher gehört:** -- ? DevExpress-spezifischer Code (? Infrastructure) -- ? File I/O (? Infrastructure) -- ? HTTP-spezifisches (? API) - -**Warum keine Infrastructure-Referenz?** -- Application kennt nur **Interfaces** (`IPdfProcessor`) -- Infrastructure **implementiert** die Interfaces (`DevExpressPdfProcessor`) -- API injiziert die Implementierung via DI -- ? Application bleibt technologie-unabhängig! - --- ### ?? Infrastructure Layer (DocumentOperator.Infrastructure) **Purpose:** Technische Implementierungen -**References:** -- ? Application (für Interfaces) -- ? Domain - **Folder Structure:** ``` DocumentOperator.Infrastructure/ ??? Services/ ? ??? PdfProcessing/ -? ? ??? DevExpressPdfProcessor.cs ? IPdfProcessor Implementation (mit Polly Resilience) -? ??? FileStorage/ -? ? ??? LocalFileStorage.cs ? **NEU:** IFileStorage Implementation (lokaler Temp-Ordner) -? ??? Queue/ -? ??? InMemoryJobQueue.cs ? **NEU:** IJobQueue Implementation (In-Memory Queue) -??? Data/ -? ??? TenantDbContext.cs ? **NEU:** EF Core DbContext (Tenant-DB) -? ??? Entities/ -? ? ??? Tenant.cs ? **NEU:** Tenant Entity -? ? ??? TenantSettings.cs ? **NEU:** TenantSettings Entity -? ??? Repositories/ -? ? ??? TenantRepository.cs ? **NEU:** ITenantRepository Implementation -? ??? Migrations/ ? **NEU:** EF Core Migrations -??? BackgroundServices/ -? ??? JobProcessorService.cs ? **NEU:** IHostedService für Async Job Processing -? ??? TempFileCleanupService.cs ? **NEU:** IHostedService für Temp-File Cleanup (täglich) +? ??? DevExpressPdfProcessor.cs ? IPdfProcessor Implementation ? ??? Configuration/ ? ??? DocumentOperatorSettings.cs ? Options Pattern Class -? ??? FileStorageSettings.cs ? **NEU:** File Storage Configuration (Temp-Ordner Pfad) -? ??? RedisSettings.cs ? **NEU:** Redis Cache Configuration ??? DependencyInjection.cs ? Service Registration ``` -**Was gehört hierher:** -- ? DevExpress Integration (mit Polly Resilience!) -- ? **File Storage:** Lokaler Temp-Ordner (IFileStorage Abstraction) -- ? **Queue:** In-Memory Queue für Async Processing -- ? **Datenbank:** EF Core + SQLite (Tenant-Management) -- ? **Background Services:** Job Processing, Temp-File Cleanup -- ? Options Pattern Classes (Settings) - -**Was NICHT hierher gehört:** -- ? Business Logic (? Application) -- ? HTTP Handling (? API) - --- -### ??? Domain Layer (DocumentOperator.Domain) - MINIMAL! +### ?? Domain Layer (DocumentOperator.Domain) - MINIMAL! **Purpose:** Business Rules (nur was wirklich gebraucht wird!) -**References:** -- ? **KEINE!** (wichtigste Clean Architecture Regel) - **Folder Structure:** ``` DocumentOperator.Domain/ -??? ValueObjects/ ? Immutable, selbst-validierend +??? ValueObjects/ ? Immutable, selbst-validierend ? ? ??? Base64String.cs ? ??? TenantId.cs ? ??? PdfMetadata.cs -? ??? JobId.cs ? **NEU:** Job-ID für Async Processing -??? Enums/ +??? Enums/ ? ? ? ??? DocumentOperationType.cs -? ??? ProcessingStatus.cs ? (Wird jetzt für Async Jobs genutzt!) -??? Exceptions/ ? Domain-spezifische Exceptions +? ??? ProcessingStatus.cs +??? Exceptions/ ? Domain-spezifische Exceptions ? ??? DomainException.cs ??? DomainValidationException.cs ??? NotFoundException.cs ??? PdfProcessingException.cs ``` -**Was gehört hierher:** -- ? Value Objects (Base64String, TenantId, PdfMetadata, JobId) -- ? Enums (DocumentOperationType, ProcessingStatus) -- ? Domain Exceptions - -**Wichtig:** -- Domain bleibt **MINIMAL** - keine EF Core Entities hier! -- Tenant, TenantSettings sind **Infrastructure** Entities (Data/Entities/) -- Domain kennt nur Value Objects (keine Navigation Properties, kein EF Core) - -**Was NICHT hierher gehört:** -- ? Domain Models (PdfDocument, etc.) ? YAGNI! DTOs reichen! -- ? Constants (ErrorCodes) ? erst wenn mehrfach gebraucht -- ? Services (? Infrastructure) -- ? MediatR (? Application) -- ? JEGLICHE externe Library! - --- -### ?? Tests Layer (DocumentOperator.Tests) - NEU! +### ?? Tests Layer (DocumentOperator.Tests) **Purpose:** Unit & Integration Tests -**References:** -- ? Alle Projekte (API, Application, Infrastructure, Domain) - **Folder Structure:** ``` @@ -811,1332 +828,15 @@ DocumentOperator.Tests/ ? ? ??? ValidatePdfHandlerTests.cs ? ??? Infrastructure/ ? ? ??? Services/ -? ? ??? DevExpressPdfProcessorTests.cs +? ? ??? DevExpressPdfProcessorTests.cs ? ? ??? Domain/ ? ??? ValueObjects/ ? ??? Base64StringTests.cs ??? Integration/ ??? API/ - ??? ValidatePdfEndpointTests.cs + ??? DocumentEndpointsTests.cs ``` -**Test-Strategie:** -- ? TDD (Test-Driven Development) -- ? Unit Tests für Handler (Application Layer) -- ? Unit Tests für Services (Infrastructure Layer) -- ? Unit Tests für Value Objects (Domain Layer) -- ? Integration Tests für Endpoints (API Layer) - ---- - -## ?? DEVELOPMENT PHILOSOPHY - -### Pragmatisch, nicht dogmatisch! - -**Prinzipien:** - -1. **YAGNI (You Ain't Gonna Need It)** - - ? Keine spekulativen Abstraktionen - - ? Keine Klassen "für später" - - ? Erst wenn 2. Use Case es braucht ? dann Abstrahieren - -2. **KISS (Keep It Simple, Stupid)** - - ? Kein Overengineering - - ? Keine unnötigen Design Patterns - - ? Einfachster Code der funktioniert - -3. **Clean Architecture JA, aber pragmatisch** - - ? Dependency Rule einhalten (wichtig!) - - ? Separation of Concerns (wichtig!) - - ? ABER: Nur Abstraktionen die wir wirklich brauchen - -4. **Test-Driven Development (TDD)** - - ? Tests schreiben **bevor** Code (Red ? Green ? Refactor) - - ? Tests als Dokumentation (wie wird es genutzt?) - - ? Tests als Safety Net (Refactoring ohne Angst) - -5. **Outside-In Development** - - ? Von außen nach innen bauen (API ? Service ? Domain) - - ? Wir sehen sofort was funktioniert (kein "spekulatives" Code) - - ? Feedback-Loop schneller - -**Konkret für unser Projekt:** -- Domain Layer **minimal** (nur Enums + Value Objects + Exceptions) -- Keine Domain Models (DTOs in Application reichen!) -- Keine Constants (erst wenn mehrfach gebraucht) -- Iterativ entwickeln (Feature für Feature) -- TDD (Test ? Code ? Refactor) - ---- - -## ??? DEVELOPMENT ROADMAP - -### ? PHASE 1: Foundation - **COMPLETED** - -**Bereits erledigt:** -- [x] Solution erstellt (4 Projekte) -- [x] Dependencies korrekt (Clean Architecture Dependency Rule) -- [x] NuGet Packages installiert -- [x] Folder-Struktur erstellt -- [x] appsettings.json konfiguriert -- [x] Options Pattern Classes erstellt -- [x] Serilog Setup (Program.cs) - ---- - -### ? PHASE 2: Domain Layer (Minimal) - **COMPLETED** - -**Ziel:** Nur was wirklich gebraucht wird! - -**Status:** ? **Alle Steps abgeschlossen!** - ---- - -#### ? Step 2.1: Domain Exceptions erstellen - **COMPLETED** - -**Bereits erstellt:** -1. [x] `DomainException.cs` (Basis-Exception) -2. [x] `DomainValidationException.cs` (Value Object Validierung) -3. [x] `NotFoundException.cs` (Resource nicht gefunden) -4. [x] `PdfProcessingException.cs` (PDF-spezifische Fehler) - -**Wo:** `Domain/Common/Exceptions/` - ---- - -#### ? Step 2.2: Enums erstellen - **COMPLETED** - -**Aufgabe:** Aufzählungen für Business-Konzepte - -**Warum JETZT (vor Value Objects)?** -- Enums haben keine Dependencies -- Werden in Value Objects gebraucht (z.B. PdfMetadata) -- Schneller Erfolg (5 Minuten Arbeit) - -**Was du tun wirst:** - -1. **DocumentOperationType.cs** erstellen - - **Wo:** `Domain/Models/Enums/DocumentOperationType.cs` - - **Inhalt:** - ```csharp - namespace DocumentOperator.Domain.Models.Enums; - - public enum DocumentOperationType - { - Validate, - ExtractAttachments, - Concatenate, - ApplyStamp, - EmbedCertificate - } - ``` - - **Warum:** Definiert welche Operationen unser Service kann - - **Wo gebraucht:** Später in Commands/DTOs - -2. **ProcessingStatus.cs** erstellen - - **Wo:** `Domain/Models/Enums/ProcessingStatus.cs` - - **Inhalt:** - ```csharp - namespace DocumentOperator.Domain.Models.Enums; - - public enum ProcessingStatus - { - Pending, - Processing, - Success, - Failed - } - ``` - - **Warum:** Status für asynchrone Operationen (später: Queue) - - **Wo gebraucht:** Response DTOs - -**Nach diesem Step:** -- Ich prüfe deine Dateien -- Wir haken Step 2.2 ab in ROADMAP.md -- Weiter zu Step 2.3 (Value Objects) - -**Status:** ? **COMPLETED** (17.01.2025) -- ? DocumentOperationType.cs erstellt -- ? ProcessingStatus.cs erstellt -- ? Build erfolgreich - ---- - -#### ? Step 2.3: Value Objects erstellen - **COMPLETED** - -**Aufgabe:** Typsichere, selbst-validierende Wert-Objekte - -**Warum Value Objects?** -- ? Typsicherheit: `Base64String` statt `string` -- ? Validierung an **einer** Stelle (Constructor) -- ? Immutable (keine Änderungen nach Erstellung) -- ? Wiederverwendbar (in Domain, Application, Infrastructure) - -**Was du erstellt hast:** - -1. **Base64String.cs** ? - - Factory Method: `Create(string value)` - - Validierung: Gültiges Base64-Format - - Konvertierung: `ToByteArray()`, `FromByteArray(byte[])` - - Wirft `DomainValidationException` bei Fehler - -2. **TenantId.cs** ? - - Factory Method: `Create(string value)` - - Validierung: Nicht leer, Max 100 Zeichen - - Normalisierung: `.ToLowerInvariant()` - - Wirft `DomainValidationException` bei Fehler - -3. **PdfMetadata.cs** ? - - Properties: PageCount, FileSizeBytes, PdfVersion, HasAttachments, AttachmentCount - - Computed Property: `FileSizeMB` - - Keine Validierung (nur Daten-Container) - -**Wo:** `Domain/Models/ValueObjects/` - -**Status:** ? **COMPLETED** (17.01.2025) -- ? Base64String.cs erstellt (sealed, Factory Methods, Validierung, Equality) -- ? TenantId.cs erstellt (sealed, Normalisierung, Validierung, Equality) -- ? PdfMetadata.cs erstellt (sealed, Computed Property, ToString()) -- ? Build erfolgreich - -**?? Phase 2 (Domain Layer) komplett abgeschlossen!** - ---- - -### ? PHASE 3: Infrastructure Layer (Outside-In!) - **NEXT** - -**Ziel:** DevExpress Services implementieren (wir sehen **echten** Code!) - ---- - -#### ? Step 3.1: IPdfProcessor Interface erstellen - **COMPLETED** - -**Aufgabe:** Abstraction für PDF-Operationen - -**Was du erstellt hast:** -- **Wo:** `Application/Common/Interfaces/IPdfProcessor.cs` ? -- **Inhalt:** - ```csharp - using DocumentOperator.Domain.Models.ValueObjects; - - namespace DocumentOperator.Application.Common.Interfaces; - - public interface IPdfProcessor - { - Task ValidateAsync(byte[] pdfBytes); - } - ``` - -**Warum Interface ERST?** -- Application kennt nur Interface (Dependency Inversion) -- Infrastructure implementiert -- TDD: Test ? Interface ? Implementation - -**Status:** ? **COMPLETED** (17.01.2025) -- ? Interface erstellt mit XML Comments -- ? Using Statement korrekt -- ? Namespace korrekt -- ? Build erfolgreich - ---- - -#### ?? Step 3.2: DevExpressPdfProcessor implementieren (mit TDD!) - **NEXT** - -**Aufgabe:** DevExpress Integration - -**Flow:** -1. **Test schreiben** (Red) - ```csharp - [Fact] - public async Task ValidateAsync_ValidPdf_ReturnMetadata() - { - // Arrange - var processor = new DevExpressPdfProcessor(); - byte[] validPdf = CreateDummyPdf(); - - // Act - var metadata = await processor.ValidateAsync(validPdf); - - // Assert - metadata.PageCount.Should().BeGreaterThan(0); - } - ``` - -2. **Implementation schreiben** (Green) - ```csharp - public class DevExpressPdfProcessor : IPdfProcessor - { - public async Task ValidateAsync(byte[] pdfBytes) - { - using var processor = new PdfDocumentProcessor(); - processor.LoadDocument(pdfBytes); - - return new PdfMetadata( - PageCount: processor.Document.Pages.Count, - FileSizeBytes: pdfBytes.Length, - // ... - ); - } - } - ``` - -3. **Test grün machen** -4. **Refactoring** (falls nötig) - -**Wo:** -- Test: `Tests/Unit/Infrastructure/Services/DevExpressPdfProcessorTests.cs` -- Code: `Infrastructure/Services/PdfProcessing/DevExpressPdfProcessor.cs` - -**Nach diesem Step:** -- Wir haben **echten** Code der mit DevExpress arbeitet! -- Wir wissen welche Exceptions geworfen werden können -- Wir können Exception Middleware bauen - ---- - -### ? PHASE 4: Application Layer (erste Feature) - -**Ziel:** ValidatePdf Feature komplett (Query ? Handler ? Validator) - ---- - -#### ? Step 4.1: MediatR Setup - -**Aufgabe:** MediatR + FluentValidation + ValidationBehavior - -**Was du erstellen wirst:** -1. `DependencyInjection.cs` (Application Layer) -2. `ValidationBehavior.cs` (MediatR Pipeline) - -**Warum jetzt?** -- Wir brauchen MediatR für Handler -- ValidationBehavior = zentrale FluentValidation Ausführung - ---- - -#### ? Step 4.2: ValidatePdf Feature (mit TDD!) - -**Aufgabe:** Erste komplette Feature-Implementierung - -**Was du erstellen wirst:** -1. **ValidatePdfQuery.cs** - ```csharp - public record ValidatePdfQuery(Base64String PdfContent) : IRequest; - ``` - -2. **ValidatePdfHandler.cs** - ```csharp - public class ValidatePdfHandler : IRequestHandler - { - private readonly IPdfProcessor _processor; - - public async Task Handle(ValidatePdfQuery query, CancellationToken ct) - { - byte[] bytes = query.PdfContent.ToByteArray(); - return await _processor.ValidateAsync(bytes); - } - } - ``` - -3. **ValidatePdfValidator.cs** (FluentValidation) - ```csharp - public class ValidatePdfValidator : AbstractValidator - { - public ValidatePdfValidator() - { - RuleFor(x => x.PdfContent).NotNull(); - } - } - ``` - -4. **ValidatePdfHandlerTests.cs** (Unit Test) - -**Wo:** `Application/Features/Documents/ValidatePdf/` - -**Flow:** -``` -DTO ? Query (Value Objects) ? ValidationBehavior (FluentValidation) - ? Handler ? IPdfProcessor ? PdfMetadata -``` - ---- - -### ? PHASE 5: API Layer - -**Ziel:** HTTP Endpoint + Exception Middleware - ---- - -#### ? Step 5.1: Exception Handling Middleware - -**Aufgabe:** Zentrale Exception ? HTTP Response Mapping - -**Was du erstellen wirst:** -- **Wo:** `API/Middleware/ExceptionHandlingMiddleware.cs` -- **Inhalt:** - ```csharp - public class ExceptionHandlingMiddleware - { - public async Task InvokeAsync(HttpContext context) - { - try - { - await _next(context); - } - catch (DomainValidationException ex) - { - await HandleDomainValidationExceptionAsync(context, ex); - } - catch (PdfProcessingException ex) - { - await HandlePdfProcessingExceptionAsync(context, ex); - } - // ... weitere Exceptions - } - - private static Task HandleDomainValidationExceptionAsync(...) - { - context.Response.StatusCode = StatusCodes.Status400BadRequest; - - var problemDetails = new ProblemDetails - { - Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1", - Title = "Validation Error", - Status = 400, - Detail = ex.Message - }; - - return context.Response.WriteAsJsonAsync(problemDetails); - } - } - ``` - -**Warum jetzt?** -- Wir kennen jetzt alle Exceptions (aus Infrastructure Step) -- Wir können sie zu HTTP Status Codes mappen - ---- - -#### ? Step 5.2: Minimal API Endpoint - -**Aufgabe:** HTTP Endpoint für ValidatePdf - -**Was du erstellen wirst:** -- **Wo:** `API/Endpoints/v1/DocumentEndpoints.cs` -- **Inhalt:** - ```csharp - public static class DocumentEndpoints - { - public static void MapDocumentEndpoints(this IEndpointRouteBuilder app) - { - var group = app.MapGroup("/api/v1/documents") - .WithTags("Documents") - .WithOpenApi(); - - group.MapPost("/validate", ValidatePdf); - } - - private static async Task ValidatePdf( - ValidatePdfRequest request, - IMediator mediator, - CancellationToken ct) - { - var query = new ValidatePdfQuery( - Base64String.Create(request.Base64Pdf) - ); - - var result = await mediator.Send(query, ct); - return Results.Ok(result); - } - } - ``` - -**DTOs:** -- `ValidatePdfRequest` (Input) -- `ValidatePdfResponse` (Output) ? oder direkt PdfMetadata? - -**In Program.cs registrieren:** -```csharp -app.MapDocumentEndpoints(); -``` - ---- - -#### ? Step 5.3: Integration Test - -**Aufgabe:** End-to-End Test (HTTP ? Handler ? Service) - -**Was du erstellen wirst:** -- **Wo:** `Tests/Integration/API/ValidatePdfEndpointTests.cs` -- **Inhalt:** - ```csharp - public class ValidatePdfEndpointTests : IClassFixture> - { - [Fact] - public async Task POST_ValidatePdf_ValidPdf_Returns200() - { - // Arrange - var client = _factory.CreateClient(); - var request = new ValidatePdfRequest(Base64Pdf: "..."); - - // Act - var response = await client.PostAsJsonAsync("/api/v1/documents/validate", request); - - // Assert - response.StatusCode.Should().Be(HttpStatusCode.OK); - var metadata = await response.Content.ReadFromJsonAsync(); - metadata.PageCount.Should().BeGreaterThan(0); - } - } - ``` - -**Warum Integration Test?** -- Testet kompletten Flow (HTTP ? MediatR ? Service ? Response) -- Testet Exception Middleware -- Testet Swagger/OpenAPI - ---- - -### ? PHASE 5.5: Health Checks & Resilience - **NEW!** - -**Ziel:** Production-Ready Features FRÜH implementieren - -**Warum JETZT (nicht Phase 9)?** -- Health Checks = **Pflicht** für Production (Load Balancer, Kubernetes) -- Resilience (Polly) = **Pflicht** für externe Dependencies (DevExpress) - ---- - -#### ? Step 5.5.1: Health Checks - -**Aufgabe:** `/health` Endpoint für Liveness/Readiness Probes - -**Was du erstellen wirst:** -1. **DevExpressPdfHealthCheck.cs** - ```csharp - public class DevExpressPdfHealthCheck : IHealthCheck - { - private readonly IPdfProcessor _processor; - - public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken ct) - { - try - { - // Dummy PDF validieren (Smoke Test) - byte[] dummyPdf = CreateMinimalPdf(); - await _processor.ValidateAsync(dummyPdf); - return HealthCheckResult.Healthy("DevExpress PDF OK"); - } - catch (Exception ex) - { - return HealthCheckResult.Unhealthy("DevExpress PDF failed", ex); - } - } - } - ``` - -2. **Program.cs Registration:** - ```csharp - builder.Services.AddHealthChecks() - .AddCheck("devexpress") - .AddCheck("self", () => HealthCheckResult.Healthy()); - - app.MapHealthChecks("/health"); - app.MapHealthChecks("/health/ready"); // Kubernetes Readiness - app.MapHealthChecks("/health/live"); // Kubernetes Liveness - ``` - -**Wo:** `API/HealthChecks/DevExpressPdfHealthCheck.cs` - ---- - -#### ? Step 5.5.2: Polly Resilience - -**Aufgabe:** Retry + Circuit Breaker + Timeout für DevExpress Calls - -**Was du erstellen wirst:** -1. **DevExpressPdfProcessor erweitern (Polly Policies):** - ```csharp - public class DevExpressPdfProcessor : IPdfProcessor - { - private readonly IAsyncPolicy _retryPolicy; - private readonly IAsyncPolicy _circuitBreakerPolicy; - private readonly IAsyncPolicy _timeoutPolicy; - - public DevExpressPdfProcessor() - { - // Retry: 3x mit Exponential Backoff - _retryPolicy = Policy - .Handle() - .WaitAndRetryAsync(3, retryAttempt => - TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); - - // Circuit Breaker: Nach 5 Fehlern für 30 Sekunden öffnen - _circuitBreakerPolicy = Policy - .Handle() - .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)); - - // Timeout: 30 Sekunden max - _timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(30)); - } - - public async Task ValidateAsync(byte[] pdfBytes) - { - // Alle Policies wrappen - return await _retryPolicy.ExecuteAsync(async () => - await _circuitBreakerPolicy.ExecuteAsync(async () => - await _timeoutPolicy.ExecuteAsync(async () => - { - // DevExpress Call - using var processor = new PdfDocumentProcessor(); - processor.LoadDocument(pdfBytes); - // ... - }))); - } - } - ``` - -2. **Logging für Resilience Events:** - ```csharp - _retryPolicy = Policy - .Handle() - .WaitAndRetryAsync(3, - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - onRetry: (exception, timespan, retryCount, context) => - { - Log.Warning("Retry {RetryCount} after {Delay}ms: {Exception}", - retryCount, timespan.TotalMilliseconds, exception.Message); - }); - ``` - -**Warum Polly?** -- ? **Retry:** DevExpress temporäre Fehler (File Lock, Memory) -- ? **Circuit Breaker:** DevExpress kaputt ? alle Requests sofort fehlschlagen (kein Retry-Storm) -- ? **Timeout:** DevExpress hängt ? Request-Timeout (nicht ewig warten) - ---- - -### ? PHASE 6: Weitere Features (iterativ) - -**Nach ValidatePdf (als Referenz):** - -Jedes Feature folgt dem gleichen Pattern: -1. Interface erweitern (IPdfProcessor) -2. Service implementieren (DevExpressPdfProcessor) + Test -3. Command/Query + Handler + Validator -4. Endpoint erstellen -5. Integration Test - -**Features:** -- [ ] ExtractAttachments (synchron) -- [ ] ConcatenatePdfs (asynchron - siehe Phase 6.5!) -- [ ] ApplyStamp (synchron) -- [ ] EmbedCertificate (synchron) - ---- - -### ? PHASE 6.5: Async Processing (In-Memory Queue-based) - **NEW!** - -**Ziel:** Große Operationen asynchron verarbeiten (> 5 Sekunden) - -**Warum?** -- ConcatenatePdfs von 50 PDFs = 10+ Sekunden -- Client wartet nicht ? HTTP Timeout -- Queue-basiert = skalierbar (Background Worker kann parallel verarbeiten) - ---- - -#### ? Step 6.5.1: In-Memory Queue Setup - -**Aufgabe:** Queue für Async Jobs (In-Memory, keine Cloud-Abhängigkeiten) - -**Was du erstellen wirst:** -1. **IJobQueue Interface (Application):** - ```csharp - public interface IJobQueue - { - Task EnqueueAsync(T jobData) where T : class; - Task GetStatusAsync(JobId jobId); - } - - public record JobStatus( - JobId JobId, - ProcessingStatus Status, - int Progress, - string? ResultFilePath, - string? ErrorMessage); - ``` - -2. **InMemoryJobQueue Implementation (Infrastructure):** - ```csharp - public class InMemoryJobQueue : IJobQueue - { - private readonly ConcurrentQueue _queue = new(); - private readonly ConcurrentDictionary _jobStatuses = new(); - - public async Task EnqueueAsync(T jobData) where T : class - { - var jobId = JobId.Create(Guid.NewGuid().ToString()); - - // Job Message in Queue - _queue.Enqueue(new JobData - { - JobId = jobId, - Data = jobData, - Type = typeof(T) - }); - - // Job Status setzen (Pending) - _jobStatuses[jobId.Value] = new JobStatus( - jobId, - ProcessingStatus.Pending, - Progress: 0, - ResultFilePath: null, - ErrorMessage: null - ); - - return jobId; - } - - public Task GetStatusAsync(JobId jobId) - { - _jobStatuses.TryGetValue(jobId.Value, out var status); - return Task.FromResult(status ?? throw new NotFoundException($"Job {jobId.Value} not found")); - } - - public bool TryDequeue(out JobData jobData) - { - return _queue.TryDequeue(out jobData); - } - - public void UpdateStatus(JobId jobId, JobStatus status) - { - _jobStatuses[jobId.Value] = status; - } - } - ``` - ---- - -#### ? Step 6.5.2: Background Worker (Job Processor) - -**Aufgabe:** IHostedService für Job-Verarbeitung - -**Was du erstellen wirst:** -1. **JobProcessorService.cs (Infrastructure/BackgroundServices/):** - ```csharp - public class JobProcessorService : BackgroundService - { - private readonly InMemoryJobQueue _jobQueue; - private readonly IPdfProcessor _pdfProcessor; - private readonly IFileStorage _fileStorage; - - protected override async Task ExecuteAsync(CancellationToken ct) - { - while (!ct.IsCancellationRequested) - { - // Queue Message abrufen - if (_jobQueue.TryDequeue(out var jobData)) - { - try - { - // Job Status: Processing - _jobQueue.UpdateStatus(jobData.JobId, new JobStatus( - jobData.JobId, - ProcessingStatus.Processing, - Progress: 0, - ResultFilePath: null, - ErrorMessage: null - )); - - // PDF-Operation (z.B. Concatenate) - var result = await _pdfProcessor.ConcatenateAsync(jobData.Data.PdfFiles); - - // Ergebnis in lokalem Temp-Ordner speichern - var resultPath = await _fileStorage.SaveAsync(result, $"results/{jobData.JobId}.pdf"); - - // Job Status: Success - _jobQueue.UpdateStatus(jobData.JobId, new JobStatus( - jobData.JobId, - ProcessingStatus.Success, - Progress: 100, - ResultFilePath: resultPath, - ErrorMessage: null - )); - } - catch (Exception ex) - { - // Job Status: Failed - _jobQueue.UpdateStatus(jobData.JobId, new JobStatus( - jobData.JobId, - ProcessingStatus.Failed, - Progress: 0, - ResultFilePath: null, - ErrorMessage: ex.Message - )); - } - } - - await Task.Delay(TimeSpan.FromMilliseconds(100), ct); // Polling-Interval - } - } - } - ``` - -2. **Program.cs Registration:** - ```csharp - builder.Services.AddHostedService(); - ``` - ---- - -#### ? Step 6.5.3: Async Endpoints - -**Aufgabe:** POST ? JobId, GET ? JobStatus - -**Was du erstellen wirst:** -1. **POST /api/v1/documents/concatenate (Async):** - ```csharp - app.MapPost("/api/v1/documents/concatenate", async ( - ConcatenateRequest request, - IJobQueue jobQueue, - CancellationToken ct) => - { - var jobId = await jobQueue.EnqueueAsync(new ConcatenateJobData - { - PdfFiles = request.PdfFiles, - TenantId = tenantContext.TenantId - }); - - return Results.Accepted($"/api/v1/jobs/{jobId}", new { jobId, status = "Pending" }); - }); - ``` - -2. **GET /api/v1/jobs/{jobId}:** - ```csharp - app.MapGet("/api/v1/jobs/{jobId}", async ( - string jobId, - IJobQueue jobQueue, - CancellationToken ct) => - { - var status = await jobQueue.GetStatusAsync(JobId.Create(jobId)); - return Results.Ok(status); - }); - ``` - -**Flow:** -``` -Client: POST /api/v1/documents/concatenate - ? API: { "jobId": "abc123", "status": "Pending" } (HTTP 202 Accepted) - -Background Worker: Verarbeitet Job aus Queue - ? Status-Update: Processing (Progress: 50%) - -Client: GET /api/v1/jobs/abc123 - ? API: { "jobId": "abc123", "status": "Processing", "progress": 50 } - -Background Worker: Job fertig - ? Status-Update: Success (ResultFilePath: "C:\\Temp\\DocumentOperator\\results\\abc123.pdf") - -Client: GET /api/v1/jobs/abc123 - ? API: { "jobId": "abc123", "status": "Success", "resultUrl": "/download/abc123" } - -Client: GET /download/abc123 - ? API: PDF-Datei aus lokalem Temp-Ordner -``` - ---- - -### ? PHASE 7: Swagger & API Documentation - **UPDATED!** - -**Ziel:** Produktionsreife API-Dokumentation - -**Steps:** - -#### ? Step 7.1: Swagger Configuration (API-Key Support) - -**Aufgabe:** API-Key Header in Swagger UI - -**Was du erstellen wirst:** -1. **SwaggerConfiguration.cs:** - ```csharp - builder.Services.AddSwaggerGen(c => - { - // API-Key Security - c.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.ApiKey, - In = ParameterLocation.Header, - Name = "X-API-Key", - Description = "Enter your API Key (from Tenant DB)" - }); - - c.AddSecurityRequirement(new OpenApiSecurityRequirement - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "ApiKey" } - }, - Array.Empty() - } - }); - - // XML Comments - var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; - var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); - c.IncludeXmlComments(xmlPath); - - // Example Schemas - c.SchemaFilter(); - }); - ``` - -2. **XML Comments in Endpoints:** - ```csharp - /// - /// Validates a PDF document and returns metadata - /// - /// PDF as Base64 string - /// PDF metadata (page count, file size, etc.) - /// PDF is valid, metadata returned - /// Invalid PDF or Base64 format - /// Internal server error during validation - app.MapPost("/api/v1/documents/validate", ValidatePdf) - .WithName("ValidatePdf") - .WithTags("Documents") - .WithOpenApi(); - ``` - ---- - -#### ? Step 7.2: Response Examples (Swashbuckle) - -**Aufgabe:** Beispiel-Responses in Swagger - -**Was du erstellen wirst:** -1. **ExampleSchemaFilter.cs:** - ```csharp - public class ExampleSchemaFilter : ISchemaFilter - { - public void Apply(OpenApiSchema schema, SchemaFilterContext context) - { - if (context.Type == typeof(ValidatePdfRequest)) - { - schema.Example = new OpenApiObject - { - ["base64Pdf"] = new OpenApiString("JVBERi0xLjQKJeLjz9MK...") - }; - } - } - } - ``` - ---- - -### ? PHASE 8: Multi-Tenancy (Database-based) - **UPDATED!** - -**Ziel:** Mandantenfähigkeit mit DB + Redis - -**Steps:** - -#### ? Step 8.1: EF Core Setup (SQLite) - -**Aufgabe:** Tenant-Datenbank - -**Was du erstellen wirst:** -1. **TenantDbContext.cs:** - ```csharp - public class TenantDbContext : DbContext - { - public DbSet Tenants { get; set; } - public DbSet TenantSettings { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id); - entity.Property(e => e.Name).IsRequired().HasMaxLength(100); - entity.Property(e => e.ApiKeyHash).IsRequired().HasMaxLength(500); - entity.HasOne(e => e.Settings).WithOne(e => e.Tenant).HasForeignKey(e => e.TenantId); - }); - } - } - ``` - -2. **EF Core Migration:** - ```bash - dotnet ef migrations add InitialCreate --project Infrastructure --startup-project API - dotnet ef database update --project Infrastructure --startup-project API - ``` - ---- - -#### ? Step 8.2: Tenant Resolution Middleware - -**Aufgabe:** API-Key ? Tenant auflösen (mit Redis Cache) - -**Was du erstellen wirst:** -1. **TenantResolutionMiddleware.cs:** - ```csharp - public class TenantResolutionMiddleware - { - private readonly RequestDelegate _next; - private readonly IDistributedCache _cache; // Redis - private readonly ITenantRepository _tenantRepository; - - public async Task InvokeAsync(HttpContext context) - { - var apiKey = context.Request.Headers["X-API-Key"].FirstOrDefault(); - - if (string.IsNullOrEmpty(apiKey)) - { - context.Response.StatusCode = 401; - await context.Response.WriteAsJsonAsync(new { error = "Missing X-API-Key header" }); - return; - } - - // Redis Cache Lookup - var cacheKey = $"tenant:{apiKey}"; - var cachedTenant = await _cache.GetStringAsync(cacheKey); - - Tenant tenant; - if (cachedTenant != null) - { - tenant = JsonSerializer.Deserialize(cachedTenant); - } - else - { - // DB Lookup (BCrypt Hash Vergleich) - tenant = await _tenantRepository.GetByApiKeyAsync(apiKey); - - if (tenant == null) - { - context.Response.StatusCode = 401; - await context.Response.WriteAsJsonAsync(new { error = "Invalid API Key" }); - return; - } - - // Redis Cache befüllen (TTL: 1 Stunde) - await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(tenant), new DistributedCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) - }); - } - - if (!tenant.IsActive) - { - context.Response.StatusCode = 403; - await context.Response.WriteAsJsonAsync(new { error = "Tenant is inactive" }); - return; - } - - // ITenantContext setzen (Scoped Service) - var tenantContext = context.RequestServices.GetRequiredService(); - tenantContext.SetTenant(tenant); - - await _next(context); - } - } - ``` - ---- - -#### ? Step 8.3: Tenant Admin API (CRUD) - -**Aufgabe:** Admin-Endpoints für Tenant-Management - -**Was du erstellen wirst:** -1. **POST /api/v1/admin/tenants (Create Tenant):** - ```csharp - app.MapPost("/api/v1/admin/tenants", async (CreateTenantRequest request, ITenantRepository repo) => - { - var apiKey = GenerateApiKey(); // Zufälliger Key - var apiKeyHash = BCrypt.Net.BCrypt.HashPassword(apiKey); - - var tenant = new Tenant - { - Id = Guid.NewGuid(), - Name = request.Name, - ApiKeyHash = apiKeyHash, - IsActive = true, - CreatedAt = DateTime.UtcNow - }; - - await repo.AddAsync(tenant); - - return Results.Created($"/api/v1/admin/tenants/{tenant.Id}", new - { - tenant.Id, - tenant.Name, - apiKey // Nur EINMAL zurückgeben! (Client muss speichern) - }); - }) - .RequireAuthorization("Admin"); // Nur für Admins! - ``` - ---- - -### ? PHASE 9: File Storage (Lokale Temp-Ordner) - **NEW!** - -**Ziel:** File Storage für Temp-Files, Logos, Zertifikate - -**Warum lokale Temp-Ordner?** -- ? **Einfachheit:** Keine Cloud-Abhängigkeiten -- ? **Schnell:** Lokaler Dateizugriff (keine Netzwerk-Latenz) -- ? **Flexibel:** Bei Bedarf später zu Cloud migrierbar (IFileStorage bleibt!) - ---- - -#### ? Step 9.1: IFileStorage Interface - -**Aufgabe:** Abstraction für File Storage - -**Was du erstellen wirst:** -1. **IFileStorage.cs (Application/Common/Interfaces/):** - ```csharp - public interface IFileStorage - { - Task SaveAsync(byte[] content, string filename); - Task GetAsync(string path); - Task DeleteAsync(string path); - Task ExistsAsync(string path); - } - ``` - ---- - -#### ? Step 9.2: LocalFileStorage Implementation - -**Aufgabe:** Lokaler File Storage Provider - -**Was du erstellen wirst:** -1. **LocalFileStorage.cs (Infrastructure/Services/FileStorage/):** - ```csharp - public class LocalFileStorage : IFileStorage - { - private readonly string _basePath; - - public LocalFileStorage(FileStorageSettings settings) - { - _basePath = settings.TempFolderPath ?? Path.Combine(Directory.GetCurrentDirectory(), "TempFiles"); - Directory.CreateDirectory(_basePath); - } - - public async Task SaveAsync(byte[] content, string filename) - { - var path = Path.Combine(_basePath, filename); - Directory.CreateDirectory(Path.GetDirectoryName(path)!); - await File.WriteAllBytesAsync(path, content); - return path; - } - - public async Task GetAsync(string path) - { - // Pfad kann absolut oder relativ sein - var fullPath = Path.IsPathFullyQualified(path) ? path : Path.Combine(_basePath, path); - return await File.ReadAllBytesAsync(fullPath); - } - - public Task DeleteAsync(string path) - { - var fullPath = Path.IsPathFullyQualified(path) ? path : Path.Combine(_basePath, path); - if (File.Exists(fullPath)) - { - File.Delete(fullPath); - } - return Task.CompletedTask; - } - - public Task ExistsAsync(string path) - { - var fullPath = Path.IsPathFullyQualified(path) ? path : Path.Combine(_basePath, path); - return Task.FromResult(File.Exists(fullPath)); - } - } - ``` - -2. **FileStorageSettings.cs (Infrastructure/Configuration/):** - ```csharp - public class FileStorageSettings - { - public string TempFolderPath { get; set; } = "TempFiles"; // Default: ./TempFiles - public int CleanupIntervalHours { get; set; } = 24; // Default: täglich - public int FileRetentionHours { get; set; } = 24; // Default: 24h Aufbewahrung - } - ``` - -3. **appsettings.json:** - ```json - { - "FileStorage": { - "TempFolderPath": "C:\\Temp\\DocumentOperator", // Windows Pfad - "CleanupIntervalHours": 24, - "FileRetentionHours": 24 - } - } - ``` - - --- - -#### ? Step 9.3: Temp-File Cleanup Service - -**Aufgabe:** IHostedService für tägliche Cleanup - -**Was du erstellen wirst:** -1. **TempFileCleanupService.cs (Infrastructure/BackgroundServices/):** - ```csharp - public class TempFileCleanupService : BackgroundService - { - private readonly IFileStorage _fileStorage; - private readonly FileStorageSettings _settings; - - protected override async Task ExecuteAsync(CancellationToken ct) - { - while (!ct.IsCancellationRequested) - { - // Warten auf nächsten Cleanup-Zeitpunkt - await Task.Delay(TimeSpan.FromHours(_settings.CleanupIntervalHours), ct); - - try - { - // Temp-Files älter als FileRetentionHours löschen - var tempFolderPath = _settings.TempFolderPath; - if (Directory.Exists(tempFolderPath)) - { - var files = Directory.GetFiles(tempFolderPath, "*.*", SearchOption.AllDirectories); - var cutoffTime = DateTime.UtcNow.AddHours(-_settings.FileRetentionHours); - - foreach (var file in files) - { - var fileInfo = new FileInfo(file); - if (fileInfo.CreationTimeUtc < cutoffTime) - { - await _fileStorage.DeleteAsync(file); - Log.Information("Deleted temp file: {Path} (Age: {Hours}h)", - file, (DateTime.UtcNow - fileInfo.CreationTimeUtc).TotalHours); - } - } - } - } - catch (Exception ex) - { - Log.Error(ex, "Error during temp file cleanup"); - } - } - } - } - ``` - ---- - -### ? PHASE 10: Logging & Monitoring - **NEW!** - -**Ziel:** Production-Ready Logging - -**Steps:** - -#### ? Step 10.1: Correlation IDs - -**Aufgabe:** Request-Tracking über alle Logs - -**Was du erstellen wirst:** -1. **CorrelationIdMiddleware.cs:** - ```csharp - public class CorrelationIdMiddleware - { - private readonly RequestDelegate _next; - - public async Task InvokeAsync(HttpContext context) - { - var correlationId = context.Request.Headers["X-Correlation-ID"].FirstOrDefault() - ?? Guid.NewGuid().ToString(); - - context.TraceIdentifier = correlationId; - context.Response.Headers.Add("X-Correlation-ID", correlationId); - - using (LogContext.PushProperty("CorrelationId", correlationId)) - { - await _next(context); - } - } - } - ``` - -2. **LoggingBehavior (MediatR Pipeline):** - ```csharp - public class LoggingBehavior : IPipelineBehavior - { - public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken ct) - { - var requestName = typeof(TRequest).Name; - Log.Information("Handling {RequestName}: {@Request}", requestName, request); - - var stopwatch = Stopwatch.StartNew(); - var response = await next(); - stopwatch.Stop(); - - Log.Information("Handled {RequestName} in {ElapsedMs}ms", requestName, stopwatch.ElapsedMilliseconds); - - return response; - } - } - ``` - ---- - -#### ? Step 10.2: Serilog Configuration (Seq + File) - -**Aufgabe:** Structured Logging Setup - -**Was du erstellen wirst:** -1. **Program.cs Serilog Setup:** - ```csharp - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Information() - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) - .Enrich.FromLogContext() - .Enrich.WithMachineName() - .Enrich.WithEnvironmentName() - .WriteTo.Console() - .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day) - .WriteTo.Seq("http://localhost:5341") // Dev: Seq UI - .CreateLogger(); - ``` - -2. **appsettings.Production.json (File Logging):** - ```json - { - "Serilog": { - "WriteTo": [ - { - "Name": "File", - "Args": { - "path": "C:\\Logs\\DocumentOperator\\log-.txt", - "rollingInterval": "Day" - } - } - ] - } - } - ``` - ---- - -### ? PHASE 11: Production Deployment - **UPDATED!** - -**Ziel:** IIS Deployment + Production Configuration - -**Steps:** -- [ ] appsettings.Production.json (Lokale Temp-Ordner, Redis, File Logging) -- [ ] IIS Web.config (Kestrel Settings) -- [ ] SSL/TLS Configuration -- [ ] Redis Connection String (Production) - OPTIONAL (In-Memory Cache Alternative) -- [ ] Shared Network Drive (bei Multi-Server Setup) - OPTIONAL (lokale Temp-Ordner für Single-Server) -- [ ] Health Checks für Load Balancer -- [ ] Rate-Limiting (Redis-based, pro Tenant) - OPTIONAL (In-Memory für Single-Server) - --- ## ?? TESTING STRATEGY @@ -2183,217 +883,94 @@ Client: GET /download/abc123 --- -### Test-Abdeckung - -**Ziel:** >80% Code Coverage (aber nicht um jeden Preis!) - -**Was testen:** -- ? Value Objects (Validierung) -- ? Handlers (Business Logic) -- ? Services (DevExpress Integration) -- ? Endpoints (HTTP Responses) -- ? Exception Middleware (Error Mapping) - -**Was NICHT testen:** -- ? DTOs (keine Logik) -- ? Enums (keine Logik) -- ? Program.cs (Startup Code) - ---- - -### Test-Naming Convention - -**Pattern:** `MethodName_Scenario_ExpectedResult` - -**Beispiele:** -```csharp -// Value Object Tests -[Fact] -public void Create_EmptyString_ThrowsDomainValidationException() { } - -[Fact] -public void Create_ValidBase64_ReturnsBase64String() { } - -// Handler Tests -[Fact] -public async Task Handle_ValidPdf_ReturnsPdfMetadata() { } - -[Fact] -public async Task Handle_InvalidPdf_ThrowsPdfProcessingException() { } - -// Endpoint Tests -[Fact] -public async Task POST_ValidatePdf_ValidPdf_Returns200() { } - -[Fact] -public async Task POST_ValidatePdf_InvalidPdf_Returns400() { } -``` - ---- - ## ?? CURRENT STATUS ### ? Completed -- **Phase 1:** Foundation & Clean Architecture Setup ? - - Solution Structure ? - - Dependencies ? - - NuGet Packages ? - - Folder Structure ? - - Configuration (appsettings.json) ? - - Serilog Setup ? - - Program.cs Setup ? -- **Phase 2:** Domain Layer (Minimal) ? - - ? Step 2.1 - Domain Exceptions (4 Exceptions erstellt) - - `DomainException.cs` - - `DomainValidationException.cs` - - `NotFoundException.cs` - - `PdfProcessingException.cs` - - ? Step 2.2 - Enums (DocumentOperationType, ProcessingStatus) - - ? Step 2.3 - Value Objects (Base64String, TenantId, PdfMetadata) +- **Foundation & Domain Layer:** + - ? Solution Structure (4 Projekte) + - ? Dependencies (Clean Architecture Dependency Rule) + - ? Domain Exceptions (4 Exceptions) + - ? Enums (DocumentOperationType, ProcessingStatus) + - ? Value Objects (Base64String, TenantId, PdfMetadata) -- **Phase 3:** Infrastructure Layer (Outside-In!) ? - - ? Step 3.1 - IPdfProcessor Interface erstellt - - ? Step 3.2 - DevExpressPdfProcessor implementieren (TDD - **COMPLETED**) - - ? Step 3.2.1 - ProcessDocument Ordner gelöscht (Application Layer cleanup) - - ? Step 3.2.2 - Test-Ordnerstruktur erstellt (Unit/Infrastructure/Services/PdfProcessing) - - ? Step 3.2.3 - Test-PDF Datei hinzugefügt (valid.pdf als Embedded Resource) - - ? Step 3.2.4 - DevExpressPdfProcessorTests.cs erstellt (TDD Red Phase - 6 Tests) - - ? Step 3.2.5 - DevExpressPdfProcessor.cs implementiert (TDD Green Phase) - - ? Step 3.2.6 - Build erfolgreich, Tests bereit zum Ausführen +- **Infrastructure Layer:** + - ? IPdfProcessor Interface + - ? DevExpressPdfProcessor.ValidateAsync (mit Tests!) ### ?? In Progress -- **Phase 4:** Application Layer (MediatR Setup) - - **NEXT:** Step 4.1 - MediatR Setup (DependencyInjection, ValidationBehavior, LoggingBehavior) - - **Status:** Phase 3 abgeschlossen, bereit für Application Layer! + +- **Feature 1: ValidatePDF** + - ? Step 1.1: Application Layer (MediatR Setup + ValidatePDF Feature) - **NEXT** ### ? Pending -- **Phase 4:** Application Layer - - Step 4.1 - MediatR Setup (DependencyInjection.cs, ValidationBehavior.cs, LoggingBehavior.cs) - - Step 4.2 - ValidatePdf Feature (Query, Handler, Validator) +- **Feature 1: ValidatePDF** + - ? Step 1.2: API Layer (Endpoint + Exception Middleware) + - ? Step 1.3: Swagger Dokumentation -- **Phase 5:** API Layer - - Step 5.1 - Exception Handling Middleware - - Step 5.2 - Minimal API Endpoint - - Step 5.3 - Integration Test - -- **Phase 5.5:** Health Checks & Resilience (**NEU!**) - - Step 5.5.1 - Health Checks (DevExpressPdfHealthCheck) - - Step 5.5.2 - Polly Resilience (Retry, Circuit Breaker, Timeout) - -- **Phase 6:** Weitere Features (synchron) - - ExtractAttachments - - ApplyStamp - - EmbedCertificate - -- **Phase 6.5:** Async Processing (**NEU!**) - - Step 6.5.1 - In-Memory Queue Setup (IJobQueue Interface + InMemoryJobQueue) - - Step 6.5.2 - Background Worker (JobProcessorService - IHostedService) - - Step 6.5.3 - Async Endpoints (POST ? JobId, GET ? Status) - -- **Phase 7:** Swagger & API Documentation (erweitert!) - - Step 7.1 - Swagger Configuration (API-Key Support, XML Comments) - - Step 7.2 - Response Examples (Swashbuckle) - -- **Phase 8:** Multi-Tenancy (DB-based!) (**NEU!**) - - Step 8.1 - EF Core Setup (SQLite, Migrations) - - Step 8.2 - Tenant Resolution Middleware (Redis Cache) - - Step 8.3 - Tenant Admin API (CRUD) - -- **Phase 9:** File Storage (**NEU!**) - - Step 9.1 - IFileStorage Interface - - Step 9.2 - LocalFileStorage Implementation (lokale Temp-Ordner) - - Step 9.3 - Temp-File Cleanup Service (IHostedService) - -- **Phase 10:** Logging & Monitoring (**NEU!**) - - Step 10.1 - Correlation IDs (Request-Tracking) - - Step 10.2 - Serilog Configuration (Seq + File Logging) - -- **Phase 11:** Production Deployment - - appsettings.Production.json - - IIS Deployment - - Rate-Limiting (Redis-based) - -### ?? Hinweise zum aktuellen Stand - -1. **Infrastructure Services:** - - Ordner existieren (PdfProcessing, FileStorage, DocumentValidation) - - **Aber:** Alle leer - - ?? **Action:** DevExpressPdfProcessor.cs implementieren (Step 3.2 - IN PROGRESS) - -2. **DevExpress Universal License:** - - ? **Verfügbar!** Wir können alle DevExpress Pakete nutzen - - Aktuell nur: `DevExpress.Pdf.Core` - - Bei Bedarf können weitere Pakete hinzugefügt werden +- **Feature 2-5:** ExtractAttachments, ApplyStamp, EmbedCertificate, ConcatenatePDFs +- **Cross-Cutting Concerns:** Multi-Tenancy, Health Checks, Polly, Logging --- ## ?? KEY LEARNINGS & DECISIONS -### 1. Domain Layer minimal halten +### 1. Feature-Driven Development statt Layer-by-Layer -**Entscheidung:** Nur Enums + Value Objects + Exceptions (ABER: EF Core Entities in Infrastructure!) +**Entscheidung:** Jedes Feature komplett fertig (bis Swagger testbar) + +**Warum:** +- ? Schnellerer Value (Feature 1 nach ~1 Tag fertig!) +- ? Klares Ziel (Swagger testbar = DONE) +- ? Weniger Komplexität (nicht alle Layer parallel) +- ? Besseres Lernen (Pattern wiederholt sich) + +**Alternative wäre gewesen:** +- Domain komplett ? Infrastructure komplett ? Application komplett ? API komplett +- **Nachteile:** Viel Code ohne sichtbares Ergebnis, spekulativ + +--- + +### 2. Domain Layer minimal halten + +**Entscheidung:** Nur Enums + Value Objects + Exceptions **Warum:** - Domain = Business-Konzepte (technologie-unabhängig) -- Tenant/TenantSettings sind **Infrastructure** Entities (EF Core, Navigation Properties) - Service-Anwendung (nicht Domain-lastig) - YAGNI (You Ain't Gonna Need It) **Was bedeutet das?** - Domain/ValueObjects/TenantId.cs ? Value Object (immutable, validierend) -- Infrastructure/Data/Entities/Tenant.cs ? EF Core Entity (mit Navigation Properties) +- Infrastructure/Data/Entities/Tenant.cs ? EF Core Entity (später!) - Domain kennt KEINE EF Core Dependencies! -**Alternative wäre gewesen:** -- Volle Domain Models (PdfDocument, DocumentAttachment, etc.) -- **Nachteile:** Overengineering, unnötige Komplexität +--- + +### 3. Multi-Tenancy NACH allen Features + +**Entscheidung:** Erst alle synchronen Features, dann Multi-Tenancy + +**Warum:** +- ? Multi-Tenancy betrifft ALLE Endpoints +- ? Einmal für alle Features (nicht 5x wiederholen!) +- ? Einfacher zu testen (erst ohne Tenancy, dann mit) + +**Nachteile (akzeptiert):** +- ? Refactoring später nötig (alle Endpoints müssen X-API-Key Header bekommen) +- ? Aber: Aufwand überschaubar (Middleware erledigt das meiste!) --- -### 2. Outside-In Development +### 4. TDD beibehalten -**Entscheidung:** Infrastructure ? Application ? API +**Entscheidung:** Test-First Development **Warum:** -- Wir sehen **echten** Code sofort (DevExpress Integration) -- Keine Spekulation (wir wissen welche Exceptions geworfen werden) -- Schnellerer Feedback-Loop - -**Alternative wäre gewesen:** -- Domain ? Application ? Infrastructure ? API -- **Nachteile:** Viel "spekulativer" Code ohne echte Implementation - ---- - -### 3. Exception-based Error Handling - -**Entscheidung:** Keine Result Pattern Library - -**Warum:** -- Einfacherer Code (kein Result Boilerplate) -- Zentrales Error Handling (Middleware) -- Standard .NET Exception-Flow - -**Alternative wäre gewesen:** -- Ardalis.Result oder FluentResults -- **Nachteile:** Extra Package, mehr Boilerplate - ---- - -### 4. TDD (Test-Driven Development) - -**Entscheidung:** Test ? Code ? Refactor - -**Warum:** -- Besseres Design (testbarer Code) -- Tests als Dokumentation -- Safety Net für Refactoring - -**Alternative wäre gewesen:** -- Code ? Test (Test-After) -- **Nachteile:** Tests werden oft vergessen, schlechteres Design +- ? Besseres Design (testbarer Code) +- ? Tests als Dokumentation +- ? Safety Net für Refactoring --- @@ -2402,213 +979,25 @@ public async Task POST_ValidatePdf_InvalidPdf_Returns400() { } **Entscheidung:** Pro Feature alles zusammen **Warum:** -- Zusammengehöriger Code ist zusammen -- Einfacher zu finden und zu ändern -- Besser für Teams (weniger Merge-Konflikte) - -**Alternative wäre gewesen:** -- Horizontal Layers (Commands/, Handlers/, Validators/) -- **Nachteile:** Code über viele Ordner verteilt - ---- - -### 6. Multi-Tenancy: Database-based (statt appsettings.json) - **NEU!** - -**Entscheidung:** EF Core + SQLite für Tenant-Management - -**Warum:** -- **Skalierbar:** Neue Tenants ohne Neustart -- **Security:** API-Key Hashing (BCrypt, NICHT Klartext!) -- **Audit-Log:** LastUsedAt pro Tenant -- **Rate-Limiting:** Redis Counter pro Tenant -- **Redis Cache:** API-Key Lookups cached (Performance!) - -**Alternative wäre gewesen:** -- appsettings.json mit API-Keys -- **Nachteile:** Nicht skalierbar, keine Rotation, kein Audit-Log - ---- - -### 7. Async Processing: In-Memory Queue-based (für große Operationen) - **NEU!** - -**Entscheidung:** In-Memory Queue + Background Worker - -**Warum:** -- ConcatenatePdfs von 50 PDFs = 10+ Sekunden -- Synchron = HTTP Timeout -- In-Memory Queue = einfach, keine Cloud-Abhängigkeiten -- ProcessingStatus Enum wird jetzt genutzt! - -**Alternative wäre gewesen:** -- Alle Operationen synchron -- **Nachteile:** HTTP Timeouts, nicht skalierbar - -**Hinweis:** -- In-Memory Queue = nicht persistent (bei Server-Neustart gehen Jobs verloren) -- Für Production später: Redis Queue oder RabbitMQ (aber IJobQueue bleibt!) -- Für Single-Server Setup: In-Memory Queue ausreichend - ---- - -### 8. File Storage: Lokale Temp-Ordner (statt Cloud) - **NEU!** - -**Entscheidung:** IFileStorage Interface + LocalFileStorage - -**Warum:** -- **Einfachheit:** Keine Cloud-Abhängigkeiten (Azure Blob Storage) -- **Schnell:** Lokaler Dateizugriff (keine Netzwerk-Latenz) -- **Flexibel:** IFileStorage Abstraction bleibt (später zu Cloud migrierbar!) -- **TempFileCleanupService:** Automatische Cleanup (täglich) - -**Alternative wäre gewesen:** -- Cloud Storage (Azure Blob, AWS S3) -- **Nachteile:** Cloud-Abhängigkeit, Kosten, Komplexität - -**Hinweis:** -- Für Multi-Server Setup: Shared Network Drive (UNC-Pfad) statt lokale Ordner -- IFileStorage Interface bleibt gleich (austauschbar!) - ---- - -### 9. Resilience: Polly (Retry, Circuit Breaker, Timeout) - **NEU!** - -**Entscheidung:** Polly für DevExpress Calls - -**Warum:** -- **Retry:** Transient Errors (File Lock, Memory) -- **Circuit Breaker:** DevExpress kaputt ? alle Requests sofort fehlschlagen -- **Timeout:** DevExpress hängt ? nicht ewig warten - -**Alternative wäre gewesen:** -- Keine Resilience -- **Nachteile:** Produktionsausfälle bei transient errors - ---- - -### 10. Health Checks: FRÜH implementieren (Phase 5.5, nicht Phase 9) - **NEU!** - -**Entscheidung:** Health Checks direkt nach erstem Endpoint - -**Warum:** -- **Pflicht** für Production (Load Balancer, Kubernetes) -- `/health` Endpoint = Liveness/Readiness Probes -- DevExpressPdfHealthCheck = Smoke Test (PDF validieren) - -**Alternative wäre gewesen:** -- Health Checks in Phase 9 (Production-Ready) -- **Nachteile:** Zu spät! Load Balancer braucht Health Checks sofort - ---- - -### 11. Logging: Correlation IDs + Seq + File Logging - **NEU!** - -**Entscheidung:** Structured Logging mit Correlation IDs - -**Warum:** -- **Correlation IDs:** Request-Tracking über alle Logs (Debugging leichter) -- **Seq:** Log-Browsing UI (Development) -- **File Logging:** Production Logs (keine Cloud-Abhängigkeit) -- **LoggingBehavior:** MediatR Pipeline Behavior (automatisches Logging) - -**Alternative wäre gewesen:** -- Nur Console Logging (keine Correlation IDs) -- **Nachteile:** Debugging schwierig, keine Request-Zusammenhänge, keine persistente Log-Speicherung - -### 5. Vertical Slice Architecture - -**Entscheidung:** Pro Feature alles zusammen - -**Warum:** -- Zusammengehöriger Code ist zusammen -- Einfacher zu finden und zu ändern -- Besser für Teams (weniger Merge-Konflikte) - -**Alternative wäre gewesen:** -- Horizontal Layers (Commands/, Handlers/, Validators/) -- **Nachteile:** Code über viele Ordner verteilt - ---- - -## ?? REFERENCES & BEST PRACTICES - -### Documentation - -- [Clean Architecture (Uncle Bob)](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) -- [MediatR Documentation](https://github.com/jbogard/MediatR) -- [FluentValidation Docs](https://docs.fluentvalidation.net/) -- [DevExpress PDF API](https://docs.devexpress.com/OfficeFileAPI/114877/pdf-document-api) -- [ASP.NET Core Minimal APIs](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis) -- [RFC 7807 Problem Details](https://datatracker.ietf.org/doc/html/rfc7807) -- [xUnit Documentation](https://xunit.net/) -- [FluentAssertions Documentation](https://fluentassertions.com/) -- [Polly Documentation](https://www.pollydocs.org/) - **NEU:** Resilience Patterns -- [EF Core Documentation](https://learn.microsoft.com/en-us/ef/core/) - **NEU:** ORM für Tenant-DB -- [Serilog Documentation](https://serilog.net/) - **NEU:** Structured Logging -- [Seq Documentation](https://docs.datalust.co/docs) - **NEU:** Log Browsing UI - -### Best Practices Applied - -- ? Clean Architecture (pragmatisch!) -- ? CQRS with MediatR -- ? Vertical Slice Architecture -- ? Value Objects (DDD) -- ? Exception-based Error Handling -- ? Minimal APIs (.NET 8) -- ? TDD (Test-Driven Development) -- ? **Resilience Patterns (Polly)** - **NEU:** Retry, Circuit Breaker, Timeout -- ? **Multi-Tenancy (Database-based)** - **NEU:** EF Core + SQLite + Redis Cache -- ? **Async Processing (In-Memory Queue)** - **NEU:** In-Memory Queue + Background Worker -- ? **File Storage Abstraction (Lokale Temp-Ordner)** - **NEU:** IFileStorage + LocalFileStorage -- ? **Correlation IDs** - **NEU:** Request-Tracking über alle Logs -- ? **Health Checks** - **NEU:** Load Balancer Support -- ? Options Pattern für Configuration -- ? Dependency Injection -- ? Async/Await überall -- ? Nullable Reference Types -- ? Record Types für DTOs (C# 12) -- ? Primary Constructors (.NET 8) -- ? Structured Logging (Serilog + Seq + File Logging) +- ? Zusammengehöriger Code ist zusammen +- ? Einfacher zu finden und zu ändern +- ? Besser für Teams (weniger Merge-Konflikte) --- ## ?? UPDATE LOG -| Date | Phase | Changes | -|------|-------|---------| -| 2024-XX-XX | Phase 1 | Project setup, dependencies, folder structure | -| 2024-XX-XX | Phase 1 | Configuration, Serilog, Options Pattern | -| 2024-XX-XX | Phase 1 | ? Phase 1 completed | -| 2024-XX-XX | Phase 2 | ? Step 2.1 completed - Domain Exceptions created | -| 17.01.2025 | Roadmap | ?? **ROADMAP komplett überarbeitet** (Pragmatisch, Outside-In, TDD) | -| 17.01.2025 | Phase 2 | ? Step 2.2 completed - Enums erstellt | -| 17.01.2025 | Phase 2 | ? Step 2.3 completed - Value Objects erstellt | -| 17.01.2025 | Phase 2 | ? **Phase 2 (Domain Layer) komplett abgeschlossen!** | -| 17.01.2025 | Phase 3 | ? Step 3.1 completed - IPdfProcessor Interface erstellt | -| 17.01.2025 | Roadmap | ?? **ROADMAP Status-Update** - Aktueller Projektstand dokumentiert | -| 17.01.2025 | Infrastructure | ?? **DevExpress Universal License** hinzugefügt - Vollzugriff auf alle Pakete | -| 17.01.2025 | Phase 3 | ?? **Step 3.2 gestartet** - DevExpressPdfProcessor (TDD) | -| 17.01.2025 | Application | ? Step 3.2.1 - ProcessDocument Ordner gelöscht (Cleanup) | -| 17.01.2025 | Tests | ? Step 3.2.2 - Test-Ordnerstruktur erstellt, UnitTest1.cs gelöscht | -| 17.01.2025 | Tests | ? Step 3.2.3 - Test-PDF (valid.pdf) als Embedded Resource hinzugefügt | -| 17.01.2025 | Tests | ? Step 3.2.4 - DevExpressPdfProcessorTests.cs erstellt (TDD Red - 6 Tests) | -| 17.01.2025 | Infrastructure | ? Step 3.2.5 - DevExpressPdfProcessor.cs implementiert (TDD Green Phase) | -| 17.01.2025 | Phase 3 | ? **Step 3.2 abgeschlossen** - DevExpressPdfProcessor vollständig implementiert! | -| 17.01.2025 | Roadmap | ?? **ROADMAP Status-Update** - Phase 3 abgeschlossen, bereit für Phase 4! | -| 17.01.2025 | Roadmap | ?? **ROADMAP MAJOR UPDATE** - Production-Ready Features hinzugefügt! | -| 17.01.2025 | Architecture | ? **Multi-Tenancy:** Database-based (EF Core + SQLite + Redis Cache) | -| 17.01.2025 | Architecture | ? **Async Processing:** In-Memory Queue-based + Background Worker | -| 17.01.2025 | Architecture | ? **File Storage:** Lokale Temp-Ordner mit IFileStorage Abstraction | -| 17.01.2025 | Architecture | ? **Resilience:** Polly (Retry, Circuit Breaker, Timeout) | -| 17.01.2025 | Architecture | ? **Health Checks:** Früh implementieren (Phase 5.5 statt Phase 9) | -| 17.01.2025 | Architecture | ? **Logging:** Correlation IDs + Seq + File Logging | -| 17.01.2025 | Technology Stack | ? **9 neue NuGet Packages hinzugefügt** (EF Core, Polly, BCrypt, Seq - OHNE Azure) | -| 17.01.2025 | Roadmap | ? **6 neue Phasen** (5.5, 6.5, 8, 9, 10, 11) - insgesamt 11 Phasen statt 9 | -| 17.01.2025 | Documentation | ? **10 Key Learnings & Decisions** dokumentiert (statt 5) | -| 17.01.2025 | Roadmap | ?? **Azure Services entfernt** - Lokale Temp-Ordner + In-Memory Queue stattdessen -| 22.06.2026 | Dokumentation | ? **Azure-Referenzen vollständig entfernt** - Alle Azure-Referenzen bereinigt (PROJECT_STATUS.md + ROADMAP.md) +| Date | Feature/Step | Changes | +|------|--------------|---------| +| 2024-XX-XX | Foundation | Project setup, dependencies, folder structure | +| 2024-XX-XX | Domain Layer | Exceptions, Enums, Value Objects | +| 17.01.2025 | Infrastructure | DevExpressPdfProcessor.ValidateAsync implementiert | +| 17.01.2025 | Tests | DevExpressPdfProcessorTests.cs erstellt (6 Tests) | +| 17.01.2025 | **ROADMAP** | ?? **Komplett umstrukturiert** (Feature-Driven Development!) | +| 17.01.2025 | **PHASENPLAN** | ?? **Komplett umstrukturiert** (Feature-basiert + Datum korrigiert) | --- **END OF ROADMAP** -*This document is a living document and will be updated as development progresses.* +*This document is a living document and will be updated after each completed step.*