# ?? DocumentOperator - Project Roadmap (Pragmatic Edition) > **Last Updated:** 17.01.2025 | **Status:** In Development | **Phase:** 2 (Domain Layer - Minimal) --- ## ?? TABLE OF CONTENTS 1. [Project Overview](#project-overview) 2. [Architecture & Design Decisions](#architecture--design-decisions) 3. [Development Philosophy](#development-philosophy) 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) --- ## ?? 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, 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 ``` Client Application ? [HTTP Request] - JSON mit Base64-PDF ? DocumentOperator API (Minimal API Endpoint) ? [FluentValidation] ? [MediatR Handler] ? [DevExpress Service] ? [Ergebnis] ? [HTTP Response] - JSON mit verarbeitetem PDF (Base64) ``` **Typischer Ablauf:** 1. Client sendet PDF als Base64 in JSON 2. API validiert Input (FluentValidation in MediatR Pipeline) 3. Handler konvertiert PDF ? Byte-Array 4. DevExpress Service führt Operation durch 5. Temporäre Dateien werden erstellt/bereinigt (falls nötig) 6. Ergebnis wird als Base64 zurückgegeben --- ## ??? ARCHITECTURE & DESIGN DECISIONS ### Clean Architecture (Pragmatisch!) 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 **Abhängigkeiten zeigen immer nach innen:** ``` API ? Application ? Domain API ? Infrastructure ? Domain Infrastructure ? Application (für Interfaces) Domain ? NICHTS! (No External Dependencies) Application ? NUR Domain ``` **Warum Clean Architecture?** - ? Testbarkeit (Application Layer kann Services mocken) - ? Austauschbarkeit (DevExpress ? anderes PDF-Lib ohne Application zu ändern) - ? Separation of Concerns (jede Schicht hat klare Verantwortung) **ABER:** - ? Kein Overengineering (nur was wir wirklich brauchen!) - ? Keine spekulativen Abstraktionen (erst wenn 2. Use Case es braucht) - ? Keine unnötigen Klassen (YAGNI - You Ain't Gonna Need It) --- ### 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 **Warum MediatR?** - ? Klare Trennung: 1 Command/Query = 1 Handler = 1 Verantwortung - ? Testbarkeit (Handler kann isoliert getestet werden) - ? Pipeline Behaviors (Validation, Logging zentral) - ? Kein aufgeblähter Service mit 20 Methoden **CQRS in unserem Kontext:** - **Command:** Ändert Daten (ProcessDocument, ApplyStamp, etc.) - **Query:** Liest Daten (ValidatePdf ? gibt nur Metadata zurück) **Beispiel:** ```csharp // Query (Read-Only) public record ValidatePdfQuery(Base64String PdfContent) : IRequest; // Handler public class ValidatePdfHandler : IRequestHandler { private readonly IPdfProcessor _processor; public async Task Handle(ValidatePdfQuery query, CancellationToken ct) { byte[] bytes = query.PdfContent.ToByteArray(); var metadata = await _processor.ValidateAsync(bytes); return metadata; } } ``` **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 **Statt Horizontal Layers** (Commands/, Handlers/, Validators/): ``` ? Horizontal (Schlecht für Wartung): Application/ ??? Commands/ ? ??? ValidatePdfCommand.cs ? ??? ProcessDocumentCommand.cs ??? Handlers/ ? ??? ValidatePdfHandler.cs ? ??? ProcessDocumentHandler.cs ??? Validators/ ??? ValidatePdfValidator.cs ??? ProcessDocumentValidator.cs ``` **Nutzen wir Vertical Slices** (pro Feature alles zusammen): ``` ? Vertical (Gut für Wartung): Features/ ??? ValidatePdf/ ? ??? ValidatePdfQuery.cs ? ??? ValidatePdfHandler.cs ? ??? ValidatePdfValidator.cs ??? ProcessDocument/ ??? ProcessDocumentCommand.cs ??? ProcessDocumentHandler.cs ??? ProcessDocumentValidator.cs ``` **Vorteile:** - ? Zusammengehöriger Code ist zusammen (Cohesion) - ? Einfacher zu finden ("Wo ist ValidatePdf?" ? ein Ordner!) - ? Einfacher zu ändern (alle Dateien im gleichen Ordner) - ? Weniger Merge-Konflikte im Team --- ### Exception-based Error Handling **Entscheidung:** Keine Result Pattern Library (Ardalis.Result entfernt) **Stattdessen:** 1. **FluentValidation** für Input-Validierung (DTO-Ebene) 2. **Domain Exceptions** für fachliche Fehler 3. **Zentrale Exception Handling Middleware** im API Layer **Warum Exception-basiert?** - ? Einfacherer Code (kein `if (result.IsSuccess)` überall) - ? 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:** ``` HTTP Request ? FluentValidation (MediatR ValidationBehavior) ? Bei Fehler: ValidationException ? Middleware ? HTTP 400 ? Handler ? Bei Fehler: DomainException ? Middleware ? HTTP 400/404/500 ? Middleware (Exception Handler) ? Mappt Exception Type ? HTTP Status Code ? Loggt Exception (Serilog) ? Gibt Problem Details (RFC 7807) zurück ? 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 **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 (später) **Flow:** ``` HTTP Request mit Header "X-API-Key: abc123" ? TenantResolutionMiddleware (wird später implementiert) ? API-Key validieren (aus appsettings.json oder Redis) ? Tenant-Info auflösen (TenantId, TenantName, IsActive) ? ITenantContext setzen (Scoped Service) ? Handler nutzt ITenantContext.TenantId ``` **Beispiel - Tenant-spezifischer Stamp:** ```csharp public class ApplyStampHandler : IRequestHandler { private readonly ITenantContext _tenantContext; public async Task Handle(ApplyStampCommand command, CancellationToken ct) { // Tenant-spezifisches Logo laden var logoPath = $"logos/{_tenantContext.TenantId}/stamp.png"; // Stamp mit Logo anwenden // ... } } ``` --- ## ??? TECHNOLOGY STACK ### Core Framework | Technology | Version | Purpose | |------------|---------|---------| | **.NET** | 8.0 | Runtime & Framework | | **ASP.NET Core** | 8.0 | Web API | | **C#** | 12 | Language (Primary Constructors, Record Types) | --- ### NuGet 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 Cache (später) | #### Application Layer | Package | Version | Purpose | |---------|---------|---------| | **MediatR** | 14.1.0 | CQRS Pattern Implementation | | **FluentValidation** | 12.1.1 | Input Validation (DTOs) | | **FluentValidation.DependencyInjectionExtensions** | 12.1.1 | DI Integration | #### Infrastructure Layer | 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.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 #### Domain Layer | Package | Version | Purpose | |---------|---------|---------| | - | - | **Keine Dependencies!** (Clean Architecture) | #### Tests (neu!) | Package | Version | Purpose | |---------|---------|---------| | **xUnit** | 2.9.3 | Test Framework | | **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 | --- ## ?? PROJECT STRUCTURE ### Solution Overview ``` DocumentOperator/ ??? DocumentOperator.API/ ? HTTP Entry Point ??? DocumentOperator.Application/ ? Use Cases (MediatR Handlers) ??? DocumentOperator.Infrastructure/ ? Technical Implementations ??? DocumentOperator.Domain/ ? Business Logic (MINIMAL!) ??? DocumentOperator.Tests/ ? Unit & Integration Tests (NEU!) ??? ROADMAP.md ? This file ``` --- ### ?? API Layer (DocumentOperator.API) **Purpose:** HTTP Entry Point, Routing, Middleware **References:** - ? Application - ? Infrastructure - ? Domain **Folder Structure:** ``` DocumentOperator.API/ ??? Endpoints/ ? ??? v1/ ? ??? DocumentEndpoints.cs ? Minimal API Endpoints ??? Middleware/ ? ??? ExceptionHandlingMiddleware.cs ? Zentrale Exception Handling ? ??? Configuration/ ? ??? SwaggerConfiguration.cs ? Swagger Setup (API-Key Support) ??? 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) ??? Common/ ? ??? Interfaces/ ? Abstractions für Infrastructure ? ? ??? IPdfProcessor.cs ? ??? Behaviors/ ? MediatR Pipeline Behaviors ? ? ??? ValidationBehavior.cs ? FluentValidation Integration ? ??? DTOs/ ? Data Transfer Objects (API Contracts) ? ??? 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 ??? Configuration/ ? ??? DocumentOperatorSettings.cs ? Options Pattern Class ? ??? ApiKeySettings.cs ? ??? TenantInfo.cs ??? DependencyInjection.cs ? Service Registration ``` **Was gehört hierher:** - ? DevExpress Integration - ? File System Zugriffe (Temp-Files) - ? Options Pattern Classes (Settings) **Was NICHT hierher gehört:** - ? Business Logic (? Application) - ? HTTP Handling (? API) --- ### ??? 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 ? ??? Base64String.cs ? ??? TenantId.cs ? ??? PdfMetadata.cs ??? Enums/ ? ??? DocumentOperationType.cs ? ??? ProcessingStatus.cs ??? Exceptions/ ? Domain-spezifische Exceptions ??? DomainException.cs ??? DomainValidationException.cs ??? NotFoundException.cs ??? PdfProcessingException.cs ``` **Was gehört hierher:** - ? Value Objects (Base64String, TenantId, PdfMetadata) - ? Enums (DocumentOperationType, ProcessingStatus) - ? Domain Exceptions **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! **Purpose:** Unit & Integration Tests **References:** - ? Alle Projekte (API, Application, Infrastructure, Domain) **Folder Structure:** ``` DocumentOperator.Tests/ ??? Unit/ ? ??? Application/ ? ? ??? Features/ ? ? ??? ValidatePdf/ ? ? ??? ValidatePdfHandlerTests.cs ? ??? Infrastructure/ ? ? ??? Services/ ? ? ??? DevExpressPdfProcessorTests.cs ? ??? Domain/ ? ??? ValueObjects/ ? ??? Base64StringTests.cs ??? Integration/ ??? API/ ??? ValidatePdfEndpointTests.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 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 - [ ] ConcatenatePdfs - [ ] ApplyStamp - [ ] EmbedCertificate --- ### ? PHASE 7: Swagger & API Documentation **Ziel:** Produktionsreife API-Dokumentation **Steps:** - [ ] Swagger Configuration (API-Key Support) - [ ] XML Comments für Endpoints - [ ] Response Examples (Swashbuckle) --- ### ? PHASE 8: Multi-Tenancy (später) **Ziel:** Mandantenfähigkeit implementieren **Steps:** - [ ] ITenantContext Interface (Application) - [ ] TenantContext Implementation (Infrastructure) - [ ] TenantResolutionMiddleware (API) - [ ] Tenant-spezifische Settings (Logo, Zertifikat) --- ### ? PHASE 9: Production-Ready **Ziel:** Deployment vorbereiten **Steps:** - [ ] Health Checks - [ ] appsettings.Production.json - [ ] IIS Deployment (Web.config) - [ ] Redis Integration (Distributed Cache) --- ## ?? TESTING STRATEGY ### Test-Driven Development (TDD) **Flow:** 1. **Red:** Test schreiben (schlägt fehl, weil Code noch nicht existiert) 2. **Green:** Code schreiben (Test wird grün) 3. **Refactor:** Code verbessern (Test bleibt grün) **Warum TDD?** - ? Tests als Dokumentation (wie wird es genutzt?) - ? Tests als Safety Net (Refactoring ohne Angst) - ? Besseres Design (testbarer Code = guter Code) - ? Keine "vergessenen" Tests (Test kommt ZUERST) --- ### Test-Pyramide ``` /\ / \ E2E Tests (wenige) / \ /------\ Integration Tests (einige) / \ /----------\ Unit Tests (viele) / \ ``` **Konkret:** - **Unit Tests (viele):** - Value Objects (Base64String.Create() wirft Exception?) - Handlers (ValidatePdfHandler ruft IPdfProcessor auf?) - Services (DevExpressPdfProcessor gibt Metadata zurück?) - **Integration Tests (einige):** - Endpoints (HTTP POST ? 200 OK + JSON?) - MediatR Pipeline (ValidationBehavior funktioniert?) - **E2E Tests (wenige/keine):** - Haben wir nicht (API ist selbst der "Top-Level") --- ### 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) - **Phase 3:** Infrastructure Layer (Outside-In!) - ? Step 3.1 - IPdfProcessor Interface erstellt - ?? Step 3.2 - DevExpressPdfProcessor implementieren (TDD - **IN PROGRESS**) - ? 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) ### ?? In Progress - **Phase 3, Step 3.2:** DevExpressPdfProcessor (TDD) - **NEXT:** Step 3.2.5 - DevExpressPdfProcessor.cs implementieren (TDD Green Phase) - **Progress:** 4/7 Mini-Steps abgeschlossen ### ? Pending - **Phase 3:** Infrastructure Layer - Step 3.2 - DevExpressPdfProcessor Implementation - **Phase 4:** Application Layer - Step 4.1 - MediatR Setup (DependencyInjection.cs, ValidationBehavior.cs) - Step 4.2 - ValidatePdf Feature (Query, Handler, Validator) - **Phase 5:** API Layer - Step 5.1 - Exception Handling Middleware - Step 5.2 - Minimal API Endpoint - Step 5.3 - Integration Test - **Phase 6-9:** Weitere Features, Swagger, Multi-Tenancy, Production ### ?? 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 --- ## ?? KEY LEARNINGS & DECISIONS ### 1. Domain Layer minimal halten **Entscheidung:** Nur Enums + Value Objects + Exceptions **Warum:** - Kein EF Core / Entities - Service-Anwendung (nicht Domain-lastig) - YAGNI (You Ain't Gonna Need It) **Alternative wäre gewesen:** - Volle Domain Models (PdfDocument, DocumentAttachment, etc.) - **Nachteile:** Overengineering, unnötige Komplexität --- ### 2. Outside-In Development **Entscheidung:** Infrastructure ? Application ? API **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 --- ### 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/) ### 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) - ? 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 | ? 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) | --- **END OF ROADMAP** *This document is a living document and will be updated as development progresses.*