Projekt: TransferX.Infrastructure

Basis Dokumente: Read Me, Clean Architecture, Coding Standards and Best Practices und TransferX Architektur

Rolle in der Architektur: Infrastructure Layer implementiert technische Details und Ports aus dem Application Layer. Referenziert Application und Domain, nicht umgekehrt.

Projektstruktur

  TransferX.Infrastructure
  |   TransferX.Infrastructure.csproj
  |
  +---Logging
  |       ILog.cs
  |       Log.cs
  |
  +---Persistence
  |   |   JsonFileStore.cs
  |   |
  |   +---Converters
  |   |       TransferJsonConverter.cs
  |   |
  |   \---Repositories
  |           JsonProviderRepository.cs
  |           JsonTransferConfigRepository.cs
  |           JsonTransferRepository.cs
  |
  \---Services
      \---Security
              AesCredentialProtector.cs
              CredentialProtectorOptions.cs
              ICredentialProtector.cs

Komponenten

Logging

Klasse / Interface Typ Beschreibung
ILog Interface Schnittstelle für Logging mit In-Memory-Log – kombiniert DI-Logging mit UI-Anzeige
Log Klasse Implementierung von ILog mit ConcurrentQueue<string> und optionalem ILogger

Persistence

Klasse Typ Beschreibung
JsonFileStore<TEntity> Klasse Generischer JSON-basierter Datei-Store – liest und schreibt Objekte als JSON
TransferJsonConverter Klasse Benutzerdefinierter JSON-Konverter für Transfer-Aggregate (privater Konstruktor, private set)
JsonProviderRepository Klasse JSON-basierte Persistenz für ProviderConfiguration-Aggregate (internes Record-Mapping)
JsonTransferConfigRepository Klasse JSON-basierte Persistenz für TransferConfiguration-Aggregate (internes Record-Mapping)
JsonTransferRepository Klasse JSON-basierte Persistenz für Transfer-Aggregate + flüchtiger Fortschritt-Cache

Services / Security

Klasse / Interface Typ Beschreibung
ICredentialProtector Interface Abstraktion für Verschlüsseln/Entschlüsseln von Zugangsdaten (DI-fähig)
CredentialProtectorOptions Klasse Konfigurationsoptionen: AES-Key (32 Bytes) und IV (16 Bytes)
AesCredentialProtector Klasse AES-256-CBC Implementierung von ICredentialProtector – plattformunabhängig

Details

Logging: ILog / Log

ILog kombiniert strukturiertes DI-Logging mit einer internen In-Memory-Sammlung, die für die UI-Anzeige genutzt werden kann.

Log kapselt optional eine ILogger-Instanz und delegiert Log-Aufrufe daran. Alle Nachrichten werden zusätzlich in einer thread-sicheren ConcurrentQueue<string> gesammelt.

// Registrierung (Composition Root / Program.cs)
services.AddSingleton<ILog>(sp =>
    new Log(sp.GetRequiredService<ILogger<Log>>(), maxLogEntries: 1000));
Methode Beschreibung
LogMessage(...) Loggt eine Nachricht mit Level und optionaler Exception
GetLogs() Gibt alle gespeicherten Log-Einträge als IReadOnlyList<string> zurück
ClearLogs() Leert alle gespeicherten Log-Einträge

Besonderheiten:

  • FIFO-Begrenzung: Älteste Einträge werden bei Überschreitung des konfigurierten Limits entfernt
  • Thread-sicher durch ConcurrentQueue<T> (näherungsweise in Multi-Thread-Szenarien)
  • ILogger ist optional – funktioniert auch ohne DI-Logger (z.B. in Tests)

Persistence: JsonFileStore<TEntity>

Leichtgewichtiger, generischer Datei-Store für Phase 1. Wird von JsonTransferRepository genutzt.

// Verwendung
var store = new JsonFileStore<Transfer>(dataDirectory, "transfers.json");
var all = await store.ReadAllAsync(cancellationToken);
Methode Beschreibung
ReadAllAsync(...) Liest alle Einträge aus der JSON-Datei, gibt [] wenn nicht vorhanden
WriteAllAsync(...) Schreibt alle Einträge in die JSON-Datei (vollständiges Überschreiben)

Besonderheiten:

  • JSON-Serialisierung mit camelCase-Benennung und eingerückter Ausgabe (WriteIndented = true)
  • Registriert TransferJsonConverter für Transfer-Aggregate mit privatem Konstruktor
  • Verzeichnis wird automatisch erstellt (Directory.CreateDirectory)
  • Unterstützt CancellationToken

Persistence: TransferJsonConverter

Benutzerdefinierter JsonConverter<Transfer> für die Serialisierung und Deserialisierung des Transfer-Aggregates. Notwendig, da Transfer einen privaten Konstruktor und private set-Properties besitzt.

Methode Beschreibung
Read(...) Deserialisiert JSON manuell via JsonDocument, ruft Transfer.Restore(...) auf
Write(...) Serialisiert alle Properties manuell via Utf8JsonWriter

Besonderheiten:

  • Unterstützt Legacy-Format { "value": "/pfad" } und aktuelles Format "/pfad" für Pfad-Properties
  • Abwärtskompatibel: fehlendes failureReason-Feld wird als null behandelt
  • Abwärtskompatibel: fehlendes ownerProcessId-Feld wird als null behandelt (Legacy-Transfers)
  • ownerProcessId wird als JSON-Zahl (number) serialisiert – null wenn nicht gesetzt
  • Leere Pfade aus alten Daten werden als Root-Pfad "/" behandelt (Migrations-Fallback)

Persistence: JsonProviderRepository

Implementiert den Port IProviderRepository (Application Layer) für ProviderConfiguration-Aggregate. Verwendet kein JsonFileStore – nutzt direktes System.Text.Json-Streaming mit einem internen ProviderRecord-Datenmodell.

// Registrierung (Composition Root / Program.cs)
services.AddSingleton(new JsonProviderRepository(dataDirectory));
services.AddSingleton<IProviderRepository>(
    sp => sp.GetRequiredService<JsonProviderRepository>());
Methode Beschreibung
GetAllAsync(...) Gibt alle gültigen Provider-Konfigurationen zurück (korrupte überspringen)
GetByIdAsync(...) Gibt eine Konfiguration anhand der ID zurück, null wenn nicht gefunden
AddAsync(...) Fügt eine neue Konfiguration hinzu und persistiert
UpdateAsync(...) Aktualisiert eine bestehende Konfiguration und persistiert
DeleteAsync(...) Löscht eine Konfiguration anhand der ID, gibt bool zurück

Besonderheiten:

  • Internes ProviderRecord-Modell für JSON-Serialisierung (flaches POCO, kein Domain-Objekt)
  • Validierung beim Laden: Datensätze mit leerem Name, BasePath oder ProviderType werden übersprungen
  • Streaming-Serialisierung via File.OpenRead / File.Create

Persistence: JsonTransferConfigRepository

Implementiert den Port ITransferConfigRepository (Application Layer) für TransferConfiguration-Aggregate. Verwendet kein JsonFileStore – nutzt direktes System.Text.Json-Streaming mit einem internen TransferConfigRecord-Datenmodell.

// Registrierung (Composition Root / Program.cs)
services.AddSingleton(new JsonTransferConfigRepository(dataDirectory));
services.AddSingleton<ITransferConfigRepository>(
    sp => sp.GetRequiredService<JsonTransferConfigRepository>());
Methode Beschreibung
GetAllAsync(...) Gibt alle gültigen Transfer-Konfigurationen zurück (korrupte überspringen)
GetByIdAsync(...) Gibt eine Konfiguration anhand der ID zurück, null wenn nicht gefunden
AddAsync(...) Fügt eine neue Transfer-Konfiguration hinzu und persistiert (transfer-configs.json)
UpdateAsync(...) Aktualisiert eine bestehende Konfiguration und persistiert
DeleteAsync(...) Löscht eine Konfiguration anhand der ID, gibt bool zurück

Besonderheiten:

  • Internes TransferConfigRecord-Modell für JSON-Serialisierung (flaches POCO)
  • TransferOperation wird als string gespeichert (Enum.Parse beim Laden)
  • Validierung beim Laden: Datensätze mit leerem Name, SourcePath oder TargetPath werden übersprungen
  • Persistenz-Datei: transfer-configs.json

Persistence: JsonTransferRepository

Implementiert den Port ITransferRepository (Application Layer) für Transfer-Aggregate. Fortschritts-Daten werden flüchtig im In-Memory-Cache gehalten (nicht persistiert).

// Registrierung (Composition Root / Program.cs)
services.AddSingleton(new JsonTransferRepository(dataDirectory));
services.AddSingleton<ITransferRepository>(
    sp => sp.GetRequiredService<JsonTransferRepository>());
Methode Beschreibung
GetAllAsync(...) Gibt alle Transfers zurück
GetByIdAsync(...) Gibt einen Transfer anhand der ID zurück, null wenn nicht gefunden
AddAsync(...) Fügt einen neuen Transfer hinzu und persistiert
UpdateAsync(...) Aktualisiert einen bestehenden Transfer und persistiert
GetProgress(transferId) Gibt aktuellen TransferProgress aus dem In-Memory-Cache zurück
UpdateProgress(id, progress) Aktualisiert den Fortschritt im In-Memory-Cache
RemoveProgress(transferId) Entfernt den Fortschritts-Eintrag eines abgeschlossenen Transfers

Besonderheiten:

  • Persistenz über JsonFileStore<Transfer> mit TransferJsonConverter (transfers.json)
  • Echtzeit-Fortschritt via ConcurrentDictionary<Guid, TransferProgress> – flüchtig, nicht persistiert

Services / Security: ICredentialProtector / AesCredentialProtector

Abstraktion und Implementierung für sichere Verwaltung von Zugangsdaten. Ersetzt die veraltete DES-Implementierung aus SOWITransfer.

// Registrierung (Composition Root / Program.cs)
var key = Convert.FromBase64String(
    Environment.GetEnvironmentVariable("TRANSFERX_CRYPT_KEY")!);
var iv = Convert.FromBase64String(
    Environment.GetEnvironmentVariable("TRANSFERX_CRYPT_IV")!);

services.AddSingleton(new CredentialProtectorOptions(key, iv));
services.AddSingleton<ICredentialProtector, AesCredentialProtector>();
Methode Beschreibung
Protect(plainText) Verschlüsselt Klartext, gibt Base64-kodierten String zurück
Unprotect(text) Entschlüsselt Base64-kodierten String, gibt Klartext zurück

Besonderheiten:

  • AES-256-CBC mit PKCS7-Padding
  • Key: 32 Bytes (256 Bit), IV: 16 Bytes (128 Bit)
  • Key und IV werden extern über Umgebungsvariablen bereitgestellt (TRANSFERX_CRYPT_KEY, TRANSFERX_CRYPT_IV)
  • Plattformunabhängig – kein DPAPI (Windows, Linux, macOS)

Abhängigkeiten

graph TD
    Infrastructure["🔧 TransferX.Infrastructure"]
    Application["📋 TransferX.Application"]
    Domain["💎 TransferX.Domain"]

    Infrastructure --> Application
    Infrastructure --> Domain
    
    style Infrastructure fill:#ffe0b2,stroke:#e65100,stroke-width:2px
    style Application fill:#fff9c4,stroke:#fbc02d,stroke-width:2px
    style Domain fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
Paket / Projekt Version Zweck
TransferX.Application Ports (Interfaces) für Repository & Services
Microsoft.AspNetCore.SignalR.Client 8.0.15 Echtzeit-Kommunikation (SignalR Reporter)
Microsoft.Extensions.DependencyInjection.Abstractions 9.0.0 DI-Abstraktion
Microsoft.Extensions.Logging.Abstractions 9.0.0 ILogger-Abstraktion für Log
Serilog 4.3.1 Strukturiertes Logging
Serilog.Sinks.Console 6.1.1 Konsolen-Ausgabe
Serilog.Sinks.File 7.0.0 Datei-Ausgabe

Architecture Decision Records (ADR)

Entscheidung Kontext Begründung
JSON Persistence Phase 1 – kein EF Core Einfach, keine DB-Abhängigkeit, ersetzbar durch EF Core
TransferJsonConverter Transfer hat privaten Konstruktor Standardmässige Deserialisierung nicht möglich – manueller Konverter nötig
Record-Mapping in Repositories ProviderConfiguration / TransferConfiguration haben private Konstruktoren Flaches POCO-Modell entkoppelt JSON-Struktur von Domain-Objekten
AES-256 statt DES Ersatz für SOWITransfer DES-Implementierung DES kryptographisch unsicher, AES-256 ist aktueller Standard
Externer Key/IV Sicherheitsanforderung für Credentials Kein hartcodierter Key – Umgebungsvariablen / DI / appsettings
In-Memory Fortschritt TransferProgress nicht persistieren Echtzeit-Daten sind flüchtig, Persistenz wäre unnötiger Overhead
ILog mit In-Memory UI benötigt Log-Anzeige ohne direkte ILogger-Bindung Kombination von DI-Logging und UI-freundlicher Sammlung
Plattformunabhängig Kein DPAPI für Verschlüsselung Windows/Linux/macOS-Kompatibilität ohne OS-Abhängigkeit
ownerProcessId im Converter Parallele Prozessinstanzen können laufende Transfers fälschlich als verwaist einstufen PID wird persistiert und beim Laden wiederhergestellt – Stale-Transfer-Erkennung funktioniert prozessübergreifend korrekt