From 3a87ace144484f1b37f38383360db8afb655fc09 Mon Sep 17 00:00:00 2001 From: OlgunR Date: Thu, 18 Jun 2026 14:32:06 +0200 Subject: [PATCH] Complete Phase 2: Domain Layer implementation Updated ROADMAP.md to mark Phase 2 as completed and added detailed descriptions of completed tasks. Introduced three new value objects (`Base64String`, `TenantId`, and `PdfMetadata`) in the `DocumentOperator.Domain.Models.ValueObjects` namespace. These classes ensure type safety, immutability, and encapsulated validation. - `Base64String`: Handles Base64 string creation, validation, and conversion. - `TenantId`: Represents a tenant identifier with validation and normalization. - `PdfMetadata`: Represents PDF metadata with computed properties. Updated `DocumentOperator.Domain.csproj` to reflect the addition of these value objects. The project is now ready to begin Phase 3 (Infrastructure Layer). --- DocumentOperator.API/ROADMAP.md | 43 +++++++++++--- .../DocumentOperator.Domain.csproj | 1 - .../Models/ValueObjects/Base64String.cs | 59 +++++++++++++++++++ .../Models/ValueObjects/PdfMetadata.cs | 32 ++++++++++ .../Models/ValueObjects/TenantId.cs | 41 +++++++++++++ 5 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 DocumentOperator.Domain/Models/ValueObjects/Base64String.cs create mode 100644 DocumentOperator.Domain/Models/ValueObjects/PdfMetadata.cs create mode 100644 DocumentOperator.Domain/Models/ValueObjects/TenantId.cs diff --git a/DocumentOperator.API/ROADMAP.md b/DocumentOperator.API/ROADMAP.md index cbdcdb6..5af24d3 100644 --- a/DocumentOperator.API/ROADMAP.md +++ b/DocumentOperator.API/ROADMAP.md @@ -726,10 +726,12 @@ DocumentOperator.Tests/ --- -### ?? PHASE 2: Domain Layer (Minimal) - **IN PROGRESS** +### ? PHASE 2: Domain Layer (Minimal) - **COMPLETED** **Ziel:** Nur was wirklich gebraucht wird! +**Status:** ? **Alle Steps abgeschlossen!** + --- #### ? Step 2.1: Domain Exceptions erstellen - **COMPLETED** @@ -802,7 +804,7 @@ DocumentOperator.Tests/ --- -#### ?? Step 2.3: Value Objects erstellen - **NEXT** +#### ? Step 2.3: Value Objects erstellen - **COMPLETED** **Aufgabe:** Typsichere, selbst-validierende Wert-Objekte @@ -812,38 +814,44 @@ DocumentOperator.Tests/ - ? Immutable (keine Änderungen nach Erstellung) - ? Wiederverwendbar (in Domain, Application, Infrastructure) -**Was du erstellen wirst:** +**Was du erstellt hast:** -1. **Base64String.cs** +1. **Base64String.cs** ? - Factory Method: `Create(string value)` - Validierung: Gültiges Base64-Format - Konvertierung: `ToByteArray()`, `FromByteArray(byte[])` - Wirft `DomainValidationException` bei Fehler -2. **TenantId.cs** +2. **TenantId.cs** ? - Factory Method: `Create(string value)` - Validierung: Nicht leer, Max 100 Zeichen - Normalisierung: `.ToLowerInvariant()` - Wirft `DomainValidationException` bei Fehler -3. **PdfMetadata.cs** +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!** +**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!) +### ? PHASE 3: Infrastructure Layer (Outside-In!) - **NEXT** **Ziel:** DevExpress Services implementieren (wir sehen **echten** Code!) --- -#### ? Step 3.1: IPdfProcessor Interface erstellen +#### ?? Step 3.1: IPdfProcessor Interface erstellen - **NEXT** **Aufgabe:** Abstraction für PDF-Operationen @@ -1271,6 +1279,23 @@ public async Task POST_ValidatePdf_InvalidPdf_Returns400() { } ## ?? CURRENT STATUS ### ? Completed +- **Phase 1:** Foundation & Clean Architecture Setup ? + - Dependencies ? + - Packages ? + - Folder Structure ? + - Configuration ? + - Serilog ? + +- **Phase 2:** Domain Layer (Minimal) ? + - ? Step 2.1 - Domain Exceptions (4 Exceptions erstellt) + - ? Step 2.2 - Enums (DocumentOperationType, ProcessingStatus) + - ? Step 2.3 - Value Objects (Base64String, TenantId, PdfMetadata) + +### ?? In Progress +- **Phase 3:** Infrastructure Layer (Outside-In!) + - **NEXT:** Step 3.1 - IPdfProcessor Interface erstellen + +### ? Pending - **Phase 1:** Foundation & Clean Architecture Setup - Dependencies ? - Packages ? diff --git a/DocumentOperator.Domain/DocumentOperator.Domain.csproj b/DocumentOperator.Domain/DocumentOperator.Domain.csproj index acaae15..df81c18 100644 --- a/DocumentOperator.Domain/DocumentOperator.Domain.csproj +++ b/DocumentOperator.Domain/DocumentOperator.Domain.csproj @@ -9,7 +9,6 @@ - diff --git a/DocumentOperator.Domain/Models/ValueObjects/Base64String.cs b/DocumentOperator.Domain/Models/ValueObjects/Base64String.cs new file mode 100644 index 0000000..b9b9f1d --- /dev/null +++ b/DocumentOperator.Domain/Models/ValueObjects/Base64String.cs @@ -0,0 +1,59 @@ +namespace DocumentOperator.Domain.Models.ValueObjects; + +public sealed class Base64String +{ + public string Value { get; } + + private Base64String(string value) + { + Value = value; + } + + public static Base64String Create(string value) + { + if (string.IsNullOrWhiteSpace(value)) + throw new Common.Exceptions.DomainValidationException("Base64 string cannot be empty."); + + // Validierung: Ist es gültiges Base64? + try + { + Convert.FromBase64String(value); + } + catch (FormatException) + { + throw new Common.Exceptions.DomainValidationException("Invalid Base64 format."); + } + + return new Base64String(value); + } + + public static Base64String FromByteArray(byte[] bytes) + { + if (bytes == null || bytes.Length == 0) + throw new Common.Exceptions.DomainValidationException("Byte array cannot be null or empty."); + + var base64 = Convert.ToBase64String(bytes); + return new Base64String(base64); + } + + public byte[] ToByteArray() + { + return Convert.FromBase64String(Value); + } + + public override string ToString() => Value; + + // Equality (wichtig für Value Objects!) + public override bool Equals(object? obj) + { + if (obj is not Base64String other) + return false; + + return Value == other.Value; + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } +} \ No newline at end of file diff --git a/DocumentOperator.Domain/Models/ValueObjects/PdfMetadata.cs b/DocumentOperator.Domain/Models/ValueObjects/PdfMetadata.cs new file mode 100644 index 0000000..be530a8 --- /dev/null +++ b/DocumentOperator.Domain/Models/ValueObjects/PdfMetadata.cs @@ -0,0 +1,32 @@ +namespace DocumentOperator.Domain.Models.ValueObjects; + +public sealed class PdfMetadata +{ + public int PageCount { get; } + public long FileSizeBytes { get; } + public string PdfVersion { get; } + public bool HasAttachments { get; } + public int AttachmentCount { get; } + + // Computed Property (berechnet aus FileSizeBytes) + public double FileSizeMB => FileSizeBytes / 1024.0 / 1024.0; + + public PdfMetadata( + int pageCount, + long fileSizeBytes, + string pdfVersion, + bool hasAttachments, + int attachmentCount) + { + PageCount = pageCount; + FileSizeBytes = fileSizeBytes; + PdfVersion = pdfVersion; + HasAttachments = hasAttachments; + AttachmentCount = attachmentCount; + } + + public override string ToString() + { + return $"PDF: {PageCount} pages, {FileSizeMB:F2} MB, Version {PdfVersion}, Attachments: {AttachmentCount}"; + } +} \ No newline at end of file diff --git a/DocumentOperator.Domain/Models/ValueObjects/TenantId.cs b/DocumentOperator.Domain/Models/ValueObjects/TenantId.cs new file mode 100644 index 0000000..42ff202 --- /dev/null +++ b/DocumentOperator.Domain/Models/ValueObjects/TenantId.cs @@ -0,0 +1,41 @@ +namespace DocumentOperator.Domain.Models.ValueObjects; + +public sealed class TenantId +{ + public string Value { get; } + + private TenantId(string value) + { + Value = value; + } + + public static TenantId Create(string value) + { + if (string.IsNullOrWhiteSpace(value)) + throw new Common.Exceptions.DomainValidationException("TenantId cannot be empty."); + + if (value.Length > 100) + throw new Common.Exceptions.DomainValidationException("TenantId cannot exceed 100 characters."); + + // Normalisierung: Lowercase + var normalized = value.Trim().ToLowerInvariant(); + + return new TenantId(normalized); + } + + public override string ToString() => Value; + + // Equality + public override bool Equals(object? obj) + { + if (obj is not TenantId other) + return false; + + return Value == other.Value; + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } +} \ No newline at end of file