Projekt: TransferX.Application

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

Rolle in der Architektur: Application Layer definiert Use Cases und Ports (Interfaces). Kein direkter Zugriff auf Infrastruktur, Datenbank oder UI. Abhängigkeiten ausschliesslich auf TransferX.Domain.

Projektstruktur

TransferX.Application
|   TransferX.Application.csproj
|
+---Common
|   +---Interfaces
|   |       ICommand.cs
|   |       ICommandHandler.cs
|   |       IProgressReporter.cs
|   |       IQuery.cs
|   |       IQueryHandler.cs
|   |
|   \---Models
|           OperationResult.cs
|           Result.cs
|
+---Features
|   +---Providers
|   |   +---Commands
|   |   |   +---DeleteProvider
|   |   |   |       DeleteProviderCommand.cs
|   |   |   |       DeleteProviderHandler.cs
|   |   |   |
|   |   |   +---RegisterProvider
|   |   |   |       RegisterProviderCommand.cs
|   |   |   |       RegisterProviderHandler.cs
|   |   |   |
|   |   |   +---TestProviderConnection
|   |   |   |       TestProviderConnectionCommand.cs
|   |   |   |       TestProviderConnectionHandler.cs
|   |   |   |
|   |   |   \---UpdateProviderConfig
|   |   |           UpdateProviderConfigCommand.cs
|   |   |           UpdateProviderConfigHandler.cs
|   |   |
|   |   \---Queries
|   |       +---BrowseProviderPath
|   |       |       BrowseProviderPathHandler.cs
|   |       |       BrowseProviderPathQuery.cs
|   |       |       BrowseResultDto.cs
|   |       |
|   |       +---GetProviderDetails
|   |       |       GetProviderDetailsHandler.cs
|   |       |       GetProviderDetailsQuery.cs
|   |       |
|   |       +---ListAvailablePlugins
|   |       |       ListAvailablePluginsHandler.cs
|   |       |       ListAvailablePluginsQuery.cs
|   |       |       PluginInfoDto.cs
|   |       |
|   |       \---ListProviders
|   |               ListProvidersHandler.cs
|   |               ListProvidersQuery.cs
|   |               ProviderDto.cs
|   |
|   +---TransferConfigs
|   |   +---Commands
|   |   |   +---DeleteTransferConfig
|   |   |   |       DeleteTransferConfigCommand.cs
|   |   |   |       DeleteTransferConfigHandler.cs
|   |   |   |
|   |   |   +---RenameTransferConfig
|   |   |   |       RenameTransferConfigCommand.cs
|   |   |   |       RenameTransferConfigHandler.cs
|   |   |   |
|   |   |   \---SaveTransferConfig
|   |   |           SaveTransferConfigCommand.cs
|   |   |           SaveTransferConfigHandler.cs
|   |   |
|   |   \---Queries
|   |       \---ListTransferConfigs
|   |               ListTransferConfigsHandler.cs
|   |               ListTransferConfigsQuery.cs
|   |               TransferConfigDto.cs
|   |
|   \---Transfers
|       +---Commands
|       |   +---CancelTransfer
|       |   |       CancelTransferCommand.cs
|       |   |       CancelTransferHandler.cs
|       |   |
|       |   \---StartTransfer
|       |           StartTransferCommand.cs
|       |           StartTransferHandler.cs
|       |
|       \---Queries
|           +---GetTransferProgress
|           |       GetTransferProgressHandler.cs
|           |       GetTransferProgressQuery.cs
|           |       TransferProgressDto.cs
|           |
|           +---GetTransferStatus
|           |       GetTransferStatusHandler.cs
|           |       GetTransferStatusQuery.cs
|           |       TransferStatusDto.cs
|           |
|           \---ListTransfers
|                   ListTransfersHandler.cs
|                   ListTransfersQuery.cs
|                   TransferListDto.cs
|
\---Interfaces
    +---Providers
    |       FileItemDto.cs
    |       FolderItemDto.cs
    |       IProviderEngine.cs
    |       IProviderHandle.cs
    |       IProviderRepository.cs
    |
    \---Transfers
            ITransferConfigRepository.cs
            ITransferEngine.cs
            ITransferRepository.cs
            TransferConfigDto.cs
            TransferResultDto.cs

Abhängigkeiten

graph TD
    App["TransferX.Application"]
    Domain["TransferX.Domain"]

    App --> Domain
    
    style App fill:#fff9c4,stroke:#fbc02d,stroke-width:2px
    style Domain fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
Paket / Projekt Version Zweck
TransferX.Domain Domain-Entitäten, Value Objects, Enums
FluentValidation 12.1.1 Input-Validierung in Commands
MediatR 12.4.1 Command/Query Dispatching (optional)
Microsoft.Extensions.DependencyInjection 9.0.0 DI-Abstraktion

Wichtig: Die Application hat keine Referenz auf Provider.Abstractions oder Transfer.Abstractions. Alle Typen an den Port-Interfaces sind Application-eigene DTOs.

Komponenten

Common – Interfaces

Interface Beschreibung
ICommand<TResponse> Marker für Commands (zustandsändernde Operationen) im CQS-Muster
ICommandHandler<TCommand, TResponse> Handler-Interface für Commands
IQuery<TResponse> Marker für Queries (lesende Operationen) im CQS-Muster
IQueryHandler<TQuery, TResponse> Handler-Interface für Queries
IProgressReporter Port-Interface für Fortschrittsmeldungen an die Presentation-Schicht

Common – Models

Klasse Beschreibung
Result<TValue> Generisches Ergebnis-Objekt mit Erfolg, Fehlermeldung und Rückgabewert
OperationResult Einfaches Ergebnis-Objekt für Operationen ohne Rückgabewert

Interfaces (Ports)

Interface / Klasse Namespace Beschreibung
IProviderEngine Interfaces.Providers Port für Provider-Operationen (Browse, Test)
IProviderHandle Interfaces.Providers Opakes Handle auf einen geladenen Provider
IProviderRepository Interfaces.Providers Port für Persistenz der Provider-Konfigurationen
FileItemDto Interfaces.Providers Application-DTO für eine Datei im Provider-Dateisystem
FolderItemDto Interfaces.Providers Application-DTO für einen Ordner im Provider-Dateisystem
ITransferEngine Interfaces.Transfers Port für Transfer-Ausführung (Start, Fortschritt)
ITransferConfigRepository Interfaces.Transfers Port für Persistenz der Transfer-Konfigurationen
ITransferRepository Interfaces.Transfers Port für Persistenz und Fortschritts-Cache der Transfers
TransferConfigDto Interfaces.Transfers Application-DTO für Transfer-Konfiguration
TransferResultDto Interfaces.Transfers Application-DTO für Transfer-Ergebnis

Features – Provider

Klasse Typ Rückgabe Beschreibung
RegisterProviderCommand Command Result<Guid> Registriert eine neue Provider-Konfiguration
RegisterProviderHandler Handler Result<Guid> Erstellt ProviderConfiguration und persistiert sie
UpdateProviderConfigCommand Command OperationResult Aktualisiert Name und Zugangsdaten eines Providers
UpdateProviderConfigHandler Handler OperationResult Lädt Provider, ruft Rename und UpdateCredentials auf
DeleteProviderCommand Command OperationResult Löscht eine Provider-Konfiguration
DeleteProviderHandler Handler OperationResult Delegiert an IProviderRepository.DeleteAsync
TestProviderConnectionCommand Command OperationResult Testet die Verbindung zu einem Provider
TestProviderConnectionHandler Handler OperationResult Delegiert an IProviderEngine.TestConnectionAsync
ListProvidersQuery Query IReadOnlyList<ProviderDto> Gibt alle Provider-Konfigurationen zurück
ListProvidersHandler Handler IReadOnlyList<ProviderDto> Liest Repository, mappt auf ProviderDto
GetProviderDetailsQuery Query Result<ProviderDto> Gibt Details einer einzelnen Provider-Konfiguration zurück
GetProviderDetailsHandler Handler Result<ProviderDto> Sucht per ID, gibt Result.Fail wenn nicht gefunden
BrowseProviderPathQuery Query Result<BrowseResultDto> Listet Ordner und Dateien unter einem Pfad auf
BrowseProviderPathHandler Handler Result<BrowseResultDto> Parallel: ListFoldersAsync + ListFilesAsync via IProviderEngine
ListAvailablePluginsQuery Query IReadOnlyList<PluginInfoDto> Listet alle verfügbaren Provider-Plugins im Plugin-Verzeichnis auf
ListAvailablePluginsHandler Handler IReadOnlyList<PluginInfoDto> Delegiert an IProviderEngine, gibt PluginInfoDto-Liste zurück

Features – TransferConfig

Klasse Typ Rückgabe Beschreibung
SaveTransferConfigCommand Command Result<Guid> Erstellt eine neue Transfer-Konfiguration
SaveTransferConfigHandler Handler Result<Guid> Erstellt TransferConfiguration-Aggregate und persistiert es
RenameTransferConfigCommand Command OperationResult Benennt eine bestehende Transfer-Konfiguration um
RenameTransferConfigHandler Handler OperationResult Lädt Konfiguration, ruft Rename() auf und persistiert
DeleteTransferConfigCommand Command OperationResult Löscht eine Transfer-Konfiguration
DeleteTransferConfigHandler Handler OperationResult Delegiert an ITransferConfigRepository.DeleteAsync
ListTransferConfigsQuery Query IReadOnlyList<TransferConfigDto> Gibt alle gespeicherten Transfer-Konfigurationen zurück
ListTransferConfigsHandler Handler IReadOnlyList<TransferConfigDto> Liest Repository, mappt auf TransferConfigDto

Features – Transfer

Klasse Typ Rückgabe Beschreibung
StartTransferCommand Command Result<Guid> Startet einen neuen Transfer
StartTransferHandler Handler Result<Guid> Erstellt Transfer, persistiert, startet Engine (Fire-and-Forget)
CancelTransferCommand Command OperationResult Bricht einen laufenden oder ausstehenden Transfer ab
CancelTransferHandler Handler OperationResult Ruft Transfer.Cancel(), persistiert, entfernt Fortschritts-Cache
GetTransferStatusQuery Query Result<TransferStatusDto> Gibt aktuellen Status eines Transfers zurück
GetTransferStatusHandler Handler Result<TransferStatusDto> Liest Transfer-Entity aus Repository, mappt auf DTO
GetTransferProgressQuery Query Result<TransferProgressDto> Gibt aktuellen Fortschritt eines laufenden Transfers zurück
GetTransferProgressHandler Handler Result<TransferProgressDto> Liest TransferProgress aus In-Memory-Cache
ListTransfersQuery Query IReadOnlyList<TransferListDto> Gibt alle gespeicherten Transfers zurück
ListTransfersHandler Handler IReadOnlyList<TransferListDto> Liest Repository, mappt auf TransferListDto

Details

CQS-Muster

Commands und Queries sind klar nach dem Command/Query Separation-Prinzip getrennt:

graph LR
    C["ICommand&lt;TResponse&gt;"]
    CH["ICommandHandler&lt;TCommand, TResponse&gt;"]
    Q["IQuery&lt;TResponse&gt;"]
    QH["IQueryHandler&lt;TQuery, TResponse&gt;"]

    C -->|"verarbeitet"| CH
    Q -->|"verarbeitet"| QH
    
    style C fill:#ffe0b2,stroke:#e65100
    style CH fill:#ffe0b2,stroke:#e65100
    style Q fill:#e1f5fe,stroke:#01579b
    style QH fill:#e1f5fe,stroke:#01579b
Typ Zweck Beispiel
Command Ändert Zustand, gibt Result<T> oder OperationResult zurück StartTransferCommand, RegisterProviderCommand
Query Liest Daten, ändert keinen Zustand GetTransferStatusQuery, ListProvidersQuery

Result-Muster

Alle Handler verwenden ein explizites Result-Muster statt Exceptions für erwartbare Fehler:

Klasse Verwendung Factory-Methoden
Result<TValue> Commands/Queries mit Rückgabewert Result<T>.Ok(value), Result<T>.Fail(message)
OperationResult Commands ohne Rückgabewert OperationResult.Ok(), OperationResult.Fail(message)

Port-Interfaces (Hexagonal Architecture)

Die Application definiert Ports – Interfaces, die von anderen Schichten implementiert werden. Die Application kennt keine konkreten Implementierungen:

graph TD
    App["📋 Application (definiert Ports)"]
    Core["⚙️ Core (implementiert IProviderEngine, ITransferEngine)"]
    Infra["🔧 Infrastructure (implementiert IProviderRepository, ITransferRepository, ITransferConfigRepository, IProgressReporter)"]

    App -->|"Port"| Core
    App -->|"Port"| Infra
    
    style App fill:#fff9c4,stroke:#fbc02d,stroke-width:2px
    style Core fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    style Infra fill:#ffe0b2,stroke:#e65100,stroke-width:2px

DTO-Entkopplung

Die Application verwendet eigene DTOs an den Port-Interfaces, um Abhängigkeiten auf Provider.Abstractions und Transfer.Abstractions zu vermeiden:

Application-DTO Entsprechung in Abstractions Mapping-Ort
FileItemDto FileItem Core Layer (ProviderEngine)
FolderItemDto FolderItem Core Layer (ProviderEngine)
TransferConfigDto TransferConfigItem Core Layer (TransferEngine)
TransferResultDto TransferResult Core Layer (TransferEngine)

StartTransfer – Fire-and-Forget

Der StartTransferHandler gibt sofort die neue Transfer-ID zurück. Die Engine-Ausführung läuft im Hintergrund:

sequenceDiagram
    participant UI as Presentation
    participant H as StartTransferHandler
    participant R as ITransferRepository
    participant E as ITransferEngine
    
    UI->>H: HandleAsync(StartTransferCommand)
	H->>R: AddAsync(transfer)
	H->>R: UpdateAsync(transfer – Status: Running)
	H-->>UI: Resultat Guid (Transfer-ID)

	Note over H,E: Fire-and-Forget im Hintergrund
	H-)E: ExecuteAsync(config, progress)
	E-->>H: TransferResultDto
	H->>R: UpdateAsync(transfer – Complete/Fail)
	H->>R: RemoveProgress(transferId)

Fortschritt-Flow

graph LR
    SC["StartTransferCommand (IProgress&lt;ProgressReport&gt;)"]
    TE["ITransferEngine (ExecuteAsync)"]
    TR["ITransferRepository (UpdateProgress)"]
    PR["IProgressReporter (ReportAsync)"]
    UI["Presentation (SignalR, UI)"]

    SC -->|"progress callback"| TE
    TE -->|"UpdateProgress"| TR
    TE -->|"ReportAsync"| PR
    PR -->|"Push"| UI
    
    style SC fill:#fff9c4,stroke:#fbc02d
    style PR fill:#fff9c4,stroke:#fbc02d
    style UI fill:#ffff4,stroke:#f1f12d

Architecture Decision Records (ADR)

Entscheidung Kontext Begründung
Keine Abstractions-Referenz Application soll unabhängig von Plugin-Details bleiben Clean Architecture: Application → nur Domain
Eigene DTOs an Ports FileItemDto, FolderItemDto, TransferConfigDto, TransferResultDto Entkopplung ohne Abstractions-Abhängigkeit
Result-Muster statt Exceptions Erwartbare Fehler (nicht gefunden, ungültiger Status) Explizite Fehlerbehandlung, bessere Testbarkeit
Fire-and-Forget bei StartTransfer Transfer-Ausführung ist langläufig Sofortige Rückgabe der Transfer-ID, Status via Query/Progress
IProgressReporter als Port UI-Technologie (SignalR, WPF) soll austauschbar sein Infrastructure implementiert, Application definiert nur Interface
CQS ohne MediatR-Zwang ICommandHandler / IQueryHandler direkt als Interfaces Testbar ohne MediatR-Overhead, MediatR optional integrierbar

Qualitätsmerkmale

Aspekt Umsetzung
Keine Infrastruktur-Abhängigkeit Nur TransferX.Domain als Projektreferenz
Keine Abstractions-Abhängigkeit Eigene DTOs an allen Port-Interfaces
CQS-Muster ICommand<T> / IQuery<T> trennen Schreib- und Lesezugriffe
Result-Muster Result<T> und OperationResult für explizite Fehlerbehandlung
Immutabilität DTOs als sealed record mit init-Properties
Async First Alle I/O-Operationen mit CancellationToken
C# 12 / .NET 8 sealed record, file-scoped Namespaces, Collection Expressions ([])
XML-Dokumentation Alle public Members dokumentiert (Deutsch)
Nullable Reference Types Aktiviert – optionale Properties explizit als ? deklariert