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) ILoggerist 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
TransferJsonConverterfürTransfer-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 alsnullbehandelt - Abwärtskompatibel: fehlendes
ownerProcessId-Feld wird alsnullbehandelt (Legacy-Transfers) ownerProcessIdwird als JSON-Zahl (number) serialisiert –nullwenn 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,BasePathoderProviderTypewerden ü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) TransferOperationwird alsstringgespeichert (Enum.Parsebeim Laden)- Validierung beim Laden: Datensätze mit leerem
Name,SourcePathoderTargetPathwerden ü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>mitTransferJsonConverter(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 |