diff --git a/DocumentOperator.API/ROADMAP.md b/DocumentOperator.API/ROADMAP.md index 2fdc923..44d7d40 100644 --- a/DocumentOperator.API/ROADMAP.md +++ b/DocumentOperator.API/ROADMAP.md @@ -1,38 +1,40 @@ -# 📘 DocumentOperator - Project Roadmap +# ?? DocumentOperator - Project Roadmap (Pragmatic Edition) -> **Last Updated:** 16.06.2026 | **Status:** In Development | **Phase:** 2 (Domain Layer) +> **Last Updated:** 17.01.2025 | **Status:** In Development | **Phase:** 2 (Domain Layer - Minimal) --- -## 📋 TABLE OF CONTENTS +## ?? 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) +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 +## ?? PROJECT OVERVIEW ### Vision & Purpose -**DocumentOperator** ist ein zentralisierter REST API Service für PDF-Dokumenten-Operationen in einer Multi-Tenant DMS-Umgebung. +**DocumentOperator** ist ein zentralisierter REST API Service fr 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 +- Keine zentrale Stelle fr 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) +**Lsung:** +- **Ein** zentraler Service fr alle PDF-Operationen +- Wiederverwendbar ber HTTP REST API +- Mandantenfhig (Multi-Tenancy) - Wartbar an einer Stelle --- @@ -42,17 +44,17 @@ Der Service bietet folgende PDF-Operationen: #### 1. **PDF Validierung** -- Prüfung auf gültiges PDF-Format +- Prfung auf gltiges PDF-Format - Korruptions-Erkennung -- Metadaten-Extraktion (Seitenzahl, Größe, Version) +- Metadaten-Extraktion (Seitenzahl, Gre, Version, Anhnge) #### 2. **Attachment-Extraktion** -- Erkennung von eingebetteten Anhängen -- Extraktion in temporären Ordner -- Rückgabe als Base64 oder Download-Link +- Erkennung von eingebetteten Anhngen +- Extraktion in temporren Ordner +- Rckgabe als Base64 oder Download-Link #### 3. **PDF-Konkatenation** -- Zusammenführen mehrerer PDFs +- Zusammenfhren mehrerer PDFs - Reihenfolge konfigurierbar - Seitenzahl-Optimierung @@ -64,7 +66,7 @@ Der Service bietet folgende PDF-Operationen: #### 5. **Zertifikat-Einbettung** - PFX-Zertifikate als Attachment einbetten - Digitale Signatur-Vorbereitung -- Workflow-Integration (Ergebnisbericht → Zertifikat → Siegel) +- Workflow-Integration (Ergebnisbericht ? Zertifikat ? Siegel) --- @@ -72,62 +74,106 @@ Der Service bietet folgende PDF-Operationen: ``` Client Application - ↓ + ? [HTTP Request] - JSON mit Base64-PDF - ↓ -DocumentOperator API - ↓ -[Validierung] → [Operation(en)] → [Ergebnis] - ↓ + ? +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) -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 +2. API validiert Input (FluentValidation in MediatR Pipeline) +3. Handler konvertiert PDF ? Byte-Array +4. DevExpress Service fhrt Operation durch +5. Temporre Dateien werden erstellt/bereinigt (falls ntig) +6. Ergebnis wird als Base64 zurckgegeben --- -## 🏛️ ARCHITECTURE & DESIGN DECISIONS +## ??? ARCHITECTURE & DESIGN DECISIONS -### Clean Architecture +### Clean Architecture (Pragmatisch!) -Wir verwenden **Clean Architecture** mit 4 Layers: +Wir verwenden **Clean Architecture** mit 4 Layers - **ABER: pragmatisch, nicht dogmatisch!** ``` -┌─────────────────────────────────────┐ -│ 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 -└─────────────────────────────────────┘ +??????????????????????????????????????? +? 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 (Kritisch!) +#### Dependency Rule -**Abhängigkeiten zeigen immer nach innen:** +**Abhngigkeiten zeigen immer nach innen:** ``` -API → Application → Domain -API → Infrastructure → Domain -Infrastructure → Application +API ? Application ? Domain +API ? Infrastructure ? Domain +Infrastructure ? Application (fr Interfaces) -Domain → NICHTS! (No External Dependencies) -Application → NUR Domain +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 +**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 unntigen 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 Geschftslogik) +- ? Daten flieen durch (Input ? Verarbeitung ? Output) + +**Deshalb: Domain Layer minimal!** + +**Was bleibt in Domain:** +1. **Enums** (DocumentOperationType, ProcessingStatus) + - Pure Business-Konzepte + - Technologie-unabhngig + - 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.) + - Fr 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 ntig, so wenig wie mglich** +- Wenn wir spter merken "das fehlt" ? dann erst hinzufgen (iterativ!) --- @@ -136,29 +182,38 @@ Application → NUR Domain **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 +- ? Klare Trennung: 1 Command/Query = 1 Handler = 1 Verantwortung +- ? Testbarkeit (Handler kann isoliert getestet werden) +- ? Pipeline Behaviors (Validation, Logging zentral) +- ? Kein aufgeblhter Service mit 20 Methoden -**Statt:** +**CQRS in unserem Kontext:** +- **Command:** ndert Daten (ProcessDocument, ApplyStamp, etc.) +- **Query:** Liest Daten (ValidatePdf ? gibt nur Metadata zurck) + +**Beispiel:** ```csharp -public class DocumentService { - public void Process() { } - public void Validate() { } - public void Extract() { } - // ... 20 Methoden +// 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; + } } ``` -**Nutzen wir:** -```csharp -// Feature: ProcessDocument -public class ProcessDocumentCommand : IRequest { } -public class ProcessDocumentHandler : IRequestHandler { } -public class ProcessDocumentValidator : AbstractValidator { } -``` +**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) --- @@ -166,81 +221,131 @@ public class ProcessDocumentValidator : AbstractValidator Wrapping) +- ? Standard .NET Exception-Flow (jeder kennt es) +- ? Zentrales Error Handling = wartbar an **einer** Stelle +- ? Ein Package weniger (keine Extra-Lib) **Flow:** ``` -Request → Validation (FluentValidation Behavior) - ↓ - Handler (wirft Exception bei Fehler) - ↓ - Middleware (fängt Exception, mappt zu HTTP Code) - ↓ - Response (Problem Details RFC 7807) +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) zurck + ? +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 -- ✅ Weniger Boilerplate (keine Controller-Klassen) -- ✅ Direkte Endpoint-Definition -- ✅ Swagger funktioniert 1:1 -- ✅ Bessere Performance -- ✅ Moderner, funktionaler Stil +- ? .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/process", async ( - ProcessDocumentRequest request, - IMediator mediator) => +app.MapPost("/api/v1/documents/validate", async ( + ValidatePdfRequest request, + IMediator mediator, + CancellationToken ct) => { - var command = new ProcessDocumentCommand(request); - var result = await mediator.Send(command); + // 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("ProcessDocument") +.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 zurck (Results.Ok()) + --- ### Multi-Tenancy via API-Keys @@ -248,31 +353,50 @@ app.MapPost("/api/v1/documents/process", async ( **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.) +- Middleware resolved API-Key ? Tenant-Context +- Tenant-spezifische Einstellungen (Logo fr 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 +- ? Einfach fr Service-to-Service Communication +- ? Security + Tenant-Identification kombiniert +- ? Swagger-kompatibel (fr BB-Tests) +- ? Einfaches Rate-Limiting pro Tenant (spter) **Flow:** ``` -Request mit Header "X-API-Key: abc123" - ↓ -TenantResolutionMiddleware - ↓ -API-Key → Tenant-Konfiguration - ↓ -ITenantContext (Scoped DI) - ↓ -Handler nutzt Tenant-Settings +HTTP Request mit Header "X-API-Key: abc123" + ? +TenantResolutionMiddleware (wird spter implementiert) + ? +API-Key validieren (aus appsettings.json oder Redis) + ? +Tenant-Info auflsen (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 +## ??? TECHNOLOGY STACK ### Core Framework @@ -280,11 +404,11 @@ Handler nutzt Tenant-Settings |------------|---------|---------| | **.NET** | 8.0 | Runtime & Framework | | **ASP.NET Core** | 8.0 | Web API | -| **C#** | 12 | Language (mit Primary Constructors, Record Types) | +| **C#** | 12 | Language (Primary Constructors, Record Types) | --- -### Key Libraries & Packages +### NuGet Packages #### API Layer @@ -295,16 +419,15 @@ Handler nutzt Tenant-Settings | **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 | +| **Microsoft.Extensions.Caching.StackExchangeRedis** | 8.0.28 | Redis Cache (spter) | #### Application Layer | Package | Version | Purpose | |---------|---------|---------| | **MediatR** | 14.1.0 | CQRS Pattern Implementation | -| **FluentValidation** | 12.1.1 | Input Validation | +| **FluentValidation** | 12.1.1 | Input Validation (DTOs) | | **FluentValidation.DependencyInjectionExtensions** | 12.1.1 | DI Integration | -| ~~Ardalis.Result~~ | ~~10.1.0~~ | ❌ **ENTFERNT** (Exception-basiert stattdessen) | #### Infrastructure Layer @@ -319,414 +442,299 @@ Handler nutzt Tenant-Settings |---------|---------|---------| | - | - | **Keine Dependencies!** (Clean Architecture) | ---- +#### Tests (neu!) -### 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) | +| Package | Version | Purpose | +|---------|---------|---------| +| **xUnit** | 2.9.3 | Test Framework | +| **FluentAssertions** | 7.0.0 | Assertions (result.Should().Be(expected)) | +| **Moq** | 4.20.72 | Mocking (fr Services) | +| **Microsoft.NET.Test.Sdk** | 17.11.1 | Test SDK | +| **xunit.runner.visualstudio** | 2.8.2 | Visual Studio Test Runner | --- -## 📁 PROJECT STRUCTURE +## ?? 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 +??? 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) +### ?? 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 +- ? Application +- ? Infrastructure +- ? Domain **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 +??? 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, Auth, Logging) -- ✅ Swagger Configuration -- ✅ Dependency Injection Setup -- ✅ appsettings.json +**Was gehrt hierher:** +- ? HTTP Routing (Minimal APIs) +- ? Middleware (Exception, Logging) +- ? Swagger Configuration +- ? Dependency Injection Setup +- ? appsettings.json -**Was NICHT hierher gehört?** -- ❌ Business Logic (→ Application/Domain) -- ❌ PDF-Verarbeitung (→ Infrastructure) -- ❌ Validierung (→ Application: FluentValidation) +**Was NICHT hierher gehrt:** +- ? Business Logic (? Application) +- ? PDF-Verarbeitung (? Infrastructure) +- ? Validierung (? Application: FluentValidation) --- -### 💼 Application Layer (DocumentOperator.Application) +### ?? Application Layer (DocumentOperator.Application) **Purpose:** Use Cases, Business Logic Orchestration **References:** -- → Domain (ONLY!) - -**NuGet Packages:** -- MediatR -- FluentValidation + DI Extensions +- ? Domain (ONLY!) **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 +??? Features/ ? Vertical Slices ? +? ??? Documents/ +? ??? ValidatePdf/ +? ? ??? ValidatePdfQuery.cs +? ? ??? ValidatePdfHandler.cs +? ? ??? ValidatePdfValidator.cs +? ??? ExtractAttachments/ +? ? ??? ExtractAttachmentsCommand.cs +? ? ??? ExtractAttachmentsHandler.cs +? ? ??? ExtractAttachmentsValidator.cs +? ??? ... (weitere Features iterativ) +??? Common/ +? ??? Interfaces/ ? Abstractions fr 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 -- ✅ Handlers (orchestrieren Domain + Infrastructure) -- ✅ FluentValidation Validators -- ✅ DTOs (API Contracts) -- ✅ Interfaces für Infrastructure (Dependency Inversion!) -- ✅ Pipeline Behaviors +**Was gehrt hierher:** +- ? MediatR Commands & Queries (pro Feature) +- ? Handlers (orchestrieren Domain + Infrastructure) +- ? FluentValidation Validators +- ? DTOs (API Contracts) +- ? Interfaces fr Infrastructure (Dependency Inversion!) +- ? Pipeline Behaviors (Validation, Logging) -**Was NICHT hierher gehört?** -- ❌ DevExpress-spezifischer Code (→ Infrastructure) -- ❌ File I/O (→ Infrastructure) -- ❌ HTTP-spezifisches (→ API) -- ❌ EF Core / Database (haben wir nicht) +**Was NICHT hierher gehrt:** +- ? DevExpress-spezifischer Code (? Infrastructure) +- ? File I/O (? Infrastructure) +- ? HTTP-spezifisches (? API) **Warum keine Infrastructure-Referenz?** -- Clean Architecture: Application kennt nur **Interfaces** (`IPdfProcessor`) +- Application kennt nur **Interfaces** (`IPdfProcessor`) - Infrastructure **implementiert** die Interfaces (`DevExpressPdfProcessor`) -- API injected die Implementierung via DI -- → Application bleibt technologie-unabhängig! +- API injiziert die Implementierung via DI +- ? Application bleibt technologie-unabhngig! --- -### 🔧 Infrastructure Layer (DocumentOperator.Infrastructure) +### ?? Infrastructure Layer (DocumentOperator.Infrastructure) -**Purpose:** Technische Implementierungen, externe Abhängigkeiten +**Purpose:** Technische Implementierungen **References:** -- → Application (für Interfaces) -- → Domain - -**NuGet Packages:** -- DevExpress.Pdf.Core -- Microsoft.Extensions.Options.ConfigurationExtensions +- ? Application (fr Interfaces) +- ? Domain **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 +??? 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) -- ✅ Redis Client (später) -- ✅ Externe API Calls (falls benötigt) -- ✅ Options Pattern Classes +**Was gehrt hierher:** +- ? DevExpress Integration +- ? File System Zugriffe (Temp-Files) +- ? Options Pattern Classes (Settings) -**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; - } -} -``` +**Was NICHT hierher gehrt:** +- ? Business Logic (? Application) +- ? HTTP Handling (? API) --- -### 🏛️ Domain Layer (DocumentOperator.Domain) +### ??? Domain Layer (DocumentOperator.Domain) - MINIMAL! -**Purpose:** Kern-Geschäftslogik, Business Rules +**Purpose:** Business Rules (nur was wirklich gebraucht wird!) **References:** -- → **KEINE!** (wichtigste Clean Architecture Regel) - -**NuGet Packages:** -- **KEINE!** (reine C# Klassen) +- ? **KEINE!** (wichtigste Clean Architecture Regel) **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 +??? 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?** -- ✅ Business Models (PdfDocument, etc.) -- ✅ Value Objects (Base64String, TenantId) -- ✅ Enums (DocumentOperationType) -- ✅ Business Rules (z.B. "Max 100 Seiten") -- ✅ Domain Exceptions -- ✅ Constants +**Was gehrt hierher:** +- ? Value Objects (Base64String, TenantId, PdfMetadata) +- ? Enums (DocumentOperationType, ProcessingStatus) +- ? Domain Exceptions -**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 +**Was NICHT hierher gehrt:** +- ? Domain Models (PdfDocument, etc.) ? YAGNI! DTOs reichen! +- ? Constants (ErrorCodes) ? erst wenn mehrfach gebraucht +- ? Services (? Infrastructure) +- ? MediatR (? Application) +- ? JEGLICHE externe Library! --- -## 🗺️ DEVELOPMENT ROADMAP +### ?? 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 fr Handler (Application Layer) +- ? Unit Tests fr Services (Infrastructure Layer) +- ? Unit Tests fr Value Objects (Domain Layer) +- ? Integration Tests fr Endpoints (API Layer) --- -### ✅ **PHASE 1: Foundation & Clean Architecture Setup** - **COMPLETED** +## ?? DEVELOPMENT PHILOSOPHY -**Ziel:** Saubere Architektur-Basis ohne Funktionalität +### Pragmatisch, nicht dogmatisch! -#### ✅ 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 +**Prinzipien:** -**Ergebnis:** Clean Architecture Dependency Rules eingehalten +1. **YAGNI (You Ain't Gonna Need It)** + - ? Keine spekulativen Abstraktionen + - ? Keine Klassen "fr spter" + - ? Erst wenn 2. Use Case es braucht ? dann Abstrahieren + +2. **KISS (Keep It Simple, Stupid)** + - ? Kein Overengineering + - ? Keine unntigen 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 auen nach innen bauen (API ? Service ? Domain) + - ? Wir sehen sofort was funktioniert (kein "spekulatives" Code) + - ? Feedback-Loop schneller + +**Konkret fr 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 fr Feature) +- TDD (Test ? Code ? Refactor) --- -#### ✅ Step 1.2: NuGet Packages installieren - **DONE** +## ??? DEVELOPMENT ROADMAP -**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) +### ? PHASE 1: Foundation - **COMPLETED** -**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 +**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) --- -#### ✅ Step 1.3: Folder-Struktur erstellen - **DONE** +### ?? PHASE 2: Domain Layer (Minimal) - **IN PROGRESS** -**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 +**Ziel:** Nur was wirklich gebraucht wird! --- -#### ✅ Step 1.4: Basis-Configuration - **DONE** +#### ? Step 2.1: Domain Exceptions erstellen - **COMPLETED** -**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 - **COMPLETED** - -**Aufgabe:** Custom Exception-Klassen für fachliche Fehler - -**Erstellt:** +**Bereits erstellt:** 1. [x] `DomainException.cs` (Basis-Exception) 2. [x] `DomainValidationException.cs` (Value Object Validierung) 3. [x] `NotFoundException.cs` (Resource nicht gefunden) @@ -734,904 +742,666 @@ DocumentOperator.Domain/ **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"); +#### ?? Step 2.2: Enums erstellen - **NEXT** -// In Handlers: -if (notFound) - throw new NotFoundException("Document", id); +**Aufgabe:** Aufzhlungen fr Business-Konzepte -// Middleware fängt ab und mapped zu HTTP 400/404/500 -``` +**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:** Spter 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 fr asynchrone Operationen (spter: Queue) + - **Wo gebraucht:** Response DTOs + +**Nach diesem Step:** +- Ich prfe deine Dateien +- Wir haken Step 2.2 ab in ROADMAP.md +- Weiter zu Step 2.3 (Value Objects) --- -#### 🔄 Step 2.2: Value Objects erstellen - **NEXT** +#### ? Step 2.3: Value Objects erstellen **Aufgabe:** Typsichere, selbst-validierende Wert-Objekte -**Zu erstellen:** -1. [ ] `Base64String.cs` +**Warum Value Objects?** +- ? Typsicherheit: `Base64String` statt `string` +- ? Validierung an **einer** Stelle (Constructor) +- ? Immutable (keine nderungen nach Erstellung) +- ? Wiederverwendbar (in Domain, Application, Infrastructure) + +**Was du erstellen wirst:** + +1. **Base64String.cs** - Factory Method: `Create(string value)` - - Validierung: Gültiges Base64-Format - - Konvertierung: `ToByteArray()`, `FromByteArray()` + - Validierung: Gltiges Base64-Format + - Konvertierung: `ToByteArray()`, `FromByteArray(byte[])` - Wirft `DomainValidationException` bei Fehler -2. [ ] `TenantId.cs` +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 +3. **PdfMetadata.cs** + - Properties: PageCount, FileSizeBytes, PdfVersion, HasAttachments, AttachmentCount - 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 +**Detaillierte Anleitung kommt in Step 2.3!** --- -#### ⏳ Step 2.3: Enums erstellen +### ? PHASE 3: Infrastructure Layer (Outside-In!) -**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/` +**Ziel:** DevExpress Services implementieren (wir sehen **echten** Code!) --- -#### ⏳ Step 2.4: Domain Models erstellen +#### ? Step 3.1: IPdfProcessor Interface erstellen -**Aufgabe:** Kern-Business-Objekte +**Aufgabe:** Abstraction fr PDF-Operationen -**Zu erstellen:** -1. [ ] `PdfDocument.cs` (Core Model) - - Properties: Id, Base64Content, Metadata, Attachments, Status - - Methods: AddAttachment(), ApplyStamp(), etc. +**Was du erstellen wirst:** +- **Wo:** `Application/Common/Interfaces/IPdfProcessor.cs` +- **Inhalt:** + ```csharp + public interface IPdfProcessor + { + Task ValidateAsync(byte[] pdfBytes); + // Weitere Methoden spter (YAGNI!) + } + ``` -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) +**Warum Interface ERST?** +- Application kennt nur Interface (Dependency Inversion) - Infrastructure implementiert -- Testbar (Mocking) -- Clean Architecture Dependency Rule +- TDD: Test ? Interface ? Implementation --- -#### ⏳ Step 3.3: DTOs erstellen +#### ? Step 3.2: DevExpressPdfProcessor implementieren (mit TDD!) -**Aufgabe:** Data Transfer Objects für API +**Aufgabe:** DevExpress Integration -**Zu erstellen:** -1. [ ] `ProcessDocumentRequest.cs` (Record Type) +**Flow:** +1. **Test schreiben** (Red) ```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 + [Fact] + public async Task ValidateAsync_ValidPdf_ReturnMetadata() { - public async Task Handle(...) + // 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) { - var base64 = Base64String.Create(query.Base64Pdf); // Wirft Exception - var pdf = new PdfDocument(base64); - var metadata = await _validator.ValidateAsync(pdf); - return metadata; + using var processor = new PdfDocumentProcessor(); + processor.LoadDocument(pdfBytes); + + return new PdfMetadata( + PageCount: processor.Document.Pages.Count, + FileSizeBytes: pdfBytes.Length, + // ... + ); } } ``` -3. [ ] `ValidatePdfValidator.cs` (FluentValidation) +3. **Test grn machen** +4. **Refactoring** (falls ntig) + +**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 knnen +- Wir knnen 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 fr Handler +- ValidationBehavior = zentrale FluentValidation Ausfhrung + +--- + +#### ? 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.Base64Pdf).NotEmpty(); - RuleFor(x => x.TenantId).NotEmpty(); + RuleFor(x => x.PdfContent).NotNull(); } } ``` +4. **ValidatePdfHandlerTests.cs** (Unit Test) + **Wo:** `Application/Features/Documents/ValidatePdf/` **Flow:** ``` -API → ValidatePdfQuery - → ValidationBehavior (FluentValidation) - → ValidatePdfHandler - → IDocumentValidator (Infrastructure) - → PdfMetadata zurück +DTO ? Query (Value Objects) ? ValidationBehavior (FluentValidation) + ? Handler ? IPdfProcessor ? PdfMetadata ``` --- -#### ⏳ Step 3.5: Weitere Features +### ? PHASE 5: API Layer -Nach ValidatePdf (als Beispiel): -- [ ] ProcessDocument (orchestriert andere Commands) -- [ ] ExtractAttachments -- [ ] ConcatenatePdfs -- [ ] ApplyStamp -- [ ] EmbedCertificate - -Jeweils: Command/Query + Handler + Validator +**Ziel:** HTTP Endpoint + Exception Middleware --- -### ⏳ **PHASE 4: Infrastructure Layer** +#### ? Step 5.1: Exception Handling Middleware -**Ziel:** Interfaces implementieren mit echten Technologien +**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 knnen sie zu HTTP Status Codes mappen --- -#### ⏳ Step 4.1: DevExpress PDF Service +#### ? Step 5.2: Minimal API Endpoint -**Aufgabe:** `IPdfProcessor` implementieren +**Aufgabe:** HTTP Endpoint fr ValidatePdf -**Zu erstellen:** -1. [ ] `DevExpressPdfProcessor.cs : IPdfProcessor` +**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(); -```csharp -public class DevExpressPdfProcessor : IPdfProcessor -{ - public async Task MergePdfsAsync(List pdfs) - { - try - { - using var processor = new PdfDocumentProcessor(); // DevExpress! + group.MapPost("/validate", ValidatePdf); + } - foreach (var pdf in pdfs) - { - processor.AppendDocument(pdf.ToStream()); - } + private static async Task ValidatePdf( + ValidatePdfRequest request, + IMediator mediator, + CancellationToken ct) + { + var query = new ValidatePdfQuery( + Base64String.Create(request.Base64Pdf) + ); - var result = processor.SaveDocument(); - return new PdfDocument(result); - } - catch (Exception ex) - { - throw new PdfProcessingException("PDF merge failed", ex); - } - } + var result = await mediator.Send(query, ct); + return Results.Ok(result); + } + } + ``` - // 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/` +**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:** -- Minimal APIs (keine Controller-Klassen!) -- OpenAPI/Swagger Integration -- MediatR aufrufen -- Middleware fängt Exceptions ab +- [ ] ExtractAttachments +- [ ] ConcatenatePdfs +- [ ] ApplyStamp +- [ ] EmbedCertificate --- -#### ⏳ Step 5.3: Swagger Configuration +### ? PHASE 7: Swagger & API Documentation -**Aufgabe:** Swagger mit API-Key Support +**Ziel:** Produktionsreife API-Dokumentation -**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 +**Steps:** +- [ ] Swagger Configuration (API-Key Support) +- [ ] XML Comments fr Endpoints +- [ ] Response Examples (Swashbuckle) --- -#### ⏳ Step 5.4: Tenant Resolution Middleware +### ? PHASE 8: Multi-Tenancy (spter) -**Aufgabe:** API-Key → Tenant Context +**Ziel:** Mandantenfhigkeit implementieren -**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 +**Steps:** +- [ ] ITenantContext Interface (Application) +- [ ] TenantContext Implementation (Infrastructure) +- [ ] TenantResolutionMiddleware (API) - [ ] Tenant-spezifische Settings (Logo, Zertifikat) --- -### ⏳ **PHASE 7: Distributed Cache (Redis)** +### ? PHASE 9: Production-Ready -**Ziel:** Performance-Optimierung +**Ziel:** Deployment vorbereiten -#### ⏳ 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 +**Steps:** - [ ] Health Checks +- [ ] appsettings.Production.json +- [ ] IIS Deployment (Web.config) +- [ ] Redis Integration (Distributed Cache) --- -## 📊 CURRENT STATUS +## ?? TESTING STRATEGY -### ✅ Completed +### Test-Driven Development (TDD) + +**Flow:** +1. **Red:** Test schreiben (schlgt fehl, weil Code noch nicht existiert) +2. **Green:** Code schreiben (Test wird grn) +3. **Refactor:** Code verbessern (Test bleibt grn) + +**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 zurck?) + +- **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 - - Dependencies ✅ - - Packages ✅ - - Folder Structure ✅ - - Configuration ✅ - - Serilog ✅ + - Dependencies ? + - Packages ? + - Folder Structure ? + - Configuration ? + - Serilog ? -### 🔄 In Progress -- **Phase 2:** Domain Layer - - ✅ Step 2.1 - Domain Exceptions - - **NEXT:** Step 2.2 - Value Objects +### ?? In Progress +- **Phase 2:** Domain Layer (Minimal) + - ? Step 2.1 - Domain Exceptions + - **NEXT:** Step 2.2 - Enums -### ⏳ Pending +### ? Pending - Phase 3-9 --- -## 🎓 LEARNING NOTES +## ?? KEY LEARNINGS & DECISIONS -### Clean Architecture Principles Learned +### 1. Domain Layer minimal halten -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 +**Entscheidung:** Nur Enums + Value Objects + Exceptions -### Exception-based Error Handling +**Warum:** +- Kein EF Core / Entities +- Service-Anwendung (nicht Domain-lastig) +- YAGNI (You Ain't Gonna Need It) -**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 +**Alternative wre gewesen:** +- Volle Domain Models (PdfDocument, DocumentAttachment, etc.) +- **Nachteile:** Overengineering, unntige Komplexitt --- -## 📚 REFERENCES & RESOURCES +### 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 wre 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 wre 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 fr Refactoring + +**Alternative wre gewesen:** +- Code ? Test (Test-After) +- **Nachteile:** Tests werden oft vergessen, schlechteres Design + +--- + +### 5. Vertical Slice Architecture + +**Entscheidung:** Pro Feature alles zusammen + +**Warum:** +- Zusammengehriger Code ist zusammen +- Einfacher zu finden und zu ndern +- Besser fr Teams (weniger Merge-Konflikte) + +**Alternative wre 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 -- ✅ 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) + +- ? 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 fr Configuration +- ? Dependency Injection +- ? Async/Await berall +- ? Nullable Reference Types +- ? Record Types fr DTOs (C# 12) +- ? Primary Constructors (.NET 8) +- ? Structured Logging (Serilog) --- -## 🔄 UPDATE LOG +## ?? 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 | ✅ Step 2.1 completed - Domain Exceptions created | -| 2024-XX-XX | Phase 2 | 🔄 Starting Step 2.2 - Value Objects | +| 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 | ?? Starting Step 2.2 - Enums (vor Value Objects) | ---