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.
1639 lines
42 KiB
Markdown
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.*
|