Files
Services.DDDocumentOperator/DocumentOperator.API/ROADMAP.md
OlgunR 758d32d8e0 Update ROADMAP.md with detailed project roadmap
- 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.
2026-06-17 15:12:44 +02:00

44 KiB

📘 DocumentOperator - Project Roadmap

Last Updated: 16.06.2026 | Status: In Development | Phase: 2 (Domain Layer)


📋 TABLE OF CONTENTS

  1. Project Overview
  2. Architecture & Design Decisions
  3. Technology Stack
  4. Project Structure
  5. Development Roadmap
  6. 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:

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:

  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:

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:

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 - COMPLETED

Aufgabe: Custom Exception-Klassen für fachliche Fehler

Erstellt:

  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 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 - 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

    public enum DocumentOperationType
    {
        Validate,
        ExtractAttachments,
        Concatenate,
        ApplyStamp,
        EmbedCertificate
    }
    
  2. ProcessingStatus.cs

    public enum ProcessingStatus
    {
        Pending,
        Processing,
        Success,
        Failed
    }
    
  3. PdfValidationError.cs

    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

    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

    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

    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

    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

    public interface IDocumentValidator
    {
        Task<PdfMetadata> ValidateAsync(PdfDocument pdf);
        bool IsValidFormat(byte[] content);
    }
    
  4. ICertificateService.cs

    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)

    public record ProcessDocumentRequest(
        string Base64Pdf,
        string TenantId,
        List<DocumentOperationDto> Operations
    );
    
  2. ProcessDocumentResponse.cs

    public record ProcessDocumentResponse(
        string Base64Pdf,
        PdfMetadata Metadata,
        List<string> PerformedOperations,
        bool Success
    );
    
  3. DocumentOperationDto.cs

    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

    public record ValidatePdfQuery(string Base64Pdf, string TenantId) 
        : IRequest<PdfMetadata>;
    
  2. ValidatePdfHandler.cs

    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)

    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
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
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
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)
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:

  1. 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:

  1. 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:

  1. 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:

  1. 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:

  • 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 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

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.