Projekt: TransferX.Core

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

Rolle in der Architektur: Core Layer ist die zentrale Geschäftslogik-Schicht. Implementiert die Application-Ports IProviderEngine und ITransferEngine, orchestriert Domain-Objekte und lädt Plugins dynamisch. Referenziert Application, Domain, Provider.Abstractions und Transfer.Abstractions.

Projektstruktur

TransferX.Core
|   TransferX.Core.csproj
|
+---Engine
|       ProviderEngine.cs
|       ProviderHandle.cs
|       TransferEngine.cs
|
+---Loaders
|       PluginLoadContext.cs
|       ProviderLoader.cs
|       TransferLoader.cs
|
\---Services
    \---Progress
            ProgressAggregator.cs

Abhängigkeiten

graph TD
    Core["⚙️ TransferX.Core"]
    App["📋 TransferX.Application"]
    Domain["💎 TransferX.Domain"]
    PAbs["📦 TransferX.Provider.Abstractions"]
    TAbs["📦 TransferX.Transfer.Abstractions"]

    Core --> App
    Core --> Domain
    Core --> PAbs
    Core --> TAbs
    
    style Core fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    style App fill:#fff9c4,stroke:#fbc02d,stroke-width:2px
    style Domain fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
    style PAbs fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style TAbs fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
Paket / Projekt Version Zweck
TransferX.Application Ports IProviderEngine, ITransferEngine, DTOs
TransferX.Domain Domain-Exceptions, Enumerations, Value Objects
TransferX.Provider.Abstractions IProvider, Requests, Responses, Models, Metadata
TransferX.Transfer.Abstractions ITransferHandler, Models, Metadata
Microsoft.Extensions.Logging 10.x ILogger<T> für strukturiertes Logging
System.Reflection.Metadata 10.x Assembly-Metadaten für Plugin-Discovery

Komponenten

Engine

Klasse Typ Beschreibung
ProviderEngine Klasse Implementiert IProviderEngine – lädt, initialisiert und führt Provider aus
ProviderHandle Klasse Internes opakes Handle auf einen initialisierten IProvider
TransferEngine Klasse Implementiert ITransferEngine – orchestriert Transfer-Handler und Provider

Loaders

Klasse Typ Beschreibung
PluginLoadContext Klasse Isolierter AssemblyLoadContext für Plugin-Assemblies (collectible)
ProviderLoader Klasse Lädt IProvider-Plugins via Reflection und ProviderMetadataAttribute
TransferLoader Klasse Lädt ITransferHandler-Plugins via Reflection und TransferMetadataAttribute

Services / Progress

Klasse Typ Beschreibung
ProgressAggregator Klasse Multi-Level Fortschritts-Aggregator (Byte, Datei, Transfer) – thread-sicher

Plugin-Informationsmodelle (Records)

Record Beschreibung
ProviderPluginInfo Metadaten und Instanz eines gefundenen Provider-Plugins (Typ, Name, Version, Instanz)
TransferPluginInfo Metadaten und Instanz eines gefundenen Transfer-Plugins (Name, Operationen, Instanz)

Details

Engine: ProviderEngine

Implementiert den Application-Port IProviderEngine. Kapselt alle Abhängigkeiten auf TransferX.Provider.Abstractions – die Application kennt nur ihre eigenen DTOs.

// Registrierung (Composition Root / Program.cs)
services.AddSingleton(sp => new ProviderEngine(
    sp.GetRequiredService<IProviderRepository>(),
    sp.GetRequiredService<ProviderLoader>(),
    sp.GetRequiredService<ILogger<ProviderEngine>>(),
    pluginDirectory: "./plugins/providers"));

services.AddSingleton<IProviderEngine>(sp => sp.GetRequiredService<ProviderEngine>());
Methode Rückgabe Beschreibung
ListFoldersAsync(...) IReadOnlyList<FolderItemDto> Listet Ordner unter einem Pfad – mappt FolderItemFolderItemDto
ListFilesAsync(...) IReadOnlyList<FileItemDto> Listet Dateien unter einem Pfad – mappt FileItemFileItemDto
TestConnectionAsync(...) Task Testet Verbindung durch Laden und Initialisieren des Providers
GetProviderHandleAsync(...) IProviderHandle Gibt ein opakes Handle auf den initialisierten Provider zurück
LoadAndInitializeProviderAsync(...) | IProvider*(internal)* | Lädt Konfiguration, erstellt Plugin-Instanz, initialisiert – fürTransferEngine`

DTO-Mapping ProviderEngine:

Abstractions-Typ Application-DTO Mapping-Ort
FolderItem FolderItemDto ProviderEngine.ListFoldersAsync
FileItem FileItemDto ProviderEngine.ListFilesAsync
ProviderConfiguration ProviderConfigItem ProviderEngine.LoadAndInitializeProviderAsync

Engine: ProviderHandle

Internes Implementierungsdetail des Core Layers. Kapselt eine geladene und initialisierte IProvider-Instanz hinter dem Application-Interface IProviderHandle.

graph LR
    App["IProviderHandle (Application)"]
    Handle["ProviderHandle (Core – internal)"]
    Provider["IProvider (Abstractions)"]

    App -->|"opakes Handle"| Handle
    Handle -->|"internal Provider"| Provider

Besonderheiten:

  • internal sealed – nicht ausserhalb des Core Layers sichtbar
  • Die TransferEngine greift intern direkt auf ProviderHandle.Provider zu

Engine: TransferEngine

Implementiert den Application-Port ITransferEngine. Orchestriert das Laden von Transfer-Handler-Plugins und Provider-Instanzen und delegiert die eigentliche Übertragung an den ITransferHandler. Der IProgress<ProgressReport> wird direkt an den ITransferHandler weitergereicht – der Handler ist verantwortlich für die Verwendung des ProgressAggregator.

// Registrierung (Composition Root / Program.cs)
services.AddSingleton(sp => new TransferEngine(
    sp.GetRequiredService<ProviderEngine>(),
    sp.GetRequiredService<TransferLoader>(),
    sp.GetRequiredService<ILogger<TransferEngine>>(),
    pluginDirectory: "./plugins/transfers"));

services.AddSingleton<ITransferEngine>(sp => sp.GetRequiredService<TransferEngine>());
Methode Rückgabe Beschreibung
ExecuteAsync(…) TransferResultDto Lädt Handler + Provider, führt Transfer aus, mappt Ergebnis auf DTO

Ablauf ExecuteAsync:

sequenceDiagram
    participant App as Application (StartTransferHandler)
    participant TE as TransferEngine
    participant TL as TransferLoader
    participant PE as ProviderEngine
    participant TH as ITransferHandler

    App->>TE: ExecuteAsync(transferId, config, progress, ct)
    TE->>TL: CreateHandler(pluginDirectory, operation)
    TL-->>TE: ITransferHandler
    TE->>PE: LoadAndInitializeProviderAsync(sourceId, ct)
    PE-->>TE: IProvider (Source)
    TE->>PE: LoadAndInitializeProviderAsync(targetId, ct)
    PE-->>TE: IProvider (Target)
    TE->>TH: ExecuteAsync(configItem, source, target, progress, ct)
    TH-->>TE: TransferResult
    TE-->>App: TransferResultDto

DTO-Mapping TransferEngine:

Application-DTO Abstractions-Typ Mapping-Methode
TransferConfigDto TransferConfigItem ExecuteAsync (lokal)
TransferResult TransferResultDto MapToDto (privat)

Loaders: PluginLoadContext

Isolierter AssemblyLoadContext für Plugin-Assemblies. Verhindert Versionskonflikte zwischen Host und Plugins durch separaten Ladebereich.

Eigenschaft Wert
isCollectible true – Assemblies können entladen werden
Name Dateiname der Plugin-Assembly (ohne Extension)
Resolver AssemblyDependencyResolver pro Plugin

Besonderheiten:

  • internal sealed – wird nur von ProviderLoader und TransferLoader verwendet
  • Transitiv abhängige Assemblies des Plugins werden automatisch aufgelöst
  • Nicht aufgelöste Assemblies fallen an den Standard-AssemblyLoadContext zurück (Host-Assemblies wie TransferX.Provider.Abstractions werden damit korrekt geteilt)

Loaders: ProviderLoader

Lädt IProvider-Implementierungen dynamisch aus Plugin-Assemblies mittels Reflection.

// Verwendung in ProviderEngine
var provider = this._providerLoader.CreateProvider(
    assemblyPath: "./plugins/providers/WebDav.dll",
    providerType: "WebDav");
Methode Beschreibung
LoadPlugins(...) Lädt Assembly, gibt alle IProvider-Typen mit ProviderMetadataAttribute zurück
CreateProvider(...) Erstellt Instanz eines Providers anhand des technischen Typs (z.B. "WebDav")
DiscoverAll(...) Durchsucht ein gesamtes Verzeichnis nach Provider-Plugins (alle *.dll)

Plugin-Discovery Ablauf:

graph LR
    PL["ProviderLoader"]
    PLCtx["PluginLoadContext"]
    ASS["Plugin Assembly (*.dll)"]
    REFL["Reflection (GetExportedTypes)"]
    META["ProviderMetadataAttribute"]
    IProvider["IProvider"]
    Info["ProviderPluginInfo"]

    PL -->|"erstellt"| PLCtx
    PLCtx -->|"lädt"| ASS
    ASS -->|"scan"| REFL
    REFL -->|"prüft"| META
    REFL -->|"prüft"| IProvider
    META -->|"erzeugt"| Info

ProviderPluginInfo Record:

Property Typ Beschreibung
ProviderType string Technischer Bezeichner (z.B. "WebDav")
Name string Anzeigename des Providers
Version string Versionsnummer (z.B. "1.0.0")
Instance IProvider Bereits erstellte Provider-Instanz
AssemblyPath string Vollständiger Pfad zur geladenen Assembly

Loaders: TransferLoader

Lädt ITransferHandler-Implementierungen dynamisch aus Plugin-Assemblies mittels Reflection.

// Verwendung in TransferEngine
var handler = this._transferLoader.CreateHandler(
    assemblyPath: "./plugins/transfers/Copy.dll",
    operation: TransferOperation.Copy);
Methode Beschreibung
LoadPlugins(...) Lädt Assembly, gibt alle ITransferHandler-Typen mit TransferMetadataAttribute zurück
CreateHandler(...) Erstellt Instanz eines Handlers anhand der TransferOperation
DiscoverAll(...) Durchsucht ein Verzeichnis nach Transfer-Handler-Plugins (alle *.dll)

TransferPluginInfo Record:

Property Typ Beschreibung
Name string Anzeigename (z.B. "Copy")
Version string Versionsnummer (z.B. "1.0.0")
SupportedOperations IReadOnlyList<TransferOperation> Liste aller unterstützten Transfer-Operationen
Instance ITransferHandler Bereits erstellte Transfer-Handler-Instanz
AssemblyPath string Vollständiger Pfad zur geladenen Assembly

Services: ProgressAggregator

Multi-Level Fortschritts-Aggregator für Transfers. Aggregiert Byte-, Datei- und Transfer-Level Fortschritt und meldet kombinierte ProgressReport-Objekte an den Aufrufer. Thread-sicher durch explizites lock. Wird von ITransferHandler-Plugins verwendet – nicht direkt von der TransferEngine.

// Verwendung in einem ITransferHandler (z.B. CopyTransferHandler)
var aggregator = new ProgressAggregator(config.TransferId, progress);
aggregator.Initialize(totalFiles: files.Count, totalBytes: files.Sum(f => f.Size));

foreach (var file in files)
{
    aggregator.StartFile(file.Path, file.Size);
    // ... Datei übertragen ...
    aggregator.ReportFileProgress(file.Path, transferred, file.Size);
    // ... Übertragung abgeschlossen ...
    aggregator.CompleteFile(file.Path, file.Size);
}
Methode Beschreibung
Initialize(...) Setzt Gesamtanzahl Dateien und Gesamtgrösse – löst erste Transfer-Meldung aus
StartFile(...) Meldet Beginn einer Dateiübertragung (Status: Transferring)
ReportFileProgress(...) Meldet aktuellen Byte-Fortschritt einer laufenden Übertragung
CompleteFile(...) Meldet erfolgreichen Abschluss – erhöht ProcessedFiles, aktualisiert Bytes
FailFile(...) Meldet fehlgeschlagene Übertragung (Status: Failed)

Fortschritts-Stufen:

graph TD
    PA["ProgressAggregator"]
    FP["FileProgress (ProgressLevel.File)"]
    TP["TransferProgress (ProgressLevel.Transfer)"]
    PR["ProgressReport"]
    CB["IProgress&lt;ProgressReport&gt; (Callback)"]

    PA -->|"ReportFileProgress"| FP
    PA -->|"ReportTransferProgress"| TP
    FP -->|"eingebettet in"| PR
    TP -->|"eingebettet in"| PR
    PR -->|"Report()"| CB

Berechnungen in ReportTransferProgress:

Kennzahl Berechnung
BytesPerSecond TransferredBytes / Elapsed.TotalSeconds
EstimatedTimeRemaining RemainingBytes / BytesPerSecond (null wenn 0 B/s)
ProgressPercent Berechnet im TransferProgress Value Object (Domain)

Besonderheiten:

  • Thread-sicher durch lock(this._lock) auf alle öffentlichen Methoden
  • _stopwatch startet beim Erstellen des ProgressAggregator – misst Gesamtdauer
  • Bei null-Progress werden keine Meldungen erzeugt (Zero-Cost)

Zusammenspiel der Komponenten

graph TD
    subgraph Application["📋 Application Layer"]
        PE_Port["IProviderEngine"]
        TE_Port["ITransferEngine"]
    end

    subgraph Core["⚙️ Core Layer"]
        PE["ProviderEngine"]
        PH["ProviderHandle\n(internal)"]
        TE["TransferEngine"]
        PL["ProviderLoader"]
        TL["TransferLoader"]
        PLCtx["PluginLoadContext\n(internal)"]
        PA["ProgressAggregator"]
    end
    
    subgraph Abstractions["📦 Abstractions"]
        IProvider["IProvider"]
        IHandler["ITransferHandler"]
    end
    
    subgraph Domain["💎 Domain"]
        DomainExc["Exceptions"]
        ProgressDomain["Progress Value Objects"]
    end
    
    PE_Port --> PE
    TE_Port --> TE
    
    PE --> PL
    PE --> PH
    TE --> PE
    TE --> TL
    
    PL --> PLCtx
    TL --> PLCtx
    PLCtx --> IProvider
    PLCtx --> IHandler
    IHandler -.->|"verwendet"| PA
    
    PE --> DomainExc
    TE --> DomainExc
    PA --> ProgressDomain
    
    style Application fill:#fff9c4,stroke:#fbc02d,stroke-width:2px
    style Core fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    style Abstractions fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style Domain fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px

Architecture Decision Records (ADR)

Entscheidung Kontext Begründung
ProviderEngine als sealed class statt Interface Core ist Port-Implementierung IProviderEngine ist der nach aussen sichtbare Port; intern braucht TransferEngine direkten Zugriff auf internal-Methoden
LoadAndInitializeProviderAsync als internal TransferEngine und ProviderEngine liegen im selben Assembly Direkte Provider-Instanz nötig ohne Application-Port zu umgehen; IProviderHandle wäre unzureichend
PluginLoadContext mit isCollectible: true Plugins sollen hot-swappable sein Collectible Contexts können entladen werden; ermöglicht zukünftiges Plugin-Reload ohne Neustart
ProviderLoader.CreateProvider sucht per Typ-String ProviderConfiguration.ProviderType ist ein string Typ-String aus der Konfiguration bestimmt das Plugin; kein Enum-Coupling zwischen Domain und Abstractions
TransferLoader.CreateHandler sucht per TransferOperation TransferOperation ist Domain-Enum Transfer-Operationen sind im Domain definiert; kein zusätzlicher String-Vergleich nötig
ProgressAggregator als eigenständige Klasse Transfer-Handler (Plugins) müssen Fortschritt melden Trennung von Aggregations-Logik und Transfer-Logik; Handler bleiben schlank
Keine DI-Registrierung für ProgressAggregator Instanz ist pro Transfer Transient-Instanz mit transferspezifischer Guid – nicht sinnvoll als DI-Service
PluginLoadContext als internal sealed Nur von Loadern verwendet Kein öffentliches API; Implementierungsdetail des Loader-Mechanismus
ProgressAggregator nicht in TransferEngine IProgress<ProgressReport> wird direkt weitergereicht Die TransferEngine delegiert Progress-Handling vollständig an den ITransferHandler; kein zusätzlicher Adapter nötig
Marker-Attribut ohne Parameter ProviderMetadataAttribute / TransferMetadataAttribute sind parameterlos Metadaten (Name, Version, Typ/Operationen) werden nach Instanziierung direkt über das Interface gelesen – Single Responsibility

Qualitätsmerkmale

Aspekt Umsetzung
Port-Implementierung ProviderEngine und TransferEngine implementieren Application-Ports (IProviderEngine, ITransferEngine)
DTO-Entkopplung Core mappt intern zwischen Abstractions-Typen und Application-DTOs
Plugin-Isolation PluginLoadContext isoliert Plugin-Assemblies – keine Versionskonflikte mit Host
Thread-Sicherheit ProgressAggregator ist thread-sicher durch explizites lock
Async First Alle I/O-Operationen mit CancellationToken
Fehlerbehandlung Domain-spezifische Exceptions (ProviderException, TransferException) mit when-Filter
C# 12 / .NET 8 Primary Constructors, file-scoped Namespaces, Collection Expressions ([])
XML-Dokumentation Alle public Members dokumentiert (Deutsch)
Nullable Reference Types Aktiviert – optionale Parameter explizit als ? deklariert