Extensively updated `ROADMAP.md` to reflect the current project status, including documentation of the DevExpress Universal License, completion of Phases 1 and 2, and progress in Phase 3. Clarified discrepancies in the Application Layer and identified gaps in the Tests Layer and Infrastructure Services. Created `STATUS_UPDATE_17_01_2025.md` to summarize the current status, key learnings, and next steps. Outlined a TDD-driven approach for implementing the `DevExpressPdfProcessor` and cleaning up the Application Layer. Confirmed build success and updated documentation to align with the latest roadmap.
41 KiB
?? DocumentOperator - Project Roadmap (Pragmatic Edition)
Last Updated: 17.01.2025 | Status: In Development | Phase: 2 (Domain Layer - Minimal)
?? TABLE OF CONTENTS
- Project Overview
- Architecture & Design Decisions
- Development Philosophy
- Technology Stack
- Project Structure
- Development Roadmap
- Testing Strategy
- 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, Anhänge)
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 (Minimal API Endpoint)
?
[FluentValidation] ? [MediatR Handler] ? [DevExpress Service] ? [Ergebnis]
?
[HTTP Response] - JSON mit verarbeitetem PDF (Base64)
Typischer Ablauf:
- Client sendet PDF als Base64 in JSON
- API validiert Input (FluentValidation in MediatR Pipeline)
- Handler konvertiert PDF ? Byte-Array
- DevExpress Service führt Operation durch
- Temporäre Dateien werden erstellt/bereinigt (falls nötig)
- Ergebnis wird als Base64 zurückgegeben
??? ARCHITECTURE & DESIGN DECISIONS
Clean Architecture (Pragmatisch!)
Wir verwenden Clean Architecture mit 4 Layers - ABER: pragmatisch, nicht dogmatisch!
???????????????????????????????????????
? API Layer (Endpoints) ? ? HTTP Entry Point
???????????????????????????????????????
? Application Layer (Use Cases) ? ? MediatR Handlers, DTOs
???????????????????????????????????????
? Infrastructure Layer (Tech Stack) ? ? DevExpress, File I/O
???????????????????????????????????????
? Domain Layer (MINIMAL!) ? ? Nur Enums + Value Objects
???????????????????????????????????????
Dependency Rule
Abhängigkeiten zeigen immer nach innen:
API ? Application ? Domain
API ? Infrastructure ? Domain
Infrastructure ? Application (für Interfaces)
Domain ? NICHTS! (No External Dependencies)
Application ? NUR Domain
Warum Clean Architecture?
- ? Testbarkeit (Application Layer kann Services mocken)
- ? Austauschbarkeit (DevExpress ? anderes PDF-Lib ohne Application zu ändern)
- ? Separation of Concerns (jede Schicht hat klare Verantwortung)
ABER:
- ? Kein Overengineering (nur was wir wirklich brauchen!)
- ? Keine spekulativen Abstraktionen (erst wenn 2. Use Case es braucht)
- ? Keine unnötigen Klassen (YAGNI - You Ain't Gonna Need It)
Domain Layer - Warum so minimal?
Was wir NICHT haben:
- ? Keine Datenbank / EF Core
- ? Keine komplexen Entities mit Business-Logik
- ? Keine Aggregate Roots, Repositories, etc.
Was wir SIND:
- ? Ein Service (nicht eine Domain-lastige Business-Anwendung)
- ? PDF-Operationen = technische Operationen (nicht fachliche Geschäftslogik)
- ? Daten fließen durch (Input ? Verarbeitung ? Output)
Deshalb: Domain Layer minimal!
Was bleibt in Domain:
-
Enums (DocumentOperationType, ProcessingStatus)
- Pure Business-Konzepte
- Technologie-unabhängig
- Wiederverwendbar über alle Layer
-
Value Objects (Base64String, TenantId, PdfMetadata)
- Typsicherheit (Base64String statt string)
- Selbst-validierend (Fehler werfen im Constructor)
- Immutable (keine Änderungen nach Erstellung)
-
Domain Exceptions (DomainValidationException, PdfProcessingException, etc.)
- Für fachliche Fehler
- Exception Middleware mapped zu HTTP Status Codes
Was wir NICHT in Domain haben:
- ? Domain Models (PdfDocument, DocumentAttachment) ? DTOs in Application reichen!
- ? Constants (ErrorCodes) ? erst wenn wirklich mehrfach gebraucht (YAGNI)
- ? Services (? Infrastructure)
Fazit:
- Domain = so viel wie nötig, so wenig wie möglich
- Wenn wir später merken "das fehlt" ? dann erst hinzufügen (iterativ!)
CQRS with MediatR
Pattern: Command Query Responsibility Segregation
Warum MediatR?
- ? Klare Trennung: 1 Command/Query = 1 Handler = 1 Verantwortung
- ? Testbarkeit (Handler kann isoliert getestet werden)
- ? Pipeline Behaviors (Validation, Logging zentral)
- ? Kein aufgeblähter Service mit 20 Methoden
CQRS in unserem Kontext:
- Command: Ändert Daten (ProcessDocument, ApplyStamp, etc.)
- Query: Liest Daten (ValidatePdf ? gibt nur Metadata zurück)
Beispiel:
// Query (Read-Only)
public record ValidatePdfQuery(Base64String PdfContent) : IRequest<PdfMetadata>;
// Handler
public class ValidatePdfHandler : IRequestHandler<ValidatePdfQuery, PdfMetadata>
{
private readonly IPdfProcessor _processor;
public async Task<PdfMetadata> Handle(ValidatePdfQuery query, CancellationToken ct)
{
byte[] bytes = query.PdfContent.ToByteArray();
var metadata = await _processor.ValidateAsync(bytes);
return metadata;
}
}
Warum Value Objects in Query/Command?
- ? Typsicherheit (Base64String vs string)
- ? Validierung bereits beim Erstellen der Query (nicht im Handler)
- ? Handler bleibt schlank (keine Validierungs-Boilerplate)
Vertical Slice Architecture
Statt Horizontal Layers (Commands/, Handlers/, Validators/):
? Horizontal (Schlecht für Wartung):
Application/
??? Commands/
? ??? ValidatePdfCommand.cs
? ??? ProcessDocumentCommand.cs
??? Handlers/
? ??? ValidatePdfHandler.cs
? ??? ProcessDocumentHandler.cs
??? Validators/
??? ValidatePdfValidator.cs
??? ProcessDocumentValidator.cs
Nutzen wir Vertical Slices (pro Feature alles zusammen):
? Vertical (Gut für Wartung):
Features/
??? ValidatePdf/
? ??? ValidatePdfQuery.cs
? ??? ValidatePdfHandler.cs
? ??? ValidatePdfValidator.cs
??? ProcessDocument/
??? ProcessDocumentCommand.cs
??? ProcessDocumentHandler.cs
??? ProcessDocumentValidator.cs
Vorteile:
- ? Zusammengehöriger Code ist zusammen (Cohesion)
- ? Einfacher zu finden ("Wo ist ValidatePdf?" ? ein Ordner!)
- ? Einfacher zu ändern (alle Dateien im gleichen Ordner)
- ? Weniger Merge-Konflikte im Team
Exception-based Error Handling
Entscheidung: Keine Result Pattern Library (Ardalis.Result entfernt)
Stattdessen:
- FluentValidation für Input-Validierung (DTO-Ebene)
- Domain Exceptions für fachliche Fehler
- Zentrale Exception Handling Middleware im API Layer
Warum Exception-basiert?
- ? Einfacherer Code (kein
if (result.IsSuccess)überall) - ? Weniger Boilerplate (kein Result Wrapping)
- ? Standard .NET Exception-Flow (jeder kennt es)
- ? Zentrales Error Handling = wartbar an einer Stelle
- ? Ein Package weniger (keine Extra-Lib)
Flow:
HTTP Request
?
FluentValidation (MediatR ValidationBehavior)
? Bei Fehler: ValidationException ? Middleware ? HTTP 400
?
Handler
? Bei Fehler: DomainException ? Middleware ? HTTP 400/404/500
?
Middleware (Exception Handler)
? Mappt Exception Type ? HTTP Status Code
? Loggt Exception (Serilog)
? Gibt Problem Details (RFC 7807) zurück
?
HTTP Response (JSON)
Exception Types:
FluentValidation.ValidationException? HTTP 400 (Bad Request)DomainValidationException? HTTP 400 (Bad Request)NotFoundException? HTTP 404 (Not Found)PdfProcessingException? HTTP 500 (Internal Server Error)Exception(Catch-All) ? HTTP 500
Warum zentral?
- Alle Fehler an einer Stelle behandelt
- Konsistente Error-Responses (Problem Details Format)
- Handler bleiben schlank (kein Try/Catch in jedem Handler)
- Logging zentral (Serilog)
Minimal APIs (statt Controllers)
Warum Minimal APIs?
- ? .NET 8 Best Practice (Microsoft empfiehlt es)
- ? Weniger Boilerplate (keine Controller-Klassen)
- ? Direkte Endpoint-Definition (funktionaler Stil)
- ? Swagger funktioniert 1:1 (WithOpenApi())
- ? Bessere Performance (weniger Abstraktion)
Beispiel:
app.MapPost("/api/v1/documents/validate", async (
ValidatePdfRequest request,
IMediator mediator,
CancellationToken ct) =>
{
// DTO ? Query (Value Objects erstellen)
var query = new ValidatePdfQuery(
Base64String.Create(request.Base64Pdf)
);
// MediatR Handler aufrufen
var result = await mediator.Send(query, ct);
// HTTP 200 + JSON Response
return Results.Ok(result);
})
.WithName("ValidatePdf")
.WithTags("Documents")
.WithOpenApi();
Flow:
- HTTP Request kommt rein
- ASP.NET Core deserialisiert JSON ? DTO
- Endpoint ruft MediatR auf
- MediatR Pipeline: Validation ? Handler ? Response
- Endpoint gibt Result zurück (Results.Ok())
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 (später)
Flow:
HTTP Request mit Header "X-API-Key: abc123"
?
TenantResolutionMiddleware (wird später implementiert)
?
API-Key validieren (aus appsettings.json oder Redis)
?
Tenant-Info auflösen (TenantId, TenantName, IsActive)
?
ITenantContext setzen (Scoped Service)
?
Handler nutzt ITenantContext.TenantId
Beispiel - Tenant-spezifischer Stamp:
public class ApplyStampHandler : IRequestHandler<ApplyStampCommand, byte[]>
{
private readonly ITenantContext _tenantContext;
public async Task<byte[]> Handle(ApplyStampCommand command, CancellationToken ct)
{
// Tenant-spezifisches Logo laden
var logoPath = $"logos/{_tenantContext.TenantId}/stamp.png";
// Stamp mit Logo anwenden
// ...
}
}
??? TECHNOLOGY STACK
Core Framework
| Technology | Version | Purpose |
|---|---|---|
| .NET | 8.0 | Runtime & Framework |
| ASP.NET Core | 8.0 | Web API |
| C# | 12 | Language (Primary Constructors, Record Types) |
NuGet 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 Cache (später) |
Application Layer
| Package | Version | Purpose |
|---|---|---|
| MediatR | 14.1.0 | CQRS Pattern Implementation |
| FluentValidation | 12.1.1 | Input Validation (DTOs) |
| FluentValidation.DependencyInjectionExtensions | 12.1.1 | DI Integration |
Infrastructure Layer
| Package | Version | Purpose |
|---|---|---|
| DevExpress.Pdf.Core | 25.2.8 | PDF-Operationen (Merge, Extract, Sign, etc.) |
| DevExpress Universal License | ? Verfügbar | Vollzugriff auf alle DevExpress Bibliotheken |
| Microsoft.Extensions.Options.ConfigurationExtensions | 8.0.0 | Options Pattern |
Hinweis zur DevExpress Lizenz:
- ? Universal License vorhanden - wir können ALLE DevExpress Pakete nutzen
- Neben
DevExpress.Pdf.Corekönnen wir auch weitere Pakete integrieren:DevExpress.Office.Core(Word, Excel)DevExpress.Document.Processor(erweiterte Dokumenten-Verarbeitung)DevExpress.Blazor(falls UI später benötigt wird)- Alle weiteren DevExpress Produkte nach Bedarf
Domain Layer
| Package | Version | Purpose |
|---|---|---|
| - | - | Keine Dependencies! (Clean Architecture) |
Tests (neu!)
| Package | Version | Purpose |
|---|---|---|
| xUnit | 2.9.3 | Test Framework |
| FluentAssertions | 7.0.0 | Assertions (result.Should().Be(expected)) |
| Moq | 4.20.72 | Mocking (für Services) |
| Microsoft.NET.Test.Sdk | 17.11.1 | Test SDK |
| xunit.runner.visualstudio | 2.8.2 | Visual Studio Test Runner |
?? PROJECT STRUCTURE
Solution Overview
DocumentOperator/
??? DocumentOperator.API/ ? HTTP Entry Point
??? DocumentOperator.Application/ ? Use Cases (MediatR Handlers)
??? DocumentOperator.Infrastructure/ ? Technical Implementations
??? DocumentOperator.Domain/ ? Business Logic (MINIMAL!)
??? DocumentOperator.Tests/ ? Unit & Integration Tests (NEU!)
??? ROADMAP.md ? This file
?? API Layer (DocumentOperator.API)
Purpose: HTTP Entry Point, Routing, Middleware
References:
- ? Application
- ? Infrastructure
- ? Domain
Folder Structure:
DocumentOperator.API/
??? Endpoints/
? ??? v1/
? ??? DocumentEndpoints.cs ? Minimal API Endpoints
??? Middleware/
? ??? ExceptionHandlingMiddleware.cs ? Zentrale Exception Handling ?
??? Configuration/
? ??? SwaggerConfiguration.cs ? Swagger Setup (API-Key Support)
??? appsettings.json ? Base Configuration
??? appsettings.Development.json ? Dev Overrides
??? Program.cs ? Application Entry Point
Was gehört hierher:
- ? HTTP Routing (Minimal APIs)
- ? Middleware (Exception, Logging)
- ? Swagger Configuration
- ? Dependency Injection Setup
- ? appsettings.json
Was NICHT hierher gehört:
- ? Business Logic (? Application)
- ? PDF-Verarbeitung (? Infrastructure)
- ? Validierung (? Application: FluentValidation)
?? Application Layer (DocumentOperator.Application)
Purpose: Use Cases, Business Logic Orchestration
References:
- ? Domain (ONLY!)
Folder Structure:
DocumentOperator.Application/
??? Features/ ? Vertical Slices ?
? ??? Documents/
? ??? ValidatePdf/
? ? ??? ValidatePdfQuery.cs
? ? ??? ValidatePdfHandler.cs
? ? ??? ValidatePdfValidator.cs
? ??? ExtractAttachments/
? ? ??? ExtractAttachmentsCommand.cs
? ? ??? ExtractAttachmentsHandler.cs
? ? ??? ExtractAttachmentsValidator.cs
? ??? ... (weitere Features iterativ)
??? Common/
? ??? Interfaces/ ? Abstractions für Infrastructure
? ? ??? IPdfProcessor.cs
? ??? Behaviors/ ? MediatR Pipeline Behaviors
? ? ??? ValidationBehavior.cs ? FluentValidation Integration
? ??? DTOs/ ? Data Transfer Objects (API Contracts)
? ??? ValidatePdfRequest.cs
? ??? ValidatePdfResponse.cs
??? DependencyInjection.cs ? Service Registration
Was gehört hierher:
- ? MediatR Commands & Queries (pro Feature)
- ? Handlers (orchestrieren Domain + Infrastructure)
- ? FluentValidation Validators
- ? DTOs (API Contracts)
- ? Interfaces für Infrastructure (Dependency Inversion!)
- ? Pipeline Behaviors (Validation, Logging)
Was NICHT hierher gehört:
- ? DevExpress-spezifischer Code (? Infrastructure)
- ? File I/O (? Infrastructure)
- ? HTTP-spezifisches (? API)
Warum keine Infrastructure-Referenz?
- Application kennt nur Interfaces (
IPdfProcessor) - Infrastructure implementiert die Interfaces (
DevExpressPdfProcessor) - API injiziert die Implementierung via DI
- ? Application bleibt technologie-unabhängig!
?? Infrastructure Layer (DocumentOperator.Infrastructure)
Purpose: Technische Implementierungen
References:
- ? Application (für Interfaces)
- ? Domain
Folder Structure:
DocumentOperator.Infrastructure/
??? Services/
? ??? PdfProcessing/
? ??? DevExpressPdfProcessor.cs ? IPdfProcessor Implementation
??? Configuration/
? ??? DocumentOperatorSettings.cs ? Options Pattern Class
? ??? ApiKeySettings.cs
? ??? TenantInfo.cs
??? DependencyInjection.cs ? Service Registration
Was gehört hierher:
- ? DevExpress Integration
- ? File System Zugriffe (Temp-Files)
- ? Options Pattern Classes (Settings)
Was NICHT hierher gehört:
- ? Business Logic (? Application)
- ? HTTP Handling (? API)
??? Domain Layer (DocumentOperator.Domain) - MINIMAL!
Purpose: Business Rules (nur was wirklich gebraucht wird!)
References:
- ? KEINE! (wichtigste Clean Architecture Regel)
Folder Structure:
DocumentOperator.Domain/
??? ValueObjects/ ? Immutable, selbst-validierend
? ??? Base64String.cs
? ??? TenantId.cs
? ??? PdfMetadata.cs
??? Enums/
? ??? DocumentOperationType.cs
? ??? ProcessingStatus.cs
??? Exceptions/ ? Domain-spezifische Exceptions
??? DomainException.cs
??? DomainValidationException.cs
??? NotFoundException.cs
??? PdfProcessingException.cs
Was gehört hierher:
- ? Value Objects (Base64String, TenantId, PdfMetadata)
- ? Enums (DocumentOperationType, ProcessingStatus)
- ? Domain Exceptions
Was NICHT hierher gehört:
- ? Domain Models (PdfDocument, etc.) ? YAGNI! DTOs reichen!
- ? Constants (ErrorCodes) ? erst wenn mehrfach gebraucht
- ? Services (? Infrastructure)
- ? MediatR (? Application)
- ? JEGLICHE externe Library!
?? Tests Layer (DocumentOperator.Tests) - NEU!
Purpose: Unit & Integration Tests
References:
- ? Alle Projekte (API, Application, Infrastructure, Domain)
Folder Structure:
DocumentOperator.Tests/
??? Unit/
? ??? Application/
? ? ??? Features/
? ? ??? ValidatePdf/
? ? ??? ValidatePdfHandlerTests.cs
? ??? Infrastructure/
? ? ??? Services/
? ? ??? DevExpressPdfProcessorTests.cs
? ??? Domain/
? ??? ValueObjects/
? ??? Base64StringTests.cs
??? Integration/
??? API/
??? ValidatePdfEndpointTests.cs
Test-Strategie:
- ? TDD (Test-Driven Development)
- ? Unit Tests für Handler (Application Layer)
- ? Unit Tests für Services (Infrastructure Layer)
- ? Unit Tests für Value Objects (Domain Layer)
- ? Integration Tests für Endpoints (API Layer)
?? DEVELOPMENT PHILOSOPHY
Pragmatisch, nicht dogmatisch!
Prinzipien:
-
YAGNI (You Ain't Gonna Need It)
- ? Keine spekulativen Abstraktionen
- ? Keine Klassen "für später"
- ? Erst wenn 2. Use Case es braucht ? dann Abstrahieren
-
KISS (Keep It Simple, Stupid)
- ? Kein Overengineering
- ? Keine unnötigen Design Patterns
- ? Einfachster Code der funktioniert
-
Clean Architecture JA, aber pragmatisch
- ? Dependency Rule einhalten (wichtig!)
- ? Separation of Concerns (wichtig!)
- ? ABER: Nur Abstraktionen die wir wirklich brauchen
-
Test-Driven Development (TDD)
- ? Tests schreiben bevor Code (Red ? Green ? Refactor)
- ? Tests als Dokumentation (wie wird es genutzt?)
- ? Tests als Safety Net (Refactoring ohne Angst)
-
Outside-In Development
- ? Von außen nach innen bauen (API ? Service ? Domain)
- ? Wir sehen sofort was funktioniert (kein "spekulatives" Code)
- ? Feedback-Loop schneller
Konkret für unser Projekt:
- Domain Layer minimal (nur Enums + Value Objects + Exceptions)
- Keine Domain Models (DTOs in Application reichen!)
- Keine Constants (erst wenn mehrfach gebraucht)
- Iterativ entwickeln (Feature für Feature)
- TDD (Test ? Code ? Refactor)
??? DEVELOPMENT ROADMAP
? PHASE 1: Foundation - COMPLETED
Bereits erledigt:
- Solution erstellt (4 Projekte)
- Dependencies korrekt (Clean Architecture Dependency Rule)
- NuGet Packages installiert
- Folder-Struktur erstellt
- appsettings.json konfiguriert
- Options Pattern Classes erstellt
- Serilog Setup (Program.cs)
? PHASE 2: Domain Layer (Minimal) - COMPLETED
Ziel: Nur was wirklich gebraucht wird!
Status: ? Alle Steps abgeschlossen!
? Step 2.1: Domain Exceptions erstellen - COMPLETED
Bereits erstellt:
DomainException.cs(Basis-Exception)DomainValidationException.cs(Value Object Validierung)NotFoundException.cs(Resource nicht gefunden)PdfProcessingException.cs(PDF-spezifische Fehler)
Wo: Domain/Common/Exceptions/
? Step 2.2: Enums erstellen - COMPLETED
Aufgabe: Aufzählungen für Business-Konzepte
Warum JETZT (vor Value Objects)?
- Enums haben keine Dependencies
- Werden in Value Objects gebraucht (z.B. PdfMetadata)
- Schneller Erfolg (5 Minuten Arbeit)
Was du tun wirst:
-
DocumentOperationType.cs erstellen
- Wo:
Domain/Models/Enums/DocumentOperationType.cs - Inhalt:
namespace DocumentOperator.Domain.Models.Enums; public enum DocumentOperationType { Validate, ExtractAttachments, Concatenate, ApplyStamp, EmbedCertificate } - Warum: Definiert welche Operationen unser Service kann
- Wo gebraucht: Später in Commands/DTOs
- Wo:
-
ProcessingStatus.cs erstellen
- Wo:
Domain/Models/Enums/ProcessingStatus.cs - Inhalt:
namespace DocumentOperator.Domain.Models.Enums; public enum ProcessingStatus { Pending, Processing, Success, Failed } - Warum: Status für asynchrone Operationen (später: Queue)
- Wo gebraucht: Response DTOs
- Wo:
Nach diesem Step:
- Ich prüfe deine Dateien
- Wir haken Step 2.2 ab in ROADMAP.md
- Weiter zu Step 2.3 (Value Objects)
Status: ? COMPLETED (17.01.2025)
- ? DocumentOperationType.cs erstellt
- ? ProcessingStatus.cs erstellt
- ? Build erfolgreich
? Step 2.3: Value Objects erstellen - COMPLETED
Aufgabe: Typsichere, selbst-validierende Wert-Objekte
Warum Value Objects?
- ? Typsicherheit:
Base64Stringstattstring - ? Validierung an einer Stelle (Constructor)
- ? Immutable (keine Änderungen nach Erstellung)
- ? Wiederverwendbar (in Domain, Application, Infrastructure)
Was du erstellt hast:
-
Base64String.cs ?
- Factory Method:
Create(string value) - Validierung: Gültiges Base64-Format
- Konvertierung:
ToByteArray(),FromByteArray(byte[]) - 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, AttachmentCount
- Computed Property:
FileSizeMB - Keine Validierung (nur Daten-Container)
Wo: Domain/Models/ValueObjects/
Status: ? COMPLETED (17.01.2025)
- ? Base64String.cs erstellt (sealed, Factory Methods, Validierung, Equality)
- ? TenantId.cs erstellt (sealed, Normalisierung, Validierung, Equality)
- ? PdfMetadata.cs erstellt (sealed, Computed Property, ToString())
- ? Build erfolgreich
?? Phase 2 (Domain Layer) komplett abgeschlossen!
? PHASE 3: Infrastructure Layer (Outside-In!) - NEXT
Ziel: DevExpress Services implementieren (wir sehen echten Code!)
? Step 3.1: IPdfProcessor Interface erstellen - COMPLETED
Aufgabe: Abstraction für PDF-Operationen
Was du erstellt hast:
- Wo:
Application/Common/Interfaces/IPdfProcessor.cs? - Inhalt:
using DocumentOperator.Domain.Models.ValueObjects; namespace DocumentOperator.Application.Common.Interfaces; public interface IPdfProcessor { Task<PdfMetadata> ValidateAsync(byte[] pdfBytes); }
Warum Interface ERST?
- Application kennt nur Interface (Dependency Inversion)
- Infrastructure implementiert
- TDD: Test ? Interface ? Implementation
Status: ? COMPLETED (17.01.2025)
- ? Interface erstellt mit XML Comments
- ? Using Statement korrekt
- ? Namespace korrekt
- ? Build erfolgreich
?? Step 3.2: DevExpressPdfProcessor implementieren (mit TDD!) - NEXT
Aufgabe: DevExpress Integration
Flow:
-
Test schreiben (Red)
[Fact] public async Task ValidateAsync_ValidPdf_ReturnMetadata() { // Arrange var processor = new DevExpressPdfProcessor(); byte[] validPdf = CreateDummyPdf(); // Act var metadata = await processor.ValidateAsync(validPdf); // Assert metadata.PageCount.Should().BeGreaterThan(0); } -
Implementation schreiben (Green)
public class DevExpressPdfProcessor : IPdfProcessor { public async Task<PdfMetadata> ValidateAsync(byte[] pdfBytes) { using var processor = new PdfDocumentProcessor(); processor.LoadDocument(pdfBytes); return new PdfMetadata( PageCount: processor.Document.Pages.Count, FileSizeBytes: pdfBytes.Length, // ... ); } } -
Test grün machen
-
Refactoring (falls nötig)
Wo:
- Test:
Tests/Unit/Infrastructure/Services/DevExpressPdfProcessorTests.cs - Code:
Infrastructure/Services/PdfProcessing/DevExpressPdfProcessor.cs
Nach diesem Step:
- Wir haben echten Code der mit DevExpress arbeitet!
- Wir wissen welche Exceptions geworfen werden können
- Wir können Exception Middleware bauen
? PHASE 4: Application Layer (erste Feature)
Ziel: ValidatePdf Feature komplett (Query ? Handler ? Validator)
? Step 4.1: MediatR Setup
Aufgabe: MediatR + FluentValidation + ValidationBehavior
Was du erstellen wirst:
DependencyInjection.cs(Application Layer)ValidationBehavior.cs(MediatR Pipeline)
Warum jetzt?
- Wir brauchen MediatR für Handler
- ValidationBehavior = zentrale FluentValidation Ausführung
? Step 4.2: ValidatePdf Feature (mit TDD!)
Aufgabe: Erste komplette Feature-Implementierung
Was du erstellen wirst:
-
ValidatePdfQuery.cs
public record ValidatePdfQuery(Base64String PdfContent) : IRequest<PdfMetadata>; -
ValidatePdfHandler.cs
public class ValidatePdfHandler : IRequestHandler<ValidatePdfQuery, PdfMetadata> { private readonly IPdfProcessor _processor; public async Task<PdfMetadata> Handle(ValidatePdfQuery query, CancellationToken ct) { byte[] bytes = query.PdfContent.ToByteArray(); return await _processor.ValidateAsync(bytes); } } -
ValidatePdfValidator.cs (FluentValidation)
public class ValidatePdfValidator : AbstractValidator<ValidatePdfQuery> { public ValidatePdfValidator() { RuleFor(x => x.PdfContent).NotNull(); } } -
ValidatePdfHandlerTests.cs (Unit Test)
Wo: Application/Features/Documents/ValidatePdf/
Flow:
DTO ? Query (Value Objects) ? ValidationBehavior (FluentValidation)
? Handler ? IPdfProcessor ? PdfMetadata
? PHASE 5: API Layer
Ziel: HTTP Endpoint + Exception Middleware
? Step 5.1: Exception Handling Middleware
Aufgabe: Zentrale Exception ? HTTP Response Mapping
Was du erstellen wirst:
- Wo:
API/Middleware/ExceptionHandlingMiddleware.cs - Inhalt:
public class ExceptionHandlingMiddleware { public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (DomainValidationException ex) { await HandleDomainValidationExceptionAsync(context, ex); } catch (PdfProcessingException ex) { await HandlePdfProcessingExceptionAsync(context, ex); } // ... weitere Exceptions } private static Task HandleDomainValidationExceptionAsync(...) { 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 = 400, Detail = ex.Message }; return context.Response.WriteAsJsonAsync(problemDetails); } }
Warum jetzt?
- Wir kennen jetzt alle Exceptions (aus Infrastructure Step)
- Wir können sie zu HTTP Status Codes mappen
? Step 5.2: Minimal API Endpoint
Aufgabe: HTTP Endpoint für ValidatePdf
Was du erstellen wirst:
- Wo:
API/Endpoints/v1/DocumentEndpoints.cs - Inhalt:
public static class DocumentEndpoints { public static void MapDocumentEndpoints(this IEndpointRouteBuilder app) { var group = app.MapGroup("/api/v1/documents") .WithTags("Documents") .WithOpenApi(); group.MapPost("/validate", ValidatePdf); } private static async Task<IResult> ValidatePdf( ValidatePdfRequest request, IMediator mediator, CancellationToken ct) { var query = new ValidatePdfQuery( Base64String.Create(request.Base64Pdf) ); var result = await mediator.Send(query, ct); return Results.Ok(result); } }
DTOs:
ValidatePdfRequest(Input)ValidatePdfResponse(Output) ? oder direkt PdfMetadata?
In Program.cs registrieren:
app.MapDocumentEndpoints();
? Step 5.3: Integration Test
Aufgabe: End-to-End Test (HTTP ? Handler ? Service)
Was du erstellen wirst:
- Wo:
Tests/Integration/API/ValidatePdfEndpointTests.cs - Inhalt:
public class ValidatePdfEndpointTests : IClassFixture<WebApplicationFactory<Program>> { [Fact] public async Task POST_ValidatePdf_ValidPdf_Returns200() { // Arrange var client = _factory.CreateClient(); var request = new ValidatePdfRequest(Base64Pdf: "..."); // Act var response = await client.PostAsJsonAsync("/api/v1/documents/validate", request); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var metadata = await response.Content.ReadFromJsonAsync<PdfMetadata>(); metadata.PageCount.Should().BeGreaterThan(0); } }
Warum Integration Test?
- Testet kompletten Flow (HTTP ? MediatR ? Service ? Response)
- Testet Exception Middleware
- Testet Swagger/OpenAPI
? PHASE 6: Weitere Features (iterativ)
Nach ValidatePdf (als Referenz):
Jedes Feature folgt dem gleichen Pattern:
- Interface erweitern (IPdfProcessor)
- Service implementieren (DevExpressPdfProcessor) + Test
- Command/Query + Handler + Validator
- Endpoint erstellen
- Integration Test
Features:
- ExtractAttachments
- ConcatenatePdfs
- ApplyStamp
- EmbedCertificate
? PHASE 7: Swagger & API Documentation
Ziel: Produktionsreife API-Dokumentation
Steps:
- Swagger Configuration (API-Key Support)
- XML Comments für Endpoints
- Response Examples (Swashbuckle)
? PHASE 8: Multi-Tenancy (später)
Ziel: Mandantenfähigkeit implementieren
Steps:
- ITenantContext Interface (Application)
- TenantContext Implementation (Infrastructure)
- TenantResolutionMiddleware (API)
- Tenant-spezifische Settings (Logo, Zertifikat)
? PHASE 9: Production-Ready
Ziel: Deployment vorbereiten
Steps:
- Health Checks
- appsettings.Production.json
- IIS Deployment (Web.config)
- Redis Integration (Distributed Cache)
?? TESTING STRATEGY
Test-Driven Development (TDD)
Flow:
- Red: Test schreiben (schlägt fehl, weil Code noch nicht existiert)
- Green: Code schreiben (Test wird grün)
- Refactor: Code verbessern (Test bleibt grün)
Warum TDD?
- ? Tests als Dokumentation (wie wird es genutzt?)
- ? Tests als Safety Net (Refactoring ohne Angst)
- ? Besseres Design (testbarer Code = guter Code)
- ? Keine "vergessenen" Tests (Test kommt ZUERST)
Test-Pyramide
/\
/ \ E2E Tests (wenige)
/ \
/------\ Integration Tests (einige)
/ \
/----------\ Unit Tests (viele)
/ \
Konkret:
-
Unit Tests (viele):
- Value Objects (Base64String.Create() wirft Exception?)
- Handlers (ValidatePdfHandler ruft IPdfProcessor auf?)
- Services (DevExpressPdfProcessor gibt Metadata zurück?)
-
Integration Tests (einige):
- Endpoints (HTTP POST ? 200 OK + JSON?)
- MediatR Pipeline (ValidationBehavior funktioniert?)
-
E2E Tests (wenige/keine):
- Haben wir nicht (API ist selbst der "Top-Level")
Test-Abdeckung
Ziel: >80% Code Coverage (aber nicht um jeden Preis!)
Was testen:
- ? Value Objects (Validierung)
- ? Handlers (Business Logic)
- ? Services (DevExpress Integration)
- ? Endpoints (HTTP Responses)
- ? Exception Middleware (Error Mapping)
Was NICHT testen:
- ? DTOs (keine Logik)
- ? Enums (keine Logik)
- ? Program.cs (Startup Code)
Test-Naming Convention
Pattern: MethodName_Scenario_ExpectedResult
Beispiele:
// Value Object Tests
[Fact]
public void Create_EmptyString_ThrowsDomainValidationException() { }
[Fact]
public void Create_ValidBase64_ReturnsBase64String() { }
// Handler Tests
[Fact]
public async Task Handle_ValidPdf_ReturnsPdfMetadata() { }
[Fact]
public async Task Handle_InvalidPdf_ThrowsPdfProcessingException() { }
// Endpoint Tests
[Fact]
public async Task POST_ValidatePdf_ValidPdf_Returns200() { }
[Fact]
public async Task POST_ValidatePdf_InvalidPdf_Returns400() { }
?? CURRENT STATUS
? Completed
-
Phase 1: Foundation & Clean Architecture Setup ?
- Solution Structure ?
- Dependencies ?
- NuGet Packages ?
- Folder Structure ?
- Configuration (appsettings.json) ?
- Serilog Setup ?
- Program.cs Setup ?
-
Phase 2: Domain Layer (Minimal) ?
- ? Step 2.1 - Domain Exceptions (4 Exceptions erstellt)
DomainException.csDomainValidationException.csNotFoundException.csPdfProcessingException.cs
- ? Step 2.2 - Enums (DocumentOperationType, ProcessingStatus)
- ? Step 2.3 - Value Objects (Base64String, TenantId, PdfMetadata)
- ? Step 2.1 - Domain Exceptions (4 Exceptions erstellt)
-
Phase 3: Infrastructure Layer (Outside-In!)
- ? Step 3.1 - IPdfProcessor Interface erstellt
?? In Progress
- Phase 3: Infrastructure Layer
- NEXT: Step 3.2 - DevExpressPdfProcessor implementieren (mit TDD!)
- ?? Wichtig: Ordnerstruktur existiert, aber Services noch nicht implementiert
- ?? Wichtig: Application Layer hat ProcessDocument-Dateien (leer), sollte ValidatePdf sein
? Pending
-
Phase 3: Infrastructure Layer
- Step 3.2 - DevExpressPdfProcessor Implementation
-
Phase 4: Application Layer
- Step 4.1 - MediatR Setup (DependencyInjection.cs, ValidationBehavior.cs)
- Step 4.2 - ValidatePdf Feature (Query, Handler, Validator)
-
Phase 5: API Layer
- Step 5.1 - Exception Handling Middleware
- Step 5.2 - Minimal API Endpoint
- Step 5.3 - Integration Test
-
Phase 6-9: Weitere Features, Swagger, Multi-Tenancy, Production
?? Hinweise zum aktuellen Stand
-
Application Layer - ProcessDocument vs ValidatePdf:
- Aktuell:
Features/Documents/ProcessDocument/(leer) - Roadmap:
Features/Documents/ValidatePdf/(geplant) - ?? Action: ProcessDocument-Dateien sollten gelöscht oder umbenannt werden
- Aktuell:
-
Tests Layer:
- Aktuell: Nur
UnitTest1.cs(Dummy-Test) - Roadmap: Ordnerstruktur für Unit/Integration Tests
- ?? Action: Ordnerstruktur erstellen, UnitTest1.cs löschen
- Aktuell: Nur
-
Infrastructure Services:
- Ordner existieren (PdfProcessing, FileStorage, DocumentValidation)
- Aber: Alle leer
- ?? Action: DevExpressPdfProcessor.cs implementieren (Step 3.2)
-
DevExpress Universal License:
- ? Verfügbar! Wir können alle DevExpress Pakete nutzen
- Aktuell nur:
DevExpress.Pdf.Core - Bei Bedarf können weitere Pakete hinzugefügt werden
?? KEY LEARNINGS & DECISIONS
1. Domain Layer minimal halten
Entscheidung: Nur Enums + Value Objects + Exceptions
Warum:
- Kein EF Core / Entities
- Service-Anwendung (nicht Domain-lastig)
- YAGNI (You Ain't Gonna Need It)
Alternative wäre gewesen:
- Volle Domain Models (PdfDocument, DocumentAttachment, etc.)
- Nachteile: Overengineering, unnötige Komplexität
2. Outside-In Development
Entscheidung: Infrastructure ? Application ? API
Warum:
- Wir sehen echten Code sofort (DevExpress Integration)
- Keine Spekulation (wir wissen welche Exceptions geworfen werden)
- Schnellerer Feedback-Loop
Alternative wäre gewesen:
- Domain ? Application ? Infrastructure ? API
- Nachteile: Viel "spekulativer" Code ohne echte Implementation
3. Exception-based Error Handling
Entscheidung: Keine Result Pattern Library
Warum:
- Einfacherer Code (kein Result Boilerplate)
- Zentrales Error Handling (Middleware)
- Standard .NET Exception-Flow
Alternative wäre gewesen:
- Ardalis.Result oder FluentResults
- Nachteile: Extra Package, mehr Boilerplate
4. TDD (Test-Driven Development)
Entscheidung: Test ? Code ? Refactor
Warum:
- Besseres Design (testbarer Code)
- Tests als Dokumentation
- Safety Net für Refactoring
Alternative wäre gewesen:
- Code ? Test (Test-After)
- Nachteile: Tests werden oft vergessen, schlechteres Design
5. Vertical Slice Architecture
Entscheidung: Pro Feature alles zusammen
Warum:
- Zusammengehöriger Code ist zusammen
- Einfacher zu finden und zu ändern
- Besser für Teams (weniger Merge-Konflikte)
Alternative wäre gewesen:
- Horizontal Layers (Commands/, Handlers/, Validators/)
- Nachteile: Code über viele Ordner verteilt
?? REFERENCES & BEST PRACTICES
Documentation
- Clean Architecture (Uncle Bob)
- MediatR Documentation
- FluentValidation Docs
- DevExpress PDF API
- ASP.NET Core Minimal APIs
- RFC 7807 Problem Details
- xUnit Documentation
- FluentAssertions Documentation
Best Practices Applied
- ? Clean Architecture (pragmatisch!)
- ? CQRS with MediatR
- ? Vertical Slice Architecture
- ? Value Objects (DDD)
- ? Exception-based Error Handling
- ? Minimal APIs (.NET 8)
- ? TDD (Test-Driven Development)
- ? 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 | ? Phase 1 completed |
| 2024-XX-XX | Phase 2 | ? Step 2.1 completed - Domain Exceptions created |
| 17.01.2025 | Roadmap | ?? ROADMAP komplett überarbeitet (Pragmatisch, Outside-In, TDD) |
| 17.01.2025 | Phase 2 | ? Step 2.2 completed - Enums erstellt |
| 17.01.2025 | Phase 2 | ? Step 2.3 completed - Value Objects erstellt |
| 17.01.2025 | Phase 2 | ? Phase 2 (Domain Layer) komplett abgeschlossen! |
| 17.01.2025 | Phase 3 | ? Step 3.1 completed - IPdfProcessor Interface erstellt |
| 17.01.2025 | Roadmap | ?? ROADMAP Status-Update - Aktueller Projektstand dokumentiert |
| 17.01.2025 | Infrastructure | ?? DevExpress Universal License hinzugefügt - Vollzugriff auf alle Pakete |
END OF ROADMAP
This document is a living document and will be updated as development progresses.