The roadmap document was restructured to reflect a pragmatic, iterative, and test-driven development (TDD) approach. Key updates include: - Title updated to "Pragmatic Edition" with revised last updated date. - Table of contents reorganized with new sections (e.g., "Development Philosophy," "Testing Strategy"). - Expanded "Core Features" and "Business Workflow" sections with additional details and updated flow diagrams. - Revised "Architecture & Design Decisions" to emphasize minimal domain layers, dependency rules, and vertical slice architecture. - Updated "CQRS with MediatR" and "Minimal APIs" sections with examples and best practices. - Added "Development Philosophy," "Testing Strategy," and "Key Learnings & Decisions" sections. - Updated "Technology Stack" to include testing libraries and remove unused dependencies. - Reflected new folder structure, including a `Tests` project for unit and integration tests. - Rewrote "Development Roadmap" with detailed steps for each phase, focusing on TDD and outside-in development. - Updated "References & Best Practices" and "Update Log" to align with the new approach. These changes aim to improve clarity, maintainability, and alignment with modern .NET practices.
1411 lines
37 KiB
Markdown
1411 lines
37 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.) |
|
|
| **Microsoft.Extensions.Options.ConfigurationExtensions** | 8.0.0 | Options Pattern |
|
|
|
|
#### 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) - **IN PROGRESS**
|
|
|
|
**Ziel:** Nur was wirklich gebraucht wird!
|
|
|
|
---
|
|
|
|
#### ? 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 - **NEXT**
|
|
|
|
**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)
|
|
|
|
---
|
|
|
|
#### ? Step 2.3: Value Objects erstellen
|
|
|
|
**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 erstellen wirst:**
|
|
|
|
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/`
|
|
|
|
**Detaillierte Anleitung kommt in Step 2.3!**
|
|
|
|
---
|
|
|
|
### ? PHASE 3: Infrastructure Layer (Outside-In!)
|
|
|
|
**Ziel:** DevExpress Services implementieren (wir sehen **echten** Code!)
|
|
|
|
---
|
|
|
|
#### ? Step 3.1: IPdfProcessor Interface erstellen
|
|
|
|
**Aufgabe:** Abstraction für PDF-Operationen
|
|
|
|
**Was du erstellen wirst:**
|
|
- **Wo:** `Application/Common/Interfaces/IPdfProcessor.cs`
|
|
- **Inhalt:**
|
|
```csharp
|
|
public interface IPdfProcessor
|
|
{
|
|
Task<PdfMetadata> ValidateAsync(byte[] pdfBytes);
|
|
// Weitere Methoden später (YAGNI!)
|
|
}
|
|
```
|
|
|
|
**Warum Interface ERST?**
|
|
- Application kennt nur Interface (Dependency Inversion)
|
|
- Infrastructure implementiert
|
|
- TDD: Test ? Interface ? Implementation
|
|
|
|
---
|
|
|
|
#### ? Step 3.2: DevExpressPdfProcessor implementieren (mit TDD!)
|
|
|
|
**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
|
|
- Dependencies ?
|
|
- Packages ?
|
|
- Folder Structure ?
|
|
- Configuration ?
|
|
- Serilog ?
|
|
|
|
### ?? In Progress
|
|
- **Phase 2:** Domain Layer (Minimal)
|
|
- ? Step 2.1 - Domain Exceptions
|
|
- **NEXT:** Step 2.2 - Enums
|
|
|
|
### ? Pending
|
|
- Phase 3-9
|
|
|
|
---
|
|
|
|
## ?? 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 | ?? Starting Step 2.2 - Enums (vor Value Objects) |
|
|
|
|
---
|
|
|
|
**END OF ROADMAP**
|
|
|
|
*This document is a living document and will be updated as development progresses.*
|