- Added "Last Updated" timestamp, current status, and phase. - Introduced a "TABLE OF CONTENTS" for easier navigation. - Expanded "PROJECT OVERVIEW" with vision, purpose, and workflow. - Detailed "ARCHITECTURE & DESIGN DECISIONS" with key patterns. - Listed frameworks, libraries, and components in "TECH STACK." - Provided a breakdown of the solution's folder structure. - Outlined development phases in "DEVELOPMENT ROADMAP." - Documented progress in "CURRENT STATUS" and added "UPDATE LOG." - Included "LEARNING NOTES" and references to best practices. - Improved formatting for clarity and readability.
1641 lines
44 KiB
Markdown
1641 lines
44 KiB
Markdown
# 📘 DocumentOperator - Project Roadmap
|
|
|
|
> **Last Updated:** 16.06.2026 | **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 - **COMPLETED**
|
|
|
|
**Aufgabe:** Custom Exception-Klassen für fachliche Fehler
|
|
|
|
**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/`
|
|
|
|
**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 - **NEXT**
|
|
|
|
**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
|
|
- ✅ Step 2.1 - Domain Exceptions
|
|
- **NEXT:** Step 2.2 - Value Objects
|
|
|
|
### ⏳ 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 | ✅ Step 2.1 completed - Domain Exceptions created |
|
|
| 2024-XX-XX | Phase 2 | 🔄 Starting Step 2.2 - Value Objects |
|
|
|
|
---
|
|
|
|
**END OF ROADMAP**
|
|
|
|
*This document is a living document and will be updated as development progresses.*
|