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<TResponse>"]
CH["ICommandHandler<TCommand, TResponse>"]
Q["IQuery<TResponse>"]
QH["IQueryHandler<TQuery, TResponse>"]
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<ProgressReport>)"]
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 |