Updated ROADMAP.md to reflect progress on the TDD process for DevExpressPdfProcessor, including the creation of unit tests (6 tests in the Red Phase). Updated progress to 4/7 mini-steps completed and outlined the next step (Green Phase). Added `DevExpressPdfProcessorTests.cs` with unit tests to validate PDF files and extract metadata. Tests cover valid PDFs, null/empty inputs, corrupted PDFs, and file size calculations. Used `FluentAssertions` for assertions. Cleaned up `DocumentOperator.Tests.csproj` by removing an unused folder reference. No functional changes to the project structure.
1495 lines
41 KiB
Markdown
1495 lines
41 KiB
Markdown
# ?? DocumentOperator - Project Roadmap (Pragmatic Edition)
|
|
|
|
> **Last Updated:** 17.01.2025 | **Status:** In Development | **Phase:** 2 (Domain Layer - Minimal)
|
|
|
|
---
|
|
|
|
## ?? TABLE OF CONTENTS
|
|
|
|
1. [Project Overview](#project-overview)
|
|
2. [Architecture & Design Decisions](#architecture--design-decisions)
|
|
3. [Development Philosophy](#development-philosophy)
|
|
4. [Technology Stack](#technology-stack)
|
|
5. [Project Structure](#project-structure)
|
|
6. [Development Roadmap](#development-roadmap)
|
|
7. [Testing Strategy](#testing-strategy)
|
|
8. [Current Status](#current-status)
|
|
|
|
---
|
|
|
|
## ?? PROJECT OVERVIEW
|
|
|
|
### Vision & Purpose
|
|
|
|
**DocumentOperator** ist ein zentralisierter REST API Service für PDF-Dokumenten-Operationen in einer Multi-Tenant DMS-Umgebung.
|
|
|
|
### Problem Statement
|
|
|
|
**Aktuell:**
|
|
- Verschiedene DMS-Kunden bei unterschiedlichen Mandanten
|
|
- Jede Anwendung implementiert PDF-Operationen redundant
|
|
- Keine zentrale Stelle für Dokumenten-Verarbeitung
|
|
- Wartungsaufwand multipliziert sich mit jeder Anwendung
|
|
|
|
**Lösung:**
|
|
- **Ein** zentraler Service für alle PDF-Operationen
|
|
- Wiederverwendbar über HTTP REST API
|
|
- Mandantenfähig (Multi-Tenancy)
|
|
- Wartbar an einer Stelle
|
|
|
|
---
|
|
|
|
### Core Features
|
|
|
|
Der Service bietet folgende PDF-Operationen:
|
|
|
|
#### 1. **PDF Validierung**
|
|
- Prüfung auf gültiges PDF-Format
|
|
- Korruptions-Erkennung
|
|
- Metadaten-Extraktion (Seitenzahl, Größe, Version, 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:**
|
|
1. Client sendet PDF als Base64 in JSON
|
|
2. API validiert Input (FluentValidation in MediatR Pipeline)
|
|
3. Handler konvertiert PDF ? Byte-Array
|
|
4. DevExpress Service führt Operation durch
|
|
5. Temporäre Dateien werden erstellt/bereinigt (falls nötig)
|
|
6. 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:**
|
|
1. **Enums** (DocumentOperationType, ProcessingStatus)
|
|
- Pure Business-Konzepte
|
|
- Technologie-unabhängig
|
|
- Wiederverwendbar über alle Layer
|
|
|
|
2. **Value Objects** (Base64String, TenantId, PdfMetadata)
|
|
- Typsicherheit (Base64String statt string)
|
|
- Selbst-validierend (Fehler werfen im Constructor)
|
|
- Immutable (keine Änderungen nach Erstellung)
|
|
|
|
3. **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:**
|
|
```csharp
|
|
// 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:**
|
|
1. **FluentValidation** für Input-Validierung (DTO-Ebene)
|
|
2. **Domain Exceptions** für fachliche Fehler
|
|
3. **Zentrale Exception Handling Middleware** im API Layer
|
|
|
|
**Warum Exception-basiert?**
|
|
- ? Einfacherer Code (kein `if (result.IsSuccess)` überall)
|
|
- ? Weniger Boilerplate (kein Result<T> 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:**
|
|
```csharp
|
|
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:**
|
|
1. HTTP Request kommt rein
|
|
2. ASP.NET Core deserialisiert JSON ? DTO
|
|
3. Endpoint ruft MediatR auf
|
|
4. MediatR Pipeline: Validation ? Handler ? Response
|
|
5. 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:**
|
|
```csharp
|
|
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.Core` kö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:**
|
|
|
|
1. **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
|
|
|
|
2. **KISS (Keep It Simple, Stupid)**
|
|
- ? Kein Overengineering
|
|
- ? Keine unnötigen Design Patterns
|
|
- ? Einfachster Code der funktioniert
|
|
|
|
3. **Clean Architecture JA, aber pragmatisch**
|
|
- ? Dependency Rule einhalten (wichtig!)
|
|
- ? Separation of Concerns (wichtig!)
|
|
- ? ABER: Nur Abstraktionen die wir wirklich brauchen
|
|
|
|
4. **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)
|
|
|
|
5. **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:**
|
|
- [x] Solution erstellt (4 Projekte)
|
|
- [x] Dependencies korrekt (Clean Architecture Dependency Rule)
|
|
- [x] NuGet Packages installiert
|
|
- [x] Folder-Struktur erstellt
|
|
- [x] appsettings.json konfiguriert
|
|
- [x] Options Pattern Classes erstellt
|
|
- [x] 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:**
|
|
1. [x] `DomainException.cs` (Basis-Exception)
|
|
2. [x] `DomainValidationException.cs` (Value Object Validierung)
|
|
3. [x] `NotFoundException.cs` (Resource nicht gefunden)
|
|
4. [x] `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:**
|
|
|
|
1. **DocumentOperationType.cs** erstellen
|
|
- **Wo:** `Domain/Models/Enums/DocumentOperationType.cs`
|
|
- **Inhalt:**
|
|
```csharp
|
|
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
|
|
|
|
2. **ProcessingStatus.cs** erstellen
|
|
- **Wo:** `Domain/Models/Enums/ProcessingStatus.cs`
|
|
- **Inhalt:**
|
|
```csharp
|
|
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
|
|
|
|
**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: `Base64String` statt `string`
|
|
- ? Validierung an **einer** Stelle (Constructor)
|
|
- ? Immutable (keine Änderungen nach Erstellung)
|
|
- ? Wiederverwendbar (in Domain, Application, Infrastructure)
|
|
|
|
**Was du erstellt hast:**
|
|
|
|
1. **Base64String.cs** ?
|
|
- Factory Method: `Create(string value)`
|
|
- Validierung: Gültiges Base64-Format
|
|
- Konvertierung: `ToByteArray()`, `FromByteArray(byte[])`
|
|
- 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, 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:**
|
|
```csharp
|
|
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:**
|
|
1. **Test schreiben** (Red)
|
|
```csharp
|
|
[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);
|
|
}
|
|
```
|
|
|
|
2. **Implementation schreiben** (Green)
|
|
```csharp
|
|
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,
|
|
// ...
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
3. **Test grün machen**
|
|
4. **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:**
|
|
1. `DependencyInjection.cs` (Application Layer)
|
|
2. `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:**
|
|
1. **ValidatePdfQuery.cs**
|
|
```csharp
|
|
public record ValidatePdfQuery(Base64String PdfContent) : IRequest<PdfMetadata>;
|
|
```
|
|
|
|
2. **ValidatePdfHandler.cs**
|
|
```csharp
|
|
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);
|
|
}
|
|
}
|
|
```
|
|
|
|
3. **ValidatePdfValidator.cs** (FluentValidation)
|
|
```csharp
|
|
public class ValidatePdfValidator : AbstractValidator<ValidatePdfQuery>
|
|
{
|
|
public ValidatePdfValidator()
|
|
{
|
|
RuleFor(x => x.PdfContent).NotNull();
|
|
}
|
|
}
|
|
```
|
|
|
|
4. **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:**
|
|
```csharp
|
|
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:**
|
|
```csharp
|
|
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:**
|
|
```csharp
|
|
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:**
|
|
```csharp
|
|
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:
|
|
1. Interface erweitern (IPdfProcessor)
|
|
2. Service implementieren (DevExpressPdfProcessor) + Test
|
|
3. Command/Query + Handler + Validator
|
|
4. Endpoint erstellen
|
|
5. 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:**
|
|
1. **Red:** Test schreiben (schlägt fehl, weil Code noch nicht existiert)
|
|
2. **Green:** Code schreiben (Test wird grün)
|
|
3. **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:**
|
|
```csharp
|
|
// 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.cs`
|
|
- `DomainValidationException.cs`
|
|
- `NotFoundException.cs`
|
|
- `PdfProcessingException.cs`
|
|
- ? Step 2.2 - Enums (DocumentOperationType, ProcessingStatus)
|
|
- ? Step 2.3 - Value Objects (Base64String, TenantId, PdfMetadata)
|
|
|
|
- **Phase 3:** Infrastructure Layer (Outside-In!)
|
|
- ? Step 3.1 - IPdfProcessor Interface erstellt
|
|
- ?? Step 3.2 - DevExpressPdfProcessor implementieren (TDD - **IN PROGRESS**)
|
|
- ? Step 3.2.1 - ProcessDocument Ordner gelöscht (Application Layer cleanup)
|
|
- ? Step 3.2.2 - Test-Ordnerstruktur erstellt (Unit/Infrastructure/Services/PdfProcessing)
|
|
- ? Step 3.2.3 - Test-PDF Datei hinzugefügt (valid.pdf als Embedded Resource)
|
|
- ? Step 3.2.4 - DevExpressPdfProcessorTests.cs erstellt (TDD Red Phase - 6 Tests)
|
|
|
|
### ?? In Progress
|
|
- **Phase 3, Step 3.2:** DevExpressPdfProcessor (TDD)
|
|
- **NEXT:** Step 3.2.5 - DevExpressPdfProcessor.cs implementieren (TDD Green Phase)
|
|
- **Progress:** 4/7 Mini-Steps abgeschlossen
|
|
|
|
### ? 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
|
|
|
|
1. **Infrastructure Services:**
|
|
- Ordner existieren (PdfProcessing, FileStorage, DocumentValidation)
|
|
- **Aber:** Alle leer
|
|
- ?? **Action:** DevExpressPdfProcessor.cs implementieren (Step 3.2 - IN PROGRESS)
|
|
|
|
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<T> 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)](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
|
|
- [MediatR Documentation](https://github.com/jbogard/MediatR)
|
|
- [FluentValidation Docs](https://docs.fluentvalidation.net/)
|
|
- [DevExpress PDF API](https://docs.devexpress.com/OfficeFileAPI/114877/pdf-document-api)
|
|
- [ASP.NET Core Minimal APIs](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis)
|
|
- [RFC 7807 Problem Details](https://datatracker.ietf.org/doc/html/rfc7807)
|
|
- [xUnit Documentation](https://xunit.net/)
|
|
- [FluentAssertions Documentation](https://fluentassertions.com/)
|
|
|
|
### 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 |
|
|
| 17.01.2025 | Phase 3 | ?? **Step 3.2 gestartet** - DevExpressPdfProcessor (TDD) |
|
|
| 17.01.2025 | Application | ? Step 3.2.1 - ProcessDocument Ordner gelöscht (Cleanup) |
|
|
| 17.01.2025 | Tests | ? Step 3.2.2 - Test-Ordnerstruktur erstellt, UnitTest1.cs gelöscht |
|
|
| 17.01.2025 | Tests | ? Step 3.2.3 - Test-PDF (valid.pdf) als Embedded Resource hinzugefügt |
|
|
| 17.01.2025 | Tests | ? Step 3.2.4 - DevExpressPdfProcessorTests.cs erstellt (TDD Red - 6 Tests) |
|
|
|
|
---
|
|
|
|
**END OF ROADMAP**
|
|
|
|
*This document is a living document and will be updated as development progresses.*
|