Files
Services.DDDocumentOperator/DocumentOperator.API/ROADMAP.md
OlgunR 64be11f7ad Add tests for DevExpressPdfProcessor and roadmap updates
Updated ROADMAP.md to reflect progress on the TDD process for
DevExpressPdfProcessor, including the creation of unit tests
(6 tests in the Red Phase). Updated progress to 4/7 mini-steps
completed and outlined the next step (Green Phase).

Added `DevExpressPdfProcessorTests.cs` with unit tests to
validate PDF files and extract metadata. Tests cover valid
PDFs, null/empty inputs, corrupted PDFs, and file size
calculations. Used `FluentAssertions` for assertions.

Cleaned up `DocumentOperator.Tests.csproj` by removing an
unused folder reference. No functional changes to the project
structure.
2026-06-19 14:38:12 +02:00

1495 lines
41 KiB
Markdown

# ?? 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<PdfMetadata>;
// Handler
public class ValidatePdfHandler : IRequestHandler<ValidatePdfQuery, PdfMetadata>
{
private readonly IPdfProcessor _processor;
public async Task<PdfMetadata> 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<T> 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<ApplyStampCommand, byte[]>
{
private readonly ITenantContext _tenantContext;
public async Task<byte[]> 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<PdfMetadata> 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<PdfMetadata> 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<PdfMetadata>;
```
2. **ValidatePdfHandler.cs**
```csharp
public class ValidatePdfHandler : IRequestHandler<ValidatePdfQuery, PdfMetadata>
{
private readonly IPdfProcessor _processor;
public async Task<PdfMetadata> Handle(ValidatePdfQuery query, CancellationToken ct)
{
byte[] bytes = query.PdfContent.ToByteArray();
return await _processor.ValidateAsync(bytes);
}
}
```
3. **ValidatePdfValidator.cs** (FluentValidation)
```csharp
public class ValidatePdfValidator : AbstractValidator<ValidatePdfQuery>
{
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<IResult> 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<WebApplicationFactory<Program>>
{
[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<PdfMetadata>();
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<T> 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.*