# ?? 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.*