diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..1556cfd --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,1638 @@ +# ?? DocumentOperator - Project Roadmap + +> **Last Updated:** 2024 | **Status:** In Development | **Phase:** 2 (Domain Layer) + +--- + +## ?? TABLE OF CONTENTS + +1. [Project Overview](#project-overview) +2. [Architecture & Design Decisions](#architecture--design-decisions) +3. [Technology Stack](#technology-stack) +4. [Project Structure](#project-structure) +5. [Development Roadmap](#development-roadmap) +6. [Current Status](#current-status) + +--- + +## ?? PROJECT OVERVIEW + +### Vision & Purpose + +**DocumentOperator** ist ein zentralisierter REST API Service für PDF-Dokumenten-Operationen in einer Multi-Tenant DMS-Umgebung. + +### Problem Statement + +**Aktuell:** +- Verschiedene DMS-Kunden bei unterschiedlichen Mandanten +- Jede Anwendung implementiert PDF-Operationen redundant +- Keine zentrale Stelle für Dokumenten-Verarbeitung +- Wartungsaufwand multipliziert sich mit jeder Anwendung + +**Lösung:** +- **Ein** zentraler Service für alle PDF-Operationen +- Wiederverwendbar über HTTP REST API +- Mandantenfähig (Multi-Tenancy) +- 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) + +#### 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 + +``` +Client Application + ? +[HTTP Request] - JSON mit Base64-PDF + ? +DocumentOperator API + ? +[Validierung] ? [Operation(en)] ? [Ergebnis] + ? +[HTTP Response] - JSON mit verarbeitetem PDF (Base64) +``` + +**Typischer Ablauf:** +1. Client sendet PDF als Base64 in JSON +2. API validiert Input (FluentValidation) +3. PDF wird in Byte-Array konvertiert +4. Operationen werden durchgeführt (DevExpress) +5. Temporäre Dateien werden erstellt/bereinigt +6. Ergebnis wird als Base64 zurückgegeben + +--- + +## ??? ARCHITECTURE & DESIGN DECISIONS + +### Clean Architecture + +Wir verwenden **Clean Architecture** mit 4 Layers: + +``` +??????????????????????????????????????? +? API Layer (Endpoints) ? ? HTTP Entry Point +??????????????????????????????????????? +? Application Layer (Use Cases) ? ? Business Logic Orchestration +??????????????????????????????????????? +? Infrastructure Layer (Tech Stack) ? ? DevExpress, File I/O, Redis +??????????????????????????????????????? +? Domain Layer (Core Logic) ? ? Business Rules, Models +??????????????????????????????????????? +``` + +#### Dependency Rule (Kritisch!) + +**Abhängigkeiten zeigen immer nach innen:** + +``` +API ? Application ? Domain +API ? Infrastructure ? Domain +Infrastructure ? Application + +Domain ? NICHTS! (No External Dependencies) +Application ? NUR Domain +``` + +**Warum?** +- Domain = reine Geschäftslogik, technologie-unabhängig +- Application = Use Cases, kennt nur Interfaces +- Infrastructure = technische Details, austauschbar +- API = dünne Schicht, nur Routing + +--- + +### CQRS with MediatR + +**Pattern:** Command Query Responsibility Segregation + +**Warum MediatR?** +- ? Klare Trennung: 1 Command/Query = 1 Handler +- ? Single Responsibility Principle +- ? Pipeline Behaviors (Validation, Logging, etc.) +- ? Bessere Testbarkeit +- ? Keine aufgeblähten Service-Klassen + +**Statt:** +```csharp +public class DocumentService { + public void Process() { } + public void Validate() { } + public void Extract() { } + // ... 20 Methoden +} +``` + +**Nutzen wir:** +```csharp +// Feature: ProcessDocument +public class ProcessDocumentCommand : IRequest { } +public class ProcessDocumentHandler : IRequestHandler { } +public class ProcessDocumentValidator : AbstractValidator { } +``` + +--- + +### Vertical Slice Architecture + +**Statt Horizontal Layers** (Commands/, Handlers/, Validators/): + +**Nutzen wir Vertical Slices** (pro Feature alles zusammen): + +``` +Features/ + ??? ProcessDocument/ + ? ??? ProcessDocumentCommand.cs + ? ??? ProcessDocumentHandler.cs + ? ??? ProcessDocumentValidator.cs + ??? ExtractAttachments/ + ? ??? ExtractAttachmentsCommand.cs + ? ??? ExtractAttachmentsHandler.cs + ? ??? ExtractAttachmentsValidator.cs +``` + +**Vorteile:** +- ? Zusammengehöriger Code ist zusammen +- ? Einfacher zu finden und zu ändern +- ? Feature-basierte Organisation (nicht technische Schichten) +- ? Besser für Teams (weniger Merge-Konflikte) + +--- + +### Exception-based Error Handling (Zentral!) + +**Entscheidung:** Keine Result Pattern Library (Ardalis.Result entfernt) + +**Stattdessen:** +1. **Domain Exceptions** für fachliche Fehler +2. **FluentValidation** für Input-Validierung +3. **Zentrale Exception Handling Middleware** im API Layer + +**Warum Exception-basiert?** +- ? Einfacherer Code (kein `if (result.IsSuccess)` überall) +- ? Weniger Boilerplate +- ? Ein Package weniger (keine Extra-Lib) +- ? **Zentrales Error Handling** = bessere Wartbarkeit +- ? Standard .NET Exception-Flow + +**Flow:** +``` +Request ? Validation (FluentValidation Behavior) + ? + Handler (wirft Exception bei Fehler) + ? + Middleware (fängt Exception, mappt zu HTTP Code) + ? + Response (Problem Details RFC 7807) +``` + +--- + +### Minimal APIs (statt Controllers) + +**Warum Minimal APIs?** +- ? .NET 8 Best Practice +- ? Weniger Boilerplate (keine Controller-Klassen) +- ? Direkte Endpoint-Definition +- ? Swagger funktioniert 1:1 +- ? Bessere Performance +- ? Moderner, funktionaler Stil + +**Beispiel:** +```csharp +app.MapPost("/api/v1/documents/process", async ( + ProcessDocumentRequest request, + IMediator mediator) => +{ + var command = new ProcessDocumentCommand(request); + var result = await mediator.Send(command); + return Results.Ok(result); +}) +.WithName("ProcessDocument") +.WithOpenApi(); +``` + +--- + +### Multi-Tenancy via API-Keys + +**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` +- Middleware resolved API-Key ? Tenant-Context +- Tenant-spezifische Einstellungen (Logo für Stamps, Zertifikat, etc.) + +**Warum API-Keys?** +- ? Einfach für Service-to-Service Communication +- ? Security + Tenant-Identification kombiniert +- ? Swagger-kompatibel (für BB-Tests) +- ? Einfaches Rate-Limiting pro Tenant + +**Flow:** +``` +Request mit Header "X-API-Key: abc123" + ? +TenantResolutionMiddleware + ? +API-Key ? Tenant-Konfiguration + ? +ITenantContext (Scoped DI) + ? +Handler nutzt Tenant-Settings +``` + +--- + +## ??? TECHNOLOGY STACK + +### Core Framework + +| Technology | Version | Purpose | +|------------|---------|---------| +| **.NET** | 8.0 | Runtime & Framework | +| **ASP.NET Core** | 8.0 | Web API | +| **C#** | 12 | Language (mit Primary Constructors, Record Types) | + +--- + +### Key Libraries & Packages + +#### API Layer + +| Package | Version | Purpose | +|---------|---------|---------| +| **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.Enrichers.Environment** | 3.0.1 | Log-Enrichment (MachineName, etc.) | +| **Asp.Versioning.Http** | 8.1.1 | API Versioning (/api/v1/, /api/v2/) | +| **Microsoft.Extensions.Caching.StackExchangeRedis** | 8.0.28 | Redis Distributed Cache | + +#### Application Layer + +| Package | Version | Purpose | +|---------|---------|---------| +| **MediatR** | 14.1.0 | CQRS Pattern Implementation | +| **FluentValidation** | 12.1.1 | Input Validation | +| **FluentValidation.DependencyInjectionExtensions** | 12.1.1 | DI Integration | +| ~~Ardalis.Result~~ | ~~10.1.0~~ | ? **ENTFERNT** (Exception-basiert stattdessen) | + +#### Infrastructure Layer + +| Package | Version | Purpose | +|---------|---------|---------| +| **DevExpress.Pdf.Core** | 25.2.8 | PDF-Operationen (Merge, Extract, Sign, etc.) | +| **Microsoft.Extensions.Options.ConfigurationExtensions** | 8.0.0 | Options Pattern | + +#### Domain Layer + +| Package | Version | Purpose | +|---------|---------|---------| +| - | - | **Keine Dependencies!** (Clean Architecture) | + +--- + +### Infrastructure Components + +| Component | Technology | Purpose | +|-----------|------------|---------| +| **Hosting** | IIS | Production Deployment | +| **Cache** | Redis | Distributed Cache (API-Keys, Tenant-Settings) | +| **Message Queue** | (Future) RabbitMQ/Azure Service Bus | Async Processing für große PDFs | +| **Logging** | Serilog ? File/Console | Strukturiertes Logging | +| **Temp Storage** | Local File System | Temporäre PDF-Dateien (später: Blob Storage) | + +--- + +## ?? PROJECT STRUCTURE + +### Solution Overview + +``` +DocumentOperator/ +??? DocumentOperator.API/ ? HTTP Entry Point +??? DocumentOperator.Application/ ? Use Cases (MediatR Handlers) +??? DocumentOperator.Infrastructure/? Technical Implementations +??? DocumentOperator.Domain/ ? Core Business Logic +??? ROADMAP.md ? This file +``` + +--- + +### ?? API Layer (DocumentOperator.API) + +**Purpose:** HTTP Entry Point, Routing, Middleware + +**References:** +- ? Application +- ? Infrastructure +- ? Domain + +**NuGet Packages:** +- Swashbuckle.AspNetCore (Swagger) +- Serilog.AspNetCore + Sinks +- Asp.Versioning.Http +- Microsoft.Extensions.Caching.StackExchangeRedis + +**Folder Structure:** + +``` +DocumentOperator.API/ +??? Endpoints/ +? ??? v1/ +? ??? DocumentEndpoints.cs ? Minimal API Endpoints +??? Middleware/ +? ??? ExceptionHandlingMiddleware.cs ? Zentrale Exception Handling ? +? ??? TenantResolutionMiddleware.cs ? API-Key ? Tenant +? ??? RequestLoggingMiddleware.cs ? Request/Response Logging +??? Configuration/ +? ??? SwaggerConfiguration.cs ? Swagger Setup (API-Key Support) +? ??? SerilogConfiguration.cs ? Serilog Helper +??? appsettings.json ? Base Configuration +??? appsettings.Development.json ? Dev Overrides +??? Program.cs ? Application Entry Point +``` + +**Was gehört hierher?** +- ? HTTP Routing (Minimal APIs) +- ? Middleware (Exception, Auth, Logging) +- ? Swagger Configuration +- ? Dependency Injection Setup +- ? appsettings.json + +**Was NICHT hierher gehört?** +- ? Business Logic (? Application/Domain) +- ? PDF-Verarbeitung (? Infrastructure) +- ? Validierung (? Application: FluentValidation) + +--- + +### ?? Application Layer (DocumentOperator.Application) + +**Purpose:** Use Cases, Business Logic Orchestration + +**References:** +- ? Domain (ONLY!) + +**NuGet Packages:** +- MediatR +- FluentValidation + DI Extensions + +**Folder Structure:** + +``` +DocumentOperator.Application/ +??? Features/ ? Vertical Slices +? ??? Documents/ +? ??? ProcessDocument/ +? ? ??? ProcessDocumentCommand.cs +? ? ??? ProcessDocumentHandler.cs +? ? ??? ProcessDocumentValidator.cs +? ??? ValidatePdf/ +? ? ??? ValidatePdfQuery.cs +? ? ??? ValidatePdfHandler.cs +? ? ??? ValidatePdfValidator.cs +? ??? ExtractAttachments/ +? ? ??? ExtractAttachmentsCommand.cs +? ? ??? ExtractAttachmentsHandler.cs +? ? ??? ExtractAttachmentsValidator.cs +? ??? ConcatenatePdfs/ +? ? ??? ConcatenatePdfsCommand.cs +? ? ??? ConcatenatePdfsHandler.cs +? ? ??? ConcatenatePdfsValidator.cs +? ??? ApplyStamp/ +? ? ??? ApplyStampCommand.cs +? ? ??? ApplyStampHandler.cs +? ? ??? ApplyStampValidator.cs +? ??? EmbedCertificate/ +? ??? EmbedCertificateCommand.cs +? ??? EmbedCertificateHandler.cs +? ??? EmbedCertificateValidator.cs +??? Common/ +? ??? Interfaces/ ? Abstractions für Infrastructure +? ? ??? IPdfProcessor.cs +? ? ??? IFileStorageService.cs +? ? ??? IDocumentValidator.cs +? ? ??? ICertificateService.cs +? ??? Behaviors/ ? MediatR Pipeline Behaviors +? ? ??? ValidationBehavior.cs ? FluentValidation Integration ? +? ? ??? LoggingBehavior.cs +? ? ??? ExceptionLoggingBehavior.cs +? ??? DTOs/ ? Data Transfer Objects +? ? ??? ProcessDocumentRequest.cs +? ? ??? ProcessDocumentResponse.cs +? ? ??? DocumentOperationDto.cs +? ? ??? AttachmentDto.cs +? ? ??? ErrorResponse.cs ? API Error Format +? ??? Mappings/ ? Domain ? DTO +? ??? MappingExtensions.cs +??? DependencyInjection.cs ? Service Registration +``` + +**Was gehört hierher?** +- ? MediatR Commands & Queries +- ? Handlers (orchestrieren Domain + Infrastructure) +- ? FluentValidation Validators +- ? DTOs (API Contracts) +- ? Interfaces für Infrastructure (Dependency Inversion!) +- ? Pipeline Behaviors + +**Was NICHT hierher gehört?** +- ? DevExpress-spezifischer Code (? Infrastructure) +- ? File I/O (? Infrastructure) +- ? HTTP-spezifisches (? API) +- ? EF Core / Database (haben wir nicht) + +**Warum keine Infrastructure-Referenz?** +- Clean Architecture: Application kennt nur **Interfaces** (`IPdfProcessor`) +- Infrastructure **implementiert** die Interfaces (`DevExpressPdfProcessor`) +- API injected die Implementierung via DI +- ? Application bleibt technologie-unabhängig! + +--- + +### ?? Infrastructure Layer (DocumentOperator.Infrastructure) + +**Purpose:** Technische Implementierungen, externe Abhängigkeiten + +**References:** +- ? Application (für Interfaces) +- ? Domain + +**NuGet Packages:** +- DevExpress.Pdf.Core +- Microsoft.Extensions.Options.ConfigurationExtensions + +**Folder Structure:** + +``` +DocumentOperator.Infrastructure/ +??? Services/ +? ??? PdfProcessing/ +? ? ??? DevExpressPdfProcessor.cs ? IPdfProcessor Implementation +? ??? FileStorage/ +? ? ??? LocalFileStorageService.cs ? IFileStorageService Implementation +? ??? DocumentValidation/ +? ??? PdfDocumentValidator.cs ? IDocumentValidator Implementation +??? Configuration/ +? ??? DocumentOperatorSettings.cs ? Options Pattern Class +? ??? RedisSettings.cs +? ??? ApiKeySettings.cs +? ??? TenantInfo.cs +??? DependencyInjection.cs ? Service Registration +``` + +**Was gehört hierher?** +- ? DevExpress Integration +- ? File System Zugriffe (Temp-Files) +- ? Redis Client (später) +- ? Externe API Calls (falls benötigt) +- ? Options Pattern Classes + +**Was NICHT hierher gehört?** +- ? Business Logic (? Application/Domain) +- ? HTTP Handling (? API) +- ? Validierung von Inputs (? Application) + +**Beispiel - DevExpressPdfProcessor:** +```csharp +public class DevExpressPdfProcessor : IPdfProcessor +{ + public async Task MergePdfsAsync(List pdfs) + { + using var processor = new PdfDocumentProcessor(); // DevExpress! + // ... DevExpress-spezifischer Code + + if (error) + throw new PdfProcessingException("Merge failed"); // Exception! + + return result; + } +} +``` + +--- + +### ??? Domain Layer (DocumentOperator.Domain) + +**Purpose:** Kern-Geschäftslogik, Business Rules + +**References:** +- ? **KEINE!** (wichtigste Clean Architecture Regel) + +**NuGet Packages:** +- **KEINE!** (reine C# Klassen) + +**Folder Structure:** + +``` +DocumentOperator.Domain/ +??? Models/ +? ??? PdfDocument.cs ? Core Business Model +? ??? DocumentAttachment.cs +? ??? DocumentStamp.cs +? ??? DocumentCertificate.cs +??? Models/ValueObjects/ ? Immutable, selbst-validierend +? ??? Base64String.cs ? Wirft DomainValidationException +? ??? TenantId.cs +? ??? PdfMetadata.cs +??? Models/Enums/ +? ??? DocumentOperationType.cs ? Extract, Concatenate, Stamp, Sign +? ??? ProcessingStatus.cs ? Pending, Processing, Success, Failed +? ??? PdfValidationError.cs ? InvalidFormat, TooLarge, Corrupted +??? Common/ +? ??? Exceptions/ ? Domain-spezifische Exceptions +? ??? DomainException.cs ? Basis-Exception +? ??? DomainValidationException.cs? Value Object Validierung +? ??? NotFoundException.cs ? Resource nicht gefunden +? ??? PdfProcessingException.cs ? PDF-spezifische Fehler +??? Constants/ + ??? ErrorCodes.cs ? Konstanten für Error Messages + ??? ValidationMessages.cs +``` + +**Was gehört hierher?** +- ? Business Models (PdfDocument, etc.) +- ? Value Objects (Base64String, TenantId) +- ? Enums (DocumentOperationType) +- ? Business Rules (z.B. "Max 100 Seiten") +- ? Domain Exceptions +- ? Constants + +**Was NICHT hierher gehört?** +- ? DevExpress (? Infrastructure) +- ? MediatR (? Application) +- ? DTOs (? Application) +- ? Validation Logic (? Application: FluentValidation) +- ? JEGLICHE externe Library! + +**Warum keine Dependencies?** +- Domain = Herz der Anwendung +- Sollte **ewig** leben (auch wenn Tech-Stack wechselt) +- Keine Abhängigkeit von Frameworks = langlebig +- Pure C# Business Logic + +--- + +## ??? DEVELOPMENT ROADMAP + +--- + +### ? **PHASE 1: Foundation & Clean Architecture Setup** - **COMPLETED** + +**Ziel:** Saubere Architektur-Basis ohne Funktionalität + +#### ? Step 1.1: Projekt-Dependencies korrigieren - **DONE** +- [x] Application: Infrastructure-Referenz entfernt +- [x] Infrastructure: Application-Referenz hinzugefügt +- [x] Verify: Domain hat keine Dependencies +- [x] Build: Erfolgreich + +**Ergebnis:** Clean Architecture Dependency Rules eingehalten + +--- + +#### ? Step 1.2: NuGet Packages installieren - **DONE** + +**Application:** +- [x] MediatR (14.1.0) +- [x] FluentValidation (12.1.1) +- [x] FluentValidation.DependencyInjectionExtensions (12.1.1) +- [x] ~~Ardalis.Result (10.1.0)~~ ? **ENTFERNT** (Exception-basiert) + +**Infrastructure:** +- [x] DevExpress.Pdf.Core (25.2.8) +- [x] Microsoft.Extensions.Options.ConfigurationExtensions (8.0.0) + +**API:** +- [x] Serilog.AspNetCore (10.0.0) +- [x] Serilog.Enrichers.Environment (3.0.1) +- [x] Serilog.Sinks.File (7.0.0) +- [x] Asp.Versioning.Http (8.1.1) +- [x] Microsoft.Extensions.Caching.StackExchangeRedis (8.0.28) +- [x] Swashbuckle.AspNetCore (6.6.2) + +**Ergebnis:** Alle Packages installiert, neueste stable Versionen + +--- + +#### ? Step 1.3: Folder-Struktur erstellen - **DONE** + +**Domain:** +- [x] Models/ +- [x] Models/ValueObjects/ +- [x] Models/Enums/ +- [x] Common/ +- [x] Common/Exceptions/ +- [x] Constants/ + +**Application:** +- [x] Features/ +- [x] Features/Documents/ +- [x] Features/Documents/ProcessDocument/ +- [x] Features/Documents/ValidatePdf/ +- [x] Features/Documents/ExtractAttachments/ +- [x] Features/Documents/ConcatenatePdfs/ +- [x] Features/Documents/ApplyStamp/ +- [x] Features/Documents/EmbedCertificate/ +- [x] Common/ +- [x] Common/Interfaces/ +- [x] Common/Behaviors/ +- [x] Common/DTOs/ +- [x] Common/Mappings/ + +**Infrastructure:** +- [x] Services/ +- [x] Services/PdfProcessing/ +- [x] Services/FileStorage/ +- [x] Services/DocumentValidation/ +- [x] Configuration/ + +**API:** +- [x] Endpoints/ +- [x] Endpoints/v1/ +- [x] Middleware/ +- [x] Configuration/ +- [x] Controllers/ ? **GELÖSCHT** (Minimal APIs!) + +**Ergebnis:** Komplette Ordnerstruktur nach Clean Architecture + +--- + +#### ? Step 1.4: Basis-Configuration - **DONE** + +**Part A: appsettings.json** +- [x] appsettings.json mit allen Settings erstellt + - Serilog Configuration + - DocumentOperatorSettings + - RedisSettings + - ApiKeySettings (Demo-Keys) +- [x] appsettings.Development.json für Dev-Overrides +- [x] .gitignore vorhanden (bereits existiert) + +**Part B: Options Classes** +- [x] DocumentOperatorSettings.cs +- [x] RedisSettings.cs +- [x] ApiKeySettings.cs +- [x] TenantInfo.cs (als separate Klasse ? Best Practice!) + +**Part C: Serilog Setup** +- [x] Program.cs erweitert mit Serilog +- [x] Options Pattern registriert +- [x] Try/Catch für Startup-Errors +- [x] Serilog Request Logging aktiviert +- [x] Serilog Enrichers installiert + +**Ergebnis:** Produktionsreife Konfiguration, Logging funktioniert + +--- + +### ?? **PHASE 2: Domain Layer** - **IN PROGRESS** + +**Ziel:** Business Models ohne technische Dependencies erstellen + +--- + +#### ?? Step 2.1: Domain Exceptions erstellen - **NEXT** + +**Aufgabe:** Custom Exception-Klassen für fachliche Fehler + +**Zu erstellen:** +1. [ ] `DomainException.cs` (Basis-Exception) +2. [ ] `DomainValidationException.cs` (Value Object Validierung) +3. [ ] `NotFoundException.cs` (Resource nicht gefunden) +4. [ ] `PdfProcessingException.cs` (PDF-spezifische Fehler) + +**Wo:** `Domain/Common/Exceptions/` + +**Warum Exceptions?** +- Zentrale Exception Handling Middleware (API Layer) +- Einfacher Code (keine Result Checks) +- Standard .NET Exception-Flow +- Wartbar an einer Stelle + +**Verwendung:** +```csharp +// In Value Objects: +if (invalid) + throw new DomainValidationException("Base64 cannot be empty"); + +// In Handlers: +if (notFound) + throw new NotFoundException("Document", id); + +// Middleware fängt ab und mapped zu HTTP 400/404/500 +``` + +--- + +#### ? Step 2.2: Value Objects erstellen + +**Aufgabe:** Typsichere, selbst-validierende Wert-Objekte + +**Zu erstellen:** +1. [ ] `Base64String.cs` + - Factory Method: `Create(string value)` + - Validierung: Gültiges Base64-Format + - Konvertierung: `ToByteArray()`, `FromByteArray()` + - 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 + - Computed Property: `FileSizeMB` + - Keine Validierung (nur Daten-Container) + +**Wo:** `Domain/Models/ValueObjects/` + +**Warum Value Objects?** +- ? Typsicherheit: `Base64String` statt `string` +- ? Validierung an **einer** Stelle (Constructor) +- ? Immutable (keine Änderungen nach Erstellung) +- ? Domain-Driven Design Best Practice + +--- + +#### ? Step 2.3: Enums erstellen + +**Aufgabe:** Aufzählungen für Business-Konzepte + +**Zu erstellen:** +1. [ ] `DocumentOperationType.cs` + ```csharp + public enum DocumentOperationType + { + Validate, + ExtractAttachments, + Concatenate, + ApplyStamp, + EmbedCertificate + } + ``` + +2. [ ] `ProcessingStatus.cs` + ```csharp + public enum ProcessingStatus + { + Pending, + Processing, + Success, + Failed + } + ``` + +3. [ ] `PdfValidationError.cs` + ```csharp + public enum PdfValidationError + { + InvalidFormat, + FileTooLarge, + Corrupted, + UnsupportedVersion, + NoPages + } + ``` + +**Wo:** `Domain/Models/Enums/` + +--- + +#### ? Step 2.4: Domain Models erstellen + +**Aufgabe:** Kern-Business-Objekte + +**Zu erstellen:** +1. [ ] `PdfDocument.cs` (Core Model) + - Properties: Id, Base64Content, Metadata, Attachments, Status + - Methods: AddAttachment(), ApplyStamp(), etc. + +2. [ ] `DocumentAttachment.cs` + - Properties: FileName, Content (Base64), FileSize, MimeType + +3. [ ] `DocumentStamp.cs` + - Properties: Text, Position, Logo (Base64), TenantId + +4. [ ] `DocumentCertificate.cs` + - Properties: PfxContent (Base64), Password, Issuer + +**Wo:** `Domain/Models/` + +**Warum Domain Models?** +- Repräsentieren Business-Konzepte +- Enthalten Business Rules +- Keine Datenbank-Annotations (wir haben kein EF Core!) +- Pure C# Klassen + +--- + +#### ? Step 2.5: Constants erstellen + +**Aufgabe:** Konstanten für Error Messages, Limits, etc. + +**Zu erstellen:** +1. [ ] `ErrorCodes.cs` + ```csharp + public static class ErrorCodes + { + public const string InvalidBase64 = "ERR_INVALID_BASE64"; + public const string PdfTooLarge = "ERR_PDF_TOO_LARGE"; + public const string InvalidTenant = "ERR_INVALID_TENANT"; + } + ``` + +2. [ ] `ValidationMessages.cs` + ```csharp + public static class ValidationMessages + { + public const string Base64Empty = "Base64 string cannot be empty"; + public const string TenantIdEmpty = "TenantId is required"; + } + ``` + +**Wo:** `Domain/Constants/` + +**Warum Constants?** +- Keine Magic Strings im Code +- Wiederverwendbar +- Leicht änderbar (an einer Stelle) + +--- + +### ? **PHASE 3: Application Layer** + +**Ziel:** Use Cases mit MediatR implementieren + +--- + +#### ? Step 3.1: MediatR Setup & Behaviors + +**Aufgabe:** MediatR konfigurieren + Pipeline Behaviors + +**Zu erstellen:** +1. [ ] `DependencyInjection.cs` (Application Layer) + - MediatR registrieren + - FluentValidation registrieren + - Behaviors registrieren + +2. [ ] `ValidationBehavior.cs` ? + - Vor jedem Handler: FluentValidation ausführen + - Bei Fehler: `ValidationException` werfen + - Middleware fängt ab ? HTTP 400 + +3. [ ] `LoggingBehavior.cs` + - Request/Response loggen + - Execution Time messen + +4. [ ] `ExceptionLoggingBehavior.cs` + - Exceptions loggen bevor sie propagieren + +**Wo:** `Application/Common/Behaviors/` + +**Warum Behaviors?** +- Cross-Cutting Concerns (Validation, Logging) +- DRY: Nicht in jedem Handler wiederholen +- Pipeline Pattern + +--- + +#### ? Step 3.2: Interfaces für Infrastructure + +**Aufgabe:** Abstractions definieren (Dependency Inversion!) + +**Zu erstellen:** +1. [ ] `IPdfProcessor.cs` + ```csharp + public interface IPdfProcessor + { + Task MergePdfsAsync(List pdfs); + Task> ExtractAttachmentsAsync(PdfDocument pdf); + Task ApplyStampAsync(PdfDocument pdf, DocumentStamp stamp); + Task EmbedCertificateAsync(PdfDocument pdf, DocumentCertificate cert); + } + ``` + +2. [ ] `IFileStorageService.cs` + ```csharp + public interface IFileStorageService + { + Task SaveTempFileAsync(byte[] content, string extension); + Task LoadTempFileAsync(string path); + Task DeleteTempFileAsync(string path); + Task CleanupOldFilesAsync(TimeSpan maxAge); + } + ``` + +3. [ ] `IDocumentValidator.cs` + ```csharp + public interface IDocumentValidator + { + Task ValidateAsync(PdfDocument pdf); + bool IsValidFormat(byte[] content); + } + ``` + +4. [ ] `ICertificateService.cs` + ```csharp + public interface ICertificateService + { + Task ValidateCertificateAsync(DocumentCertificate cert); + } + ``` + +**Wo:** `Application/Common/Interfaces/` + +**Warum Interfaces?** +- Application kennt nur Verträge (nicht Implementierung) +- Infrastructure implementiert +- Testbar (Mocking) +- Clean Architecture Dependency Rule + +--- + +#### ? Step 3.3: DTOs erstellen + +**Aufgabe:** Data Transfer Objects für API + +**Zu erstellen:** +1. [ ] `ProcessDocumentRequest.cs` (Record Type) + ```csharp + public record ProcessDocumentRequest( + string Base64Pdf, + string TenantId, + List Operations + ); + ``` + +2. [ ] `ProcessDocumentResponse.cs` + ```csharp + public record ProcessDocumentResponse( + string Base64Pdf, + PdfMetadata Metadata, + List PerformedOperations, + bool Success + ); + ``` + +3. [ ] `DocumentOperationDto.cs` + ```csharp + public record DocumentOperationDto( + DocumentOperationType Type, + Dictionary? Parameters + ); + ``` + +4. [ ] `AttachmentDto.cs` +5. [ ] `ErrorResponse.cs` (für Exception Middleware) + +**Wo:** `Application/Common/DTOs/` + +**Warum DTOs?** +- API Contracts (können sich ändern ohne Domain zu ändern) +- Validation (FluentValidation) +- Serialization-friendly + +--- + +#### ? Step 3.4: Erste Feature - ValidatePdf + +**Aufgabe:** Erste komplette Feature-Implementierung + +**Zu erstellen:** +1. [ ] `ValidatePdfQuery.cs` + ```csharp + public record ValidatePdfQuery(string Base64Pdf, string TenantId) + : IRequest; + ``` + +2. [ ] `ValidatePdfHandler.cs` + ```csharp + public class ValidatePdfHandler : IRequestHandler + { + public async Task Handle(...) + { + var base64 = Base64String.Create(query.Base64Pdf); // Wirft Exception + var pdf = new PdfDocument(base64); + var metadata = await _validator.ValidateAsync(pdf); + return metadata; + } + } + ``` + +3. [ ] `ValidatePdfValidator.cs` (FluentValidation) + ```csharp + public class ValidatePdfValidator : AbstractValidator + { + public ValidatePdfValidator() + { + RuleFor(x => x.Base64Pdf).NotEmpty(); + RuleFor(x => x.TenantId).NotEmpty(); + } + } + ``` + +**Wo:** `Application/Features/Documents/ValidatePdf/` + +**Flow:** +``` +API ? ValidatePdfQuery + ? ValidationBehavior (FluentValidation) + ? ValidatePdfHandler + ? IDocumentValidator (Infrastructure) + ? PdfMetadata zurück +``` + +--- + +#### ? Step 3.5: Weitere Features + +Nach ValidatePdf (als Beispiel): +- [ ] ProcessDocument (orchestriert andere Commands) +- [ ] ExtractAttachments +- [ ] ConcatenatePdfs +- [ ] ApplyStamp +- [ ] EmbedCertificate + +Jeweils: Command/Query + Handler + Validator + +--- + +### ? **PHASE 4: Infrastructure Layer** + +**Ziel:** Interfaces implementieren mit echten Technologien + +--- + +#### ? Step 4.1: DevExpress PDF Service + +**Aufgabe:** `IPdfProcessor` implementieren + +**Zu erstellen:** +1. [ ] `DevExpressPdfProcessor.cs : IPdfProcessor` + +```csharp +public class DevExpressPdfProcessor : IPdfProcessor +{ + public async Task MergePdfsAsync(List pdfs) + { + try + { + using var processor = new PdfDocumentProcessor(); // DevExpress! + + foreach (var pdf in pdfs) + { + processor.AppendDocument(pdf.ToStream()); + } + + var result = processor.SaveDocument(); + return new PdfDocument(result); + } + catch (Exception ex) + { + throw new PdfProcessingException("PDF merge failed", ex); + } + } + + // ExtractAttachments(), ApplyStamp(), EmbedCertificate() ... +} +``` + +**Wo:** `Infrastructure/Services/PdfProcessing/` + +**Wichtig:** +- Wirft `PdfProcessingException` bei Fehlern +- Nutzt DevExpress API +- Async wo möglich + +--- + +#### ? Step 4.2: File Storage Service + +**Aufgabe:** `IFileStorageService` implementieren + +**Zu erstellen:** +1. [ ] `LocalFileStorageService.cs : IFileStorageService` + +```csharp +public class LocalFileStorageService : IFileStorageService +{ + private readonly DocumentOperatorSettings _settings; + + public async Task SaveTempFileAsync(byte[] content, string extension) + { + var fileName = $"{Guid.NewGuid()}{extension}"; + var path = Path.Combine(_settings.TempFolderPath, fileName); + + Directory.CreateDirectory(_settings.TempFolderPath); + await File.WriteAllBytesAsync(path, content); + + return path; + } + + // LoadTempFileAsync(), DeleteTempFileAsync(), CleanupOldFilesAsync() +} +``` + +**Wo:** `Infrastructure/Services/FileStorage/` + +**Features:** +- Nutzt `DocumentOperatorSettings.TempFolderPath` +- Cleanup für alte Dateien (Background Service später) +- Exception Handling + +--- + +#### ? Step 4.3: Document Validator + +**Aufgabe:** `IDocumentValidator` implementieren + +**Zu erstellen:** +1. [ ] `PdfDocumentValidator.cs : IDocumentValidator` + +```csharp +public class PdfDocumentValidator : IDocumentValidator +{ + public async Task ValidateAsync(PdfDocument pdf) + { + using var processor = new PdfDocumentProcessor(); + processor.LoadDocument(pdf.ToStream()); + + if (processor.Document.Pages.Count == 0) + throw new PdfProcessingException("PDF has no pages"); + + return new PdfMetadata( + pageCount: processor.Document.Pages.Count, + fileSizeBytes: pdf.Content.Length, + pdfVersion: processor.Document.Version.ToString(), + hasAttachments: processor.Document.Attachments.Count > 0, + attachmentCount: processor.Document.Attachments.Count + ); + } +} +``` + +**Wo:** `Infrastructure/Services/DocumentValidation/` + +--- + +#### ? Step 4.4: DI Registration + +**Aufgabe:** Services registrieren + +**Zu erstellen:** +1. [ ] `DependencyInjection.cs` (Infrastructure Layer) + +```csharp +public static class DependencyInjection +{ + public static IServiceCollection AddInfrastructure( + this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + return services; + } +} +``` + +**In Program.cs aufrufen:** +```csharp +builder.Services.AddInfrastructure(); +``` + +--- + +### ? **PHASE 5: API Layer - Minimal APIs & Middleware** + +**Ziel:** HTTP Endpoints + Exception Handling + +--- + +#### ? Step 5.1: Exception Handling Middleware ? + +**Aufgabe:** Zentrale Exception ? HTTP Response Mapping + +**Zu erstellen:** +1. [ ] `ExceptionHandlingMiddleware.cs` + +```csharp +public class ExceptionHandlingMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); + } + catch (DomainValidationException ex) + { + _logger.LogWarning(ex, "Validation error"); + await HandleValidationExceptionAsync(context, ex); + } + catch (NotFoundException ex) + { + _logger.LogWarning(ex, "Resource not found"); + await HandleNotFoundExceptionAsync(context, ex); + } + catch (PdfProcessingException ex) + { + _logger.LogError(ex, "PDF processing failed"); + await HandlePdfProcessingExceptionAsync(context, ex); + } + catch (ValidationException ex) // FluentValidation + { + _logger.LogWarning(ex, "Input validation failed"); + await HandleFluentValidationExceptionAsync(context, ex); + } + catch (Exception ex) + { + _logger.LogCritical(ex, "Unhandled exception"); + await HandleUnexpectedExceptionAsync(context, ex); + } + } + + private static Task HandleValidationExceptionAsync(HttpContext context, DomainValidationException ex) + { + 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 = StatusCodes.Status400BadRequest, + Detail = ex.Message, + Instance = context.Request.Path + }; + + return context.Response.WriteAsJsonAsync(problemDetails); + } + + // HandleNotFoundException(), HandlePdfProcessingException(), etc. +} +``` + +**Wo:** `API/Middleware/` + +**Registrieren in Program.cs:** +```csharp +app.UseMiddleware(); +``` + +**Warum zentral?** +- ? Alle Fehler an **einer** Stelle +- ? Konsistente Error-Responses +- ? Logging zentral +- ? HTTP Status Code Mapping +- ? Wartbar! + +--- + +#### ? Step 5.2: Minimal API Endpoints + +**Aufgabe:** HTTP Endpoints definieren + +**Zu erstellen:** +1. [ ] `DocumentEndpoints.cs` + +```csharp +public static class DocumentEndpoints +{ + public static IEndpointRouteBuilder MapDocumentEndpoints( + this IEndpointRouteBuilder app) + { + var group = app.MapGroup("/api/v1/documents") + .WithTags("Documents") + .WithOpenApi(); + + group.MapPost("/validate", ValidatePdf) + .WithName("ValidatePdf") + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status400BadRequest); + + group.MapPost("/process", ProcessDocument) + .WithName("ProcessDocument"); + + group.MapPost("/extract-attachments", ExtractAttachments); + group.MapPost("/concatenate", ConcatenatePdfs); + group.MapPost("/stamp", ApplyStamp); + group.MapPost("/sign", EmbedCertificate); + + return app; + } + + private static async Task ValidatePdf( + ValidatePdfRequest request, + IMediator mediator, + CancellationToken ct) + { + var query = new ValidatePdfQuery(request.Base64Pdf, request.TenantId); + var result = await mediator.Send(query, ct); + return Results.Ok(result); + } + + // ProcessDocument(), ExtractAttachments(), etc. +} +``` + +**Wo:** `API/Endpoints/v1/` + +**In Program.cs registrieren:** +```csharp +app.MapDocumentEndpoints(); +``` + +**Features:** +- Minimal APIs (keine Controller-Klassen!) +- OpenAPI/Swagger Integration +- MediatR aufrufen +- Middleware fängt Exceptions ab + +--- + +#### ? Step 5.3: Swagger Configuration + +**Aufgabe:** Swagger mit API-Key Support + +**Zu erstellen:** +1. [ ] `SwaggerConfiguration.cs` + +```csharp +public static class SwaggerConfiguration +{ + public static IServiceCollection AddSwaggerConfiguration( + this IServiceCollection services) + { + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo + { + Title = "DocumentOperator API", + Version = "v1", + Description = "Zentralisierter PDF-Operationen Service" + }); + + // API-Key Support + c.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme + { + Description = "API Key (Header: X-API-Key)", + Name = "X-API-Key", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey, + Scheme = "ApiKeyScheme" + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "ApiKey" + } + }, + Array.Empty() + } + }); + }); + + return services; + } +} +``` + +**Wo:** `API/Configuration/` + +**Ergebnis:** +- Swagger UI zeigt "Authorize" Button +- Dev-Leiter kann API-Key eingeben für Tests +- Alle Requests enthalten X-API-Key Header + +--- + +#### ? Step 5.4: Tenant Resolution Middleware + +**Aufgabe:** API-Key ? Tenant Context + +**Zu erstellen:** +1. [ ] `TenantResolutionMiddleware.cs` + +```csharp +public class TenantResolutionMiddleware +{ + public async Task InvokeAsync(HttpContext context) + { + var apiKey = context.Request.Headers["X-API-Key"].FirstOrDefault(); + + if (string.IsNullOrEmpty(apiKey)) + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + await context.Response.WriteAsJsonAsync(new ProblemDetails + { + Title = "API Key Missing", + Status = 401 + }); + return; + } + + // API-Key ? Tenant auflösen (aus ApiKeySettings) + var tenantInfo = _apiKeySettings.Keys.GetValueOrDefault(apiKey); + + if (tenantInfo == null || !tenantInfo.IsActive) + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return; + } + + // Tenant-Context in DI (Scoped) + var tenantContext = context.RequestServices.GetRequiredService(); + tenantContext.SetTenant(tenantInfo.TenantId, tenantInfo.TenantName); + + await _next(context); + } +} +``` + +**Wo:** `API/Middleware/` + +**Flow:** +``` +Request mit X-API-Key ? Middleware + ? API-Key validieren + ? Tenant auflösen + ? TenantContext setzen + ? Handler nutzt TenantContext +``` + +--- + +### ? **PHASE 6: Multi-Tenancy & Security** + +**Ziel:** Mandantenfähigkeit implementieren + +#### ? Steps: +- [ ] `ITenantContext` Interface (Application) +- [ ] `TenantContext` Implementation (Infrastructure) +- [ ] Rate Limiting pro Tenant +- [ ] Tenant-spezifische Settings (Logo, Zertifikat) + +--- + +### ? **PHASE 7: Distributed Cache (Redis)** + +**Ziel:** Performance-Optimierung + +#### ? Steps: +- [ ] Redis Connection Setup +- [ ] Cache für API-Key Validation +- [ ] Cache für Tenant-Settings +- [ ] Cache Invalidation Strategy + +--- + +### ? **PHASE 8: Testing** + +**Ziel:** Qualitätssicherung + +#### ? Steps: +- [ ] Unit Tests (Application Handlers) +- [ ] Integration Tests (API Endpoints) +- [ ] Test-PDFs erstellen +- [ ] Coverage >80% + +--- + +### ? **PHASE 9: Deployment (IIS)** + +**Ziel:** Production-Ready + +#### ? Steps: +- [ ] appsettings.Production.json +- [ ] IIS Application Pool (.NET 8) +- [ ] HTTPS Binding +- [ ] Environment Variables +- [ ] Health Checks + +--- + +## ?? CURRENT STATUS + +### ? Completed +- **Phase 1:** Foundation & Clean Architecture Setup + - Dependencies ? + - Packages ? + - Folder Structure ? + - Configuration ? + - Serilog ? + +### ?? In Progress +- **Phase 2:** Domain Layer + - **NEXT:** Step 2.1 - Domain Exceptions erstellen + +### ? Pending +- Phase 3-9 + +--- + +## ?? LEARNING NOTES + +### Clean Architecture Principles Learned + +1. **Dependency Rule:** Immer nach innen (Domain kennt nichts, Application nur Domain, etc.) +2. **Separation of Concerns:** Jede Schicht hat klare Verantwortung +3. **Value Objects:** Typsicherheit + Validierung in einem +4. **CQRS:** Klare Trennung Commands/Queries +5. **Vertical Slices:** Feature-basiert statt Layer-basiert + +### Exception-based Error Handling + +**Vorteile erkannt:** +- Einfacherer Code (kein Result Boilerplate) +- Zentrales Handling (Middleware) +- Wartbarer (Fehler-Mapping an einer Stelle) +- Standard .NET Flow + +**Wichtig:** +- FluentValidation für Input (erste Verteidigung) +- Domain Exceptions für Business-Fehler +- Middleware mapped zu HTTP Status Codes +- Serilog loggt alles + +--- + +## ?? REFERENCES & RESOURCES + +### 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) + +### Best Practices Applied +- ? Vertical Slice Architecture +- ? 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) + +--- + +## ?? 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 | **Decision:** Exception-based statt Ardalis.Result | +| 2024-XX-XX | Phase 1 | ? Phase 1 completed | +| 2024-XX-XX | Phase 2 | ?? Starting Domain Layer - Exceptions | + +--- + +**END OF ROADMAP** + +*This document is a living document and will be updated as development progresses.*