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

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

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

42 KiB

?? DocumentOperator - Project Roadmap

Last Updated: 2024 | 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 - 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 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:

  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
    • 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 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 ?? Starting Domain Layer - Exceptions

END OF ROADMAP

This document is a living document and will be updated as development progresses.