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.
42 KiB
?? DocumentOperator - Project Roadmap
Last Updated: 2024 | Status: In Development | Phase: 2 (Domain Layer)
?? TABLE OF CONTENTS
- Project Overview
- Architecture & Design Decisions
- Technology Stack
- Project Structure
- Development Roadmap
- 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:
- Client sendet PDF als Base64 in JSON
- API validiert Input (FluentValidation)
- PDF wird in Byte-Array konvertiert
- Operationen werden durchgeführt (DevExpress)
- Temporäre Dateien werden erstellt/bereinigt
- 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:
public class DocumentService {
public void Process() { }
public void Validate() { }
public void Extract() { }
// ... 20 Methoden
}
Nutzen wir:
// 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:
- Domain Exceptions für fachliche Fehler
- FluentValidation für Input-Validierung
- 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:
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 |
| ? 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:
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
- Application: Infrastructure-Referenz entfernt
- Infrastructure: Application-Referenz hinzugefügt
- Verify: Domain hat keine Dependencies
- Build: Erfolgreich
Ergebnis: Clean Architecture Dependency Rules eingehalten
? Step 1.2: NuGet Packages installieren - DONE
Application:
- MediatR (14.1.0)
- FluentValidation (12.1.1)
- FluentValidation.DependencyInjectionExtensions (12.1.1)
Ardalis.Result (10.1.0)? ENTFERNT (Exception-basiert)
Infrastructure:
- DevExpress.Pdf.Core (25.2.8)
- Microsoft.Extensions.Options.ConfigurationExtensions (8.0.0)
API:
- Serilog.AspNetCore (10.0.0)
- Serilog.Enrichers.Environment (3.0.1)
- Serilog.Sinks.File (7.0.0)
- Asp.Versioning.Http (8.1.1)
- Microsoft.Extensions.Caching.StackExchangeRedis (8.0.28)
- Swashbuckle.AspNetCore (6.6.2)
Ergebnis: Alle Packages installiert, neueste stable Versionen
? Step 1.3: Folder-Struktur erstellen - DONE
Domain:
- Models/
- Models/ValueObjects/
- Models/Enums/
- Common/
- Common/Exceptions/
- Constants/
Application:
- Features/
- Features/Documents/
- Features/Documents/ProcessDocument/
- Features/Documents/ValidatePdf/
- Features/Documents/ExtractAttachments/
- Features/Documents/ConcatenatePdfs/
- Features/Documents/ApplyStamp/
- Features/Documents/EmbedCertificate/
- Common/
- Common/Interfaces/
- Common/Behaviors/
- Common/DTOs/
- Common/Mappings/
Infrastructure:
- Services/
- Services/PdfProcessing/
- Services/FileStorage/
- Services/DocumentValidation/
- Configuration/
API:
- Endpoints/
- Endpoints/v1/
- Middleware/
- Configuration/
- Controllers/ ? GELÖSCHT (Minimal APIs!)
Ergebnis: Komplette Ordnerstruktur nach Clean Architecture
? Step 1.4: Basis-Configuration - DONE
Part A: appsettings.json
- appsettings.json mit allen Settings erstellt
- Serilog Configuration
- DocumentOperatorSettings
- RedisSettings
- ApiKeySettings (Demo-Keys)
- appsettings.Development.json für Dev-Overrides
- .gitignore vorhanden (bereits existiert)
Part B: Options Classes
- DocumentOperatorSettings.cs
- RedisSettings.cs
- ApiKeySettings.cs
- TenantInfo.cs (als separate Klasse ? Best Practice!)
Part C: Serilog Setup
- Program.cs erweitert mit Serilog
- Options Pattern registriert
- Try/Catch für Startup-Errors
- Serilog Request Logging aktiviert
- 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:
DomainException.cs(Basis-Exception)DomainValidationException.cs(Value Object Validierung)NotFoundException.cs(Resource nicht gefunden)PdfProcessingException.cs(PDF-spezifische Fehler)
Wo: Domain/Common/Exceptions/
Warum Exceptions?
- Zentrale Exception Handling Middleware (API Layer)
- Einfacher Code (keine Result Checks)
- Standard .NET Exception-Flow
- Wartbar an einer Stelle
Verwendung:
// 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:
-
Base64String.cs- Factory Method:
Create(string value) - Validierung: Gültiges Base64-Format
- Konvertierung:
ToByteArray(),FromByteArray() - Wirft
DomainValidationExceptionbei Fehler
- Factory Method:
-
TenantId.cs- Factory Method:
Create(string value) - Validierung: Nicht leer, Max 100 Zeichen
- Normalisierung:
.ToLowerInvariant() - Wirft
DomainValidationExceptionbei Fehler
- Factory Method:
-
PdfMetadata.cs- Properties: PageCount, FileSizeBytes, PdfVersion, HasAttachments
- Computed Property:
FileSizeMB - Keine Validierung (nur Daten-Container)
Wo: Domain/Models/ValueObjects/
Warum Value Objects?
- ? Typsicherheit:
Base64Stringstattstring - ? 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:
-
DocumentOperationType.cspublic enum DocumentOperationType { Validate, ExtractAttachments, Concatenate, ApplyStamp, EmbedCertificate } -
ProcessingStatus.cspublic enum ProcessingStatus { Pending, Processing, Success, Failed } -
PdfValidationError.cspublic enum PdfValidationError { InvalidFormat, FileTooLarge, Corrupted, UnsupportedVersion, NoPages }
Wo: Domain/Models/Enums/
? Step 2.4: Domain Models erstellen
Aufgabe: Kern-Business-Objekte
Zu erstellen:
-
PdfDocument.cs(Core Model)- Properties: Id, Base64Content, Metadata, Attachments, Status
- Methods: AddAttachment(), ApplyStamp(), etc.
-
DocumentAttachment.cs- Properties: FileName, Content (Base64), FileSize, MimeType
-
DocumentStamp.cs- Properties: Text, Position, Logo (Base64), TenantId
-
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:
-
ErrorCodes.cspublic 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"; } -
ValidationMessages.cspublic 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:
-
DependencyInjection.cs(Application Layer)- MediatR registrieren
- FluentValidation registrieren
- Behaviors registrieren
-
ValidationBehavior.cs?- Vor jedem Handler: FluentValidation ausführen
- Bei Fehler:
ValidationExceptionwerfen - Middleware fängt ab ? HTTP 400
-
LoggingBehavior.cs- Request/Response loggen
- Execution Time messen
-
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:
-
IPdfProcessor.cspublic 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); } -
IFileStorageService.cspublic interface IFileStorageService { Task<string> SaveTempFileAsync(byte[] content, string extension); Task<byte[]> LoadTempFileAsync(string path); Task DeleteTempFileAsync(string path); Task CleanupOldFilesAsync(TimeSpan maxAge); } -
IDocumentValidator.cspublic interface IDocumentValidator { Task<PdfMetadata> ValidateAsync(PdfDocument pdf); bool IsValidFormat(byte[] content); } -
ICertificateService.cspublic 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:
-
ProcessDocumentRequest.cs(Record Type)public record ProcessDocumentRequest( string Base64Pdf, string TenantId, List<DocumentOperationDto> Operations ); -
ProcessDocumentResponse.cspublic record ProcessDocumentResponse( string Base64Pdf, PdfMetadata Metadata, List<string> PerformedOperations, bool Success ); -
DocumentOperationDto.cspublic record DocumentOperationDto( DocumentOperationType Type, Dictionary<string, object>? Parameters ); -
AttachmentDto.cs -
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:
-
ValidatePdfQuery.cspublic record ValidatePdfQuery(string Base64Pdf, string TenantId) : IRequest<PdfMetadata>; -
ValidatePdfHandler.cspublic 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; } } -
ValidatePdfValidator.cs(FluentValidation)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:
DevExpressPdfProcessor.cs : IPdfProcessor
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
PdfProcessingExceptionbei Fehlern - Nutzt DevExpress API
- Async wo möglich
? Step 4.2: File Storage Service
Aufgabe: IFileStorageService implementieren
Zu erstellen:
LocalFileStorageService.cs : IFileStorageService
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:
PdfDocumentValidator.cs : IDocumentValidator
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:
DependencyInjection.cs(Infrastructure Layer)
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:
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:
ExceptionHandlingMiddleware.cs
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:
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:
DocumentEndpoints.cs
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:
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:
SwaggerConfiguration.cs
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:
TenantResolutionMiddleware.cs
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:
ITenantContextInterface (Application)TenantContextImplementation (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
- Dependency Rule: Immer nach innen (Domain kennt nichts, Application nur Domain, etc.)
- Separation of Concerns: Jede Schicht hat klare Verantwortung
- Value Objects: Typsicherheit + Validierung in einem
- CQRS: Klare Trennung Commands/Queries
- Vertical Slices: Feature-basiert statt Layer-basiert
Exception-based Error Handling
Vorteile erkannt:
- Einfacherer Code (kein Result Boilerplate)
- Zentrales Handling (Middleware)
- Wartbarer (Fehler-Mapping an einer Stelle)
- Standard .NET Flow
Wichtig:
- FluentValidation für Input (erste Verteidigung)
- Domain Exceptions für Business-Fehler
- Middleware mapped zu HTTP Status Codes
- Serilog loggt alles
?? REFERENCES & RESOURCES
Documentation
- Clean Architecture (Uncle Bob)
- MediatR Documentation
- FluentValidation Docs
- DevExpress PDF API
- ASP.NET Core Minimal APIs
- RFC 7807 Problem Details
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.