Refactored `DevExpressPdfProcessor` to improve attachment detection: - Changed `DetectEmbeddedFiles` return type to a tuple for better handling of attachment presence and count. - Enhanced logic to parse `/Names` arrays and count object references for accurate attachment detection. - Implemented robust search for `/EmbeddedFiles` to handle multiple occurrences and ensure proper context validation. Updated PHASENPLAN.md and ROADMAP.md to reflect these changes, including the addition of fixes for attachment detection and counting logic. Added new tests in `DevExpressPdfProcessorTests`: - Verified detection of multiple attachments and accurate counts. - Ensured no crashes when processing PDFs with `/EmbeddedFiles`. Included a new test resource (`pdfWithMoreThanOneAttachment.pdf`) for validating multiple attachment scenarios.
1005 lines
29 KiB
Markdown
1005 lines
29 KiB
Markdown
# ?? DocumentOperator - Project Roadmap (Feature-Driven Development)
|
|
|
|
> **Last Updated:** 17.01.2025 | **Status:** In Development | **Current Feature:** Feature 1 - ValidatePDF ? ABGESCHLOSSEN!
|
|
|
|
---
|
|
|
|
## ?? NEW APPROACH: Feature-by-Feature Development
|
|
|
|
**Was hat sich geändert?**
|
|
|
|
? **Feature-Driven Development** statt Layer-by-Layer
|
|
- Jedes Feature wird KOMPLETT umgesetzt (Domain ? Infrastructure ? Application ? API ? Tests ? Swagger)
|
|
- Feature ist erst "DONE" wenn es im Swagger testbar ist
|
|
- Dann nächstes Feature
|
|
|
|
? **Kleine Schritte** (1 Layer pro Step)
|
|
- Besser überschaubar
|
|
- Weniger Merge-Konflikte
|
|
- Schnelleres Feedback
|
|
|
|
? **Multi-Tenancy & Cross-Cutting Concerns später**
|
|
- Erst alle synchronen Features implementieren
|
|
- Dann Multi-Tenancy für ALLE Endpoints (kein Wiederholungsaufwand!)
|
|
- Dann Health Checks, Polly, Logging (einmal für alle!)
|
|
|
|
---
|
|
|
|
## ?? TABLE OF CONTENTS
|
|
|
|
1. [Feature Roadmap](#feature-roadmap)
|
|
2. [Project Overview](#project-overview)
|
|
3. [Architecture & Design Decisions](#architecture--design-decisions)
|
|
4. [Technology Stack](#technology-stack)
|
|
5. [Project Structure](#project-structure)
|
|
6. [Testing Strategy](#testing-strategy)
|
|
7. [Current Status](#current-status)
|
|
8. [Key Learnings & Decisions](#key-learnings--decisions)
|
|
|
|
---
|
|
|
|
## ?? FEATURE ROADMAP
|
|
|
|
### ?? Feature Overview
|
|
|
|
| Feature | Type | Status | Swagger Testbar? |
|
|
|---------|------|--------|------------------|
|
|
| **1. ValidatePDF** | Synchron | ? Abgeschlossen | ? |
|
|
| **2. ExtractAttachments** | Synchron | ? Pending | ? |
|
|
| **3. ApplyStamp** | Synchron | ? Pending | ? |
|
|
| **4. EmbedCertificate** | Synchron | ? Pending | ? |
|
|
| **5. ConcatenatePDFs** | Asynchron | ? Pending | ? |
|
|
|
|
### ?? Cross-Cutting Concerns (nach Features 1-4)
|
|
|
|
| Concern | Status |
|
|
|---------|--------|
|
|
| **Multi-Tenancy** (X-API-Key Header) | ? Pending |
|
|
| **Health Checks** (/health Endpoint) | ? Pending |
|
|
| **Polly Resilience** (Retry, Circuit Breaker) | ? Pending |
|
|
| **Logging & Monitoring** (Correlation IDs, Seq) | ? Pending |
|
|
|
|
---
|
|
|
|
## ?? FEATURE 1: ValidatePDF (Synchron) - **In Progress**
|
|
|
|
**Was macht dieses Feature?**
|
|
- Client sendet PDF als Base64 (JSON)
|
|
- API validiert PDF
|
|
- API gibt Metadaten zurück (Seitenzahl, Dateigröße, PDF-Version, Anhänge)
|
|
|
|
**Endpoint:**
|
|
```
|
|
POST /api/v1/documents/validate
|
|
Request: { "base64Pdf": "JVBERi0xLjQK..." }
|
|
Response: { "pageCount": 5, "fileSizeBytes": 1024, "pdfVersion": "1.4", "hasAttachments": false }
|
|
```
|
|
|
|
---
|
|
|
|
### ? Step 1.0: Foundation (ABGESCHLOSSEN)
|
|
|
|
**Was wurde bereits erstellt:**
|
|
- ? Domain Layer (Exceptions, Enums, Value Objects)
|
|
- ? Infrastructure Layer (`DevExpressPdfProcessor.ValidateAsync` - FERTIG!)
|
|
- ? Tests für Infrastructure (`DevExpressPdfProcessorTests.cs` - 6 Tests)
|
|
|
|
**Was wir wiederverwenden:**
|
|
- `Base64String` Value Object (Domain)
|
|
- `PdfMetadata` Value Object (Domain)
|
|
- `IPdfProcessor` Interface (Application)
|
|
- `DevExpressPdfProcessor.ValidateAsync` (Infrastructure)
|
|
|
|
---
|
|
|
|
### ? Step 1.1: Application Layer (MediatR Setup + ValidatePDF Feature) - **ABGESCHLOSSEN**
|
|
|
|
**Ziel:** MediatR + FluentValidation + ValidatePDF Handler
|
|
|
|
**Was wurde erstellt:**
|
|
|
|
#### 1.1.1: MediatR Setup
|
|
- ? **Datei:** `Application/DependencyInjection.cs`
|
|
- Registriert MediatR
|
|
- Registriert FluentValidation
|
|
- Registriert Pipeline Behaviors (Validation + Logging)
|
|
|
|
#### 1.1.2: Pipeline Behaviors
|
|
- ? **Datei:** `Application/Common/Behaviors/ValidationBehavior.cs`
|
|
- Führt FluentValidation automatisch aus (vor jedem Handler)
|
|
- Wirft `ValidationException` bei Fehler
|
|
|
|
- ? **Datei:** `Application/Common/Behaviors/LoggingBehavior.cs`
|
|
- Loggt jeden Request (mit Performance-Tracking)
|
|
- Nutzt `ILogger<T>` (Clean Architecture konform!)
|
|
|
|
#### 1.1.3: ValidatePDF Feature (Vertical Slice)
|
|
- ? **Ordner:** `Application/Features/Documents/ValidatePdf/`
|
|
|
|
- ? **Datei:** `ValidatePdfQuery.cs`
|
|
```csharp
|
|
public record ValidatePdfQuery(Base64String PdfContent) : IRequest<PdfMetadata>;
|
|
```
|
|
|
|
- ? **Datei:** `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);
|
|
}
|
|
}
|
|
```
|
|
|
|
- ? **Datei:** `ValidatePdfValidator.cs`
|
|
```csharp
|
|
public class ValidatePdfValidator : AbstractValidator<ValidatePdfQuery>
|
|
{
|
|
public ValidatePdfValidator()
|
|
{
|
|
RuleFor(x => x.PdfContent).NotNull().WithMessage("PDF content is required");
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 1.1.4: DTOs
|
|
- ? **Ordner:** `Application/Common/DTOs/`
|
|
|
|
- ? **Datei:** `ValidatePdfRequest.cs`
|
|
```csharp
|
|
public record ValidatePdfRequest(string Base64Pdf);
|
|
```
|
|
|
|
- ? **Datei:** `ValidatePdfResponse.cs`
|
|
```csharp
|
|
public record ValidatePdfResponse(
|
|
int PageCount,
|
|
long FileSizeBytes,
|
|
double FileSizeMB,
|
|
string PdfVersion,
|
|
bool HasAttachments,
|
|
int AttachmentCount);
|
|
```
|
|
|
|
#### 1.1.5: Tests
|
|
- ? **Datei:** `Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs`
|
|
- ? Test: `Handle_ValidPdf_ReturnsPdfMetadata`
|
|
- ? Test: `Handle_PdfProcessorThrowsException_PropagatesException`
|
|
|
|
**Akzeptanzkriterien:**
|
|
- ? Build erfolgreich
|
|
- ? Tests grün (alle 2 Tests)
|
|
- ? MediatR Pipeline funktioniert (Validation + Logging)
|
|
|
|
---
|
|
|
|
### ? Step 1.2: API Layer (Endpoint + Exception Middleware) - **ABGESCHLOSSEN**
|
|
|
|
**Ziel:** HTTP Endpoint + zentrale Exception Handling
|
|
|
|
**Was wurde erstellt:**
|
|
|
|
#### 1.2.1: Exception Handling Middleware
|
|
- ? **Datei:** `API/Middleware/ExceptionHandlingMiddleware.cs`
|
|
- Fängt alle Exceptions
|
|
- Mappt zu HTTP Status Codes (ValidationException ? 400, DomainValidationException ? 400, NotFoundException ? 404, PdfProcessingException ? 500)
|
|
- Gibt RFC 7807 Problem Details zurück
|
|
|
|
#### 1.2.2: Minimal API Endpoint
|
|
- ? **Datei:** `API/Endpoints/v1/DocumentEndpoints.cs`
|
|
- POST /api/v1/documents/validate
|
|
- Nutzt MediatR (ValidatePdfQuery ? ValidatePdfHandler)
|
|
- Returns ValidatePdfResponse (200) oder ProblemDetails (400, 500)
|
|
|
|
#### 1.2.3: Infrastructure DependencyInjection
|
|
- ? **Datei:** `Infrastructure/DependencyInjection.cs`
|
|
- Registriert IPdfProcessor ? DevExpressPdfProcessor
|
|
|
|
#### 1.2.4: Program.cs Updates
|
|
- ? Application Layer registriert (AddApplication)
|
|
- ? Infrastructure Layer registriert (AddInfrastructure)
|
|
- ? Exception Middleware registriert (FIRST in pipeline!)
|
|
- ? Endpoints registriert (MapDocumentEndpoints)
|
|
|
|
#### 1.2.5: Integration Tests
|
|
- ? **Datei:** `Tests/Integration/API/DocumentEndpointsTests.cs`
|
|
- ? Test: `POST_ValidatePdf_ValidPdf_Returns200`
|
|
- ? Test: `POST_ValidatePdf_InvalidBase64_Returns400`
|
|
- ? Test: `POST_ValidatePdf_EmptyPdf_Returns400`
|
|
|
|
**Akzeptanzkriterien:**
|
|
- ? Build erfolgreich
|
|
- ? Integration Tests grün (alle 3 Tests)
|
|
- ? Exception Middleware funktioniert
|
|
- ? Endpoint gibt korrekte HTTP Status Codes zurück
|
|
|
|
---
|
|
|
|
### ? Step 1.3: Swagger Dokumentation - **ABGESCHLOSSEN**
|
|
|
|
**Ziel:** API-Dokumentation + Swagger UI testbar
|
|
|
|
**Was wurde erstellt:**
|
|
|
|
#### 1.3.1: Swagger Configuration
|
|
- ? **Datei:** `API/Configuration/SwaggerConfiguration.cs`
|
|
- `AddSwaggerDocumentation()` Extension Method
|
|
- Swagger mit XML Comments konfiguriert
|
|
- API-Titel, Version, Beschreibung gesetzt
|
|
|
|
#### 1.3.2: XML Comments aktiviert
|
|
- ? **Datei:** `API/DocumentOperator.API.csproj`
|
|
```xml
|
|
<PropertyGroup>
|
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
</PropertyGroup>
|
|
```
|
|
|
|
#### 1.3.3: Endpoint dokumentiert
|
|
- ? **Datei:** `API/Endpoints/v1/DocumentEndpoints.cs`
|
|
- XML Comments für `ValidatePdf` Methode
|
|
- Swagger-Annotationen (`.WithSummary()`, `.WithDescription()`, `.Produces<>()`)
|
|
|
|
#### 1.3.4: DTOs dokumentiert
|
|
- ? **Datei:** `Application/Common/DTOs/ValidatePdfRequest.cs`
|
|
- XML Comments für Request-Schema
|
|
|
|
- ? **Datei:** `Application/Common/DTOs/ValidatePdfResponse.cs`
|
|
- XML Comments für Response-Schema
|
|
- `FileSizeMB` Property hinzugefügt
|
|
|
|
#### 1.3.5: Program.cs aktualisiert
|
|
- ? **Datei:** `API/Program.cs`
|
|
- `builder.Services.AddSwaggerDocumentation()` statt `AddSwaggerGen()`
|
|
- `using DocumentOperator.API.Configuration;` hinzugefügt
|
|
|
|
**Akzeptanzkriterien:**
|
|
- ? Build erfolgreich
|
|
- ? Alle Tests grün (11/11 Tests)
|
|
- ? XML-Dokumentation wird generiert (`DocumentOperator.API.xml`)
|
|
- ? Swagger UI zeigt Endpoint `/api/v1/documents/validate` mit Dokumentation
|
|
- ? Request/Response-Schemas sind dokumentiert
|
|
- ? Endpoint ist im Swagger UI testbar
|
|
|
|
---
|
|
|
|
### ? Feature 1 ABGESCHLOSSEN!
|
|
|
|
**Was haben wir erreicht?**
|
|
- ? ValidatePDF Feature komplett implementiert (Domain ? Infrastructure ? Application ? API ? Tests ? Swagger)
|
|
- ? Endpoint ist im Swagger UI testbar
|
|
- ? Unit Tests + Integration Tests grün (11/11)
|
|
- ? Clean Architecture eingehalten
|
|
- ? TDD angewendet
|
|
- ? Swagger-Dokumentation vollständig
|
|
|
|
**Nächstes Feature:**
|
|
? **Feature 2: ExtractAttachments**
|
|
|
|
---
|
|
|
|
## ?? FEATURE 2: ExtractAttachments (Synchron) - **NEXT**
|
|
|
|
**Was macht dieses Feature?**
|
|
- Client sendet PDF als Base64 (JSON)
|
|
- API extrahiert eingebettete Anhänge
|
|
- API gibt Anhänge als Base64 zurück (oder Download-Links)
|
|
|
|
**Endpoint:**
|
|
```
|
|
POST /api/v1/documents/extract-attachments
|
|
Request: { "base64Pdf": "JVBERi0xLjQK..." }
|
|
Response: { "attachments": [{ "name": "invoice.xml", "base64Content": "..." }] }
|
|
```
|
|
|
|
**Steps:**
|
|
- ?? Step 2.1: Infrastructure Layer (DevExpressPdfProcessor.ExtractAttachmentsAsync)
|
|
- ?? Step 2.2: Application Layer (ExtractAttachmentsCommand + Handler + Validator)
|
|
- ?? Step 2.3: API Layer (Endpoint)
|
|
- ?? Step 2.4: Swagger Dokumentation
|
|
|
|
---
|
|
|
|
## ?? FEATURE 3: ApplyStamp (Synchron) - **PENDING**
|
|
|
|
**Was macht dieses Feature?**
|
|
- Client sendet PDF + Stamp-Konfiguration (Text, Position)
|
|
- API fügt Stamp hinzu (Logo, Text, Wasserzeichen)
|
|
- API gibt gestempeltes PDF zurück
|
|
|
|
**Endpoint:**
|
|
```
|
|
POST /api/v1/documents/apply-stamp
|
|
Request: { "base64Pdf": "...", "text": "CONFIDENTIAL", "position": "TopRight" }
|
|
Response: { "base64Pdf": "JVBERi0xLjQK..." }
|
|
```
|
|
|
|
**Steps:**
|
|
- ?? Step 3.1: Infrastructure Layer (DevExpressPdfProcessor.ApplyStampAsync)
|
|
- ?? Step 3.2: Application Layer (ApplyStampCommand + Handler + Validator)
|
|
- ?? Step 3.3: API Layer (Endpoint)
|
|
- ?? Step 3.4: Swagger Dokumentation
|
|
|
|
---
|
|
|
|
## ?? FEATURE 4: EmbedCertificate (Synchron) - **PENDING**
|
|
|
|
**Was macht dieses Feature?**
|
|
- Client sendet PDF + Zertifikat (PFX als Base64)
|
|
- API bettet Zertifikat als Attachment ein
|
|
- API gibt PDF mit Zertifikat zurück
|
|
|
|
**Endpoint:**
|
|
```
|
|
POST /api/v1/documents/embed-certificate
|
|
Request: { "base64Pdf": "...", "base64Certificate": "..." }
|
|
Response: { "base64Pdf": "JVBERi0xLjQK..." }
|
|
```
|
|
|
|
**Steps:**
|
|
- ?? Step 4.1: Infrastructure Layer (DevExpressPdfProcessor.EmbedCertificateAsync)
|
|
- ?? Step 4.2: Application Layer (EmbedCertificateCommand + Handler + Validator)
|
|
- ?? Step 4.3: API Layer (Endpoint)
|
|
- ?? Step 4.4: Swagger Dokumentation
|
|
|
|
---
|
|
|
|
## ?? FEATURE 5: ConcatenatePDFs (Asynchron) - **PENDING**
|
|
|
|
**Was macht dieses Feature?**
|
|
- Client sendet mehrere PDFs (Array von Base64)
|
|
- API startet asynchronen Job (gibt JobId zurück)
|
|
- Client pollt Job-Status
|
|
- Wenn fertig: Client lädt Ergebnis herunter
|
|
|
|
**Endpoints:**
|
|
```
|
|
POST /api/v1/documents/concatenate (Async)
|
|
Request: { "pdfFiles": ["JVBERi0x...", "JVBERi0y..."] }
|
|
Response: { "jobId": "abc-123", "status": "Pending" }
|
|
|
|
GET /api/v1/jobs/{jobId}
|
|
Response: { "jobId": "abc-123", "status": "Processing", "progress": 50 }
|
|
|
|
GET /api/v1/jobs/{jobId}/download
|
|
Response: PDF-Datei (Binary)
|
|
```
|
|
|
|
**Steps:**
|
|
- ?? Step 5.1: Infrastructure Layer (In-Memory Queue + Background Worker)
|
|
- ?? Step 5.2: Application Layer (SubmitConcatenateJobCommand + GetJobStatusQuery)
|
|
- ?? Step 5.3: API Layer (Async Endpoints)
|
|
- ?? Step 5.4: Swagger Dokumentation
|
|
|
|
---
|
|
|
|
## ?? CROSS-CUTTING CONCERNS
|
|
|
|
**Nach Features 1-4 abgeschlossen:**
|
|
|
|
### ?? Multi-Tenancy (X-API-Key Header)
|
|
|
|
**Was wird gebaut:**
|
|
- EF Core + SQLite (Tenant-Datenbank)
|
|
- Redis Cache (API-Key Lookups)
|
|
- TenantResolutionMiddleware (X-API-Key ? Tenant)
|
|
- BCrypt API-Key Hashing
|
|
- Admin API (Tenant CRUD)
|
|
|
|
**Steps:**
|
|
- ?? Step MT.1: EF Core Setup (Entities, DbContext, Migrations)
|
|
- ?? Step MT.2: TenantResolutionMiddleware
|
|
- ?? Step MT.3: Redis Cache Integration
|
|
- ?? Step MT.4: Admin API (Tenant Management)
|
|
- ?? Step MT.5: Alle Endpoints mit X-API-Key absichern
|
|
|
|
---
|
|
|
|
### ?? Health Checks
|
|
|
|
**Was wird gebaut:**
|
|
- `/health` Endpoint (Liveness/Readiness Probes)
|
|
- DevExpressPdfHealthCheck (Smoke Test)
|
|
- Database Health Check (SQLite)
|
|
- Redis Health Check
|
|
|
|
**Steps:**
|
|
- ?? Step HC.1: DevExpressPdfHealthCheck
|
|
- ?? Step HC.2: Database Health Check
|
|
- ?? Step HC.3: Redis Health Check (optional)
|
|
|
|
---
|
|
|
|
### ??? Polly Resilience
|
|
|
|
**Was wird gebaut:**
|
|
- Retry Policy (3x mit Exponential Backoff)
|
|
- Circuit Breaker (nach 5 Fehlern 30s öffnen)
|
|
- Timeout Policy (30s max)
|
|
|
|
**Steps:**
|
|
- ?? Step PR.1: Polly Policies in DevExpressPdfProcessor
|
|
- ?? Step PR.2: Logging für Resilience Events
|
|
|
|
---
|
|
|
|
### ?? Logging & Monitoring
|
|
|
|
**Was wird gebaut:**
|
|
- Correlation IDs (X-Correlation-ID Header)
|
|
- Seq Sink (Log-Browsing UI)
|
|
- File Logging (Production)
|
|
- LoggingBehavior (MediatR Pipeline)
|
|
|
|
**Steps:**
|
|
- ?? Step LM.1: CorrelationIdMiddleware
|
|
- ?? Step LM.2: Seq Sink konfigurieren
|
|
- ?? Step LM.3: File Logging konfigurieren
|
|
- ?? Step LM.4: LoggingBehavior erweitern (Performance-Tracking)
|
|
|
|
---
|
|
|
|
## ?? 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 - später!)
|
|
- Wartbar an einer Stelle
|
|
|
|
---
|
|
|
|
## ??? 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)
|
|
|
|
---
|
|
|
|
### 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 (ApplyStamp, EmbedCertificate, 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;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 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
|
|
|
|
**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
|
|
|
|
**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)
|
|
```
|
|
|
|
---
|
|
|
|
## ?? 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.Sinks.Seq** | 8.0.0 | Log-Browsing UI (Development) |
|
|
| **Serilog.Enrichers.CorrelationId** | 3.0.1 | Correlation IDs für Request-Tracking |
|
|
| **Asp.Versioning.Http** | 8.1.1 | API Versioning (/api/v1/, /api/v2/) |
|
|
|
|
#### 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.) |
|
|
| **Polly** | 8.5.0 | Resilience (Retry, Circuit Breaker, Timeout) - SPÄTER! |
|
|
| **Microsoft.EntityFrameworkCore** | 8.0.0 | ORM für Tenant-Datenbank - SPÄTER! |
|
|
| **Microsoft.EntityFrameworkCore.Sqlite** | 8.0.0 | SQLite Provider - SPÄTER! |
|
|
| **BCrypt.Net-Next** | 4.0.3 | API-Key Hashing - SPÄTER! |
|
|
|
|
#### Domain Layer
|
|
|
|
| Package | Version | Purpose |
|
|
|---------|---------|---------|
|
|
| - | - | **Keine Dependencies!** (Clean Architecture) |
|
|
|
|
#### Tests
|
|
|
|
| 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 |
|
|
|
|
---
|
|
|
|
## ?? 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
|
|
??? ROADMAP.md ? This file
|
|
??? PHASENPLAN.md ? Project timeline
|
|
```
|
|
|
|
---
|
|
|
|
### ?? API Layer (DocumentOperator.API)
|
|
|
|
**Purpose:** HTTP Entry Point, Routing, Middleware
|
|
|
|
**Folder Structure:**
|
|
|
|
```
|
|
DocumentOperator.API/
|
|
??? Endpoints/
|
|
? ??? v1/
|
|
? ??? DocumentEndpoints.cs ? Minimal API Endpoints
|
|
??? Middleware/
|
|
? ??? ExceptionHandlingMiddleware.cs ? Zentrale Exception Handling ?
|
|
??? Configuration/
|
|
? ??? SwaggerConfiguration.cs ? Swagger Setup
|
|
??? appsettings.json ? Base Configuration
|
|
??? appsettings.Development.json ? Dev Overrides
|
|
??? Program.cs ? Application Entry Point
|
|
```
|
|
|
|
---
|
|
|
|
### ?? Application Layer (DocumentOperator.Application)
|
|
|
|
**Purpose:** Use Cases, Business Logic Orchestration
|
|
|
|
**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
|
|
? ? ??? LoggingBehavior.cs ? Structured Logging
|
|
? ??? DTOs/ ? Data Transfer Objects
|
|
? ??? ValidatePdfRequest.cs
|
|
? ??? ValidatePdfResponse.cs
|
|
??? DependencyInjection.cs ? Service Registration
|
|
```
|
|
|
|
---
|
|
|
|
### ?? Infrastructure Layer (DocumentOperator.Infrastructure)
|
|
|
|
**Purpose:** Technische Implementierungen
|
|
|
|
**Folder Structure:**
|
|
|
|
```
|
|
DocumentOperator.Infrastructure/
|
|
??? Services/
|
|
? ??? PdfProcessing/
|
|
? ??? DevExpressPdfProcessor.cs ? IPdfProcessor Implementation ?
|
|
??? Configuration/
|
|
? ??? DocumentOperatorSettings.cs ? Options Pattern Class
|
|
??? DependencyInjection.cs ? Service Registration
|
|
```
|
|
|
|
---
|
|
|
|
### ?? Domain Layer (DocumentOperator.Domain) - MINIMAL!
|
|
|
|
**Purpose:** Business Rules (nur was wirklich gebraucht wird!)
|
|
|
|
**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
|
|
```
|
|
|
|
---
|
|
|
|
### ?? Tests Layer (DocumentOperator.Tests)
|
|
|
|
**Purpose:** Unit & Integration Tests
|
|
|
|
**Folder Structure:**
|
|
|
|
```
|
|
DocumentOperator.Tests/
|
|
??? Unit/
|
|
? ??? Application/
|
|
? ? ??? Features/
|
|
? ? ??? ValidatePdf/
|
|
? ? ??? ValidatePdfHandlerTests.cs
|
|
? ??? Infrastructure/
|
|
? ? ??? Services/
|
|
? ? ??? DevExpressPdfProcessorTests.cs ?
|
|
? ??? Domain/
|
|
? ??? ValueObjects/
|
|
? ??? Base64StringTests.cs
|
|
??? Integration/
|
|
??? API/
|
|
??? DocumentEndpointsTests.cs
|
|
```
|
|
|
|
---
|
|
|
|
## ?? 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")
|
|
|
|
---
|
|
|
|
## ?? CURRENT STATUS
|
|
|
|
### ? Completed
|
|
|
|
- **Foundation & Domain Layer:**
|
|
- ? Solution Structure (4 Projekte)
|
|
- ? Dependencies (Clean Architecture Dependency Rule)
|
|
- ? Domain Exceptions (4 Exceptions)
|
|
- ? Enums (DocumentOperationType, ProcessingStatus)
|
|
- ? Value Objects (Base64String, TenantId, PdfMetadata)
|
|
|
|
- **Infrastructure Layer:**
|
|
- ? IPdfProcessor Interface
|
|
- ? DevExpressPdfProcessor.ValidateAsync (mit Tests!)
|
|
- ? DependencyInjection.cs (Infrastructure Services)
|
|
|
|
- **Application Layer:**
|
|
- ? DependencyInjection.cs (MediatR + FluentValidation)
|
|
- ? ValidationBehavior.cs (FluentValidation Pipeline)
|
|
- ? LoggingBehavior.cs (ILogger<T> Pipeline)
|
|
- ? ValidatePDF Feature (Query, Handler, Validator)
|
|
- ? DTOs (ValidatePdfRequest, ValidatePdfResponse)
|
|
- ? Tests (ValidatePdfHandlerTests - 2 Tests grün)
|
|
|
|
- **API Layer:**
|
|
- ? ExceptionHandlingMiddleware.cs (RFC 7807 Problem Details)
|
|
- ? DocumentEndpoints.cs (POST /api/v1/documents/validate)
|
|
- ? Program.cs (Services + Middleware + Endpoints)
|
|
- ? Tests (DocumentEndpointsTests - 3 Tests grün)
|
|
|
|
### ?? In Progress
|
|
|
|
- **Feature 1: ValidatePDF**
|
|
- ? Step 1.3: Swagger Dokumentation - **NEXT**
|
|
|
|
### ? Pending
|
|
|
|
- **Feature 1: ValidatePDF**
|
|
- ? Step 1.3: Swagger Dokumentation
|
|
|
|
- **Feature 2-5:** ExtractAttachments, ApplyStamp, EmbedCertificate, ConcatenatePDFs
|
|
- **Cross-Cutting Concerns:** Multi-Tenancy, Health Checks, Polly, Logging
|
|
|
|
---
|
|
|
|
## ?? KEY LEARNINGS & DECISIONS
|
|
|
|
### 1. Feature-Driven Development statt Layer-by-Layer
|
|
|
|
**Entscheidung:** Jedes Feature komplett fertig (bis Swagger testbar)
|
|
|
|
**Warum:**
|
|
- ? Schnellerer Value (Feature 1 nach ~1 Tag fertig!)
|
|
- ? Klares Ziel (Swagger testbar = DONE)
|
|
- ? Weniger Komplexität (nicht alle Layer parallel)
|
|
- ? Besseres Lernen (Pattern wiederholt sich)
|
|
|
|
**Alternative wäre gewesen:**
|
|
- Domain komplett ? Infrastructure komplett ? Application komplett ? API komplett
|
|
- **Nachteile:** Viel Code ohne sichtbares Ergebnis, spekulativ
|
|
|
|
---
|
|
|
|
### 2. Domain Layer minimal halten
|
|
|
|
**Entscheidung:** Nur Enums + Value Objects + Exceptions
|
|
|
|
**Warum:**
|
|
- Domain = Business-Konzepte (technologie-unabhängig)
|
|
- Service-Anwendung (nicht Domain-lastig)
|
|
- YAGNI (You Ain't Gonna Need It)
|
|
|
|
**Was bedeutet das?**
|
|
- Domain/ValueObjects/TenantId.cs ? Value Object (immutable, validierend)
|
|
- Infrastructure/Data/Entities/Tenant.cs ? EF Core Entity (später!)
|
|
- Domain kennt KEINE EF Core Dependencies!
|
|
|
|
---
|
|
|
|
### 3. Multi-Tenancy NACH allen Features
|
|
|
|
**Entscheidung:** Erst alle synchronen Features, dann Multi-Tenancy
|
|
|
|
**Warum:**
|
|
- ? Multi-Tenancy betrifft ALLE Endpoints
|
|
- ? Einmal für alle Features (nicht 5x wiederholen!)
|
|
- ? Einfacher zu testen (erst ohne Tenancy, dann mit)
|
|
|
|
**Nachteile (akzeptiert):**
|
|
- ? Refactoring später nötig (alle Endpoints müssen X-API-Key Header bekommen)
|
|
- ? Aber: Aufwand überschaubar (Middleware erledigt das meiste!)
|
|
|
|
---
|
|
|
|
### 4. TDD beibehalten
|
|
|
|
**Entscheidung:** Test-First Development
|
|
|
|
**Warum:**
|
|
- ? Besseres Design (testbarer Code)
|
|
- ? Tests als Dokumentation
|
|
- ? Safety Net für Refactoring
|
|
|
|
---
|
|
|
|
### 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)
|
|
|
|
---
|
|
|
|
## ?? UPDATE LOG
|
|
|
|
| Date | Feature/Step | Changes |
|
|
|------|--------------|---------|
|
|
| 2024-XX-XX | Foundation | Project setup, dependencies, folder structure |
|
|
| 2024-XX-XX | Domain Layer | Exceptions, Enums, Value Objects |
|
|
| 17.01.2025 | Infrastructure | DevExpressPdfProcessor.ValidateAsync implementiert |
|
|
| 17.01.2025 | Tests | DevExpressPdfProcessorTests.cs erstellt (6 Tests) |
|
|
| 17.01.2025 | **ROADMAP** | ?? **Komplett umstrukturiert** (Feature-Driven Development!) |
|
|
| 17.01.2025 | **PHASENPLAN** | ?? **Komplett umstrukturiert** (Feature-basiert + Datum korrigiert) |
|
|
| 17.01.2025 | **Feature 1 - Step 1.1** | ? **ABGESCHLOSSEN** - Application Layer (MediatR, Behaviors, ValidatePDF Feature, DTOs, Tests) |
|
|
| 17.01.2025 | **Feature 1 - Step 1.2** | ? **ABGESCHLOSSEN** - API Layer (ExceptionMiddleware, Endpoint, Program.cs, Integration Tests - 3/3 grün) |
|
|
| 17.01.2025 | **Feature 1 - Step 1.3** | ? **ABGESCHLOSSEN** - Swagger Dokumentation (SwaggerConfiguration, XML Comments, Endpoint/DTO-Dokumentation - 11/11 Tests grün) |
|
|
| 17.01.2025 | **Feature 1** | ? **KOMPLETT ABGESCHLOSSEN** - ValidatePDF Feature testbar im Swagger UI! |
|
|
| 17.01.2025 | **Fix: Attachment Detection (Multiple Attachments)** | ? **KORRIGIERT** - ValidatePDF erkennt jetzt auch PDFs mit mehreren Attachments korrekt (globale Suche statt 1000-Zeichen-Limit) - 13/13 Tests grün |
|
|
| 17.01.2025 | **Fix: Attachment Count (6 Attachments)** | ? **KORRIGIERT** - AttachmentCount wird jetzt korrekt gezählt (objectCount statt objectCount/2). PDFs mit 6 Attachments werden korrekt erkannt - 13/13 Tests grün |
|
|
|
|
|
|
---
|
|
|
|
**END OF ROADMAP**
|
|
|
|
*This document is a living document and will be updated after each completed step.*
|