Files
Services.DDDocumentOperator/ROADMAP.md
OlgunR d5fc0c2e51 Add detailed project roadmap for DocumentOperator
Introduced a comprehensive roadmap in `ROADMAP.md` to outline
the vision, purpose, and development phases of the
`DocumentOperator` service. Key additions include:

- Table of contents for navigation.
- Project overview with problem statement, solution, and core
  features.
- Business workflow description for API operations.
- Architecture and design decisions:
  - Clean Architecture principles and dependency rules.
  - CQRS with MediatR and Vertical Slice Architecture.
  - Exception-based error handling and Minimal APIs.
- Multi-tenancy strategy using API keys.
- Technology stack and detailed project structure.
- Development roadmap with nine phases, highlighting current
  progress (Phase 1 completed, Phase 2 in progress).
- Learning notes, references, and an update log.

The roadmap serves as a living document to guide development
and ensure alignment on goals and strategies.
2026-06-16 14:26:44 +02:00

1639 lines
42 KiB
Markdown

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